mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
chore: enforce test directory conventions (#496)
* chore: enforce test directory conventions Move package, app, and tool tests out of src and add guard enforcement so source directories stay source-only. * ci: use guard and package-scoped tests Run the new repository guard in CI and keep test execution aligned with package-scoped commands after removing root aliases. * ci: align stable release guard check Use the new repository guard in stable release verification after replacing the residual-JS-only script. * chore: tighten test layout enforcement Enforce sibling tests directories, typecheck moved test suites with dedicated configs, and refresh remaining guidance that pointed at src-based tests. * chore: clarify no-emit test tsconfigs Explicitly disable declaration-only emit in test tsconfigs so review tooling sees they are no-emit typecheck configs.
This commit is contained in:
parent
16693fdfa8
commit
bbdd4e84b5
90 changed files with 562 additions and 399 deletions
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
|
|
@ -72,11 +72,20 @@ jobs:
|
||||||
- name: Typecheck workspaces
|
- name: Typecheck workspaces
|
||||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||||
|
|
||||||
- name: Check residual JS in TypeScript packages
|
- name: Check repository layout policies
|
||||||
run: pnpm check:residual-js
|
run: pnpm guard
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm test
|
run: |
|
||||||
|
pnpm --filter @open-design/e2e test
|
||||||
|
pnpm --filter @open-design/contracts test
|
||||||
|
pnpm --filter @open-design/platform test
|
||||||
|
pnpm --filter @open-design/sidecar test
|
||||||
|
pnpm --filter @open-design/sidecar-proto test
|
||||||
|
pnpm --filter @open-design/daemon test
|
||||||
|
pnpm --filter @open-design/web test
|
||||||
|
pnpm --filter @open-design/tools-dev test
|
||||||
|
pnpm --filter @open-design/tools-pack test
|
||||||
|
|
||||||
# Keep workspace builds serialized so generated dist output and local
|
# Keep workspace builds serialized so generated dist output and local
|
||||||
# runtime artifacts are produced in a deterministic order. Parallel
|
# runtime artifacts are produced in a deterministic order. Parallel
|
||||||
|
|
|
||||||
4
.github/workflows/release-stable.yml
vendored
4
.github/workflows/release-stable.yml
vendored
|
|
@ -86,8 +86,8 @@ jobs:
|
||||||
- name: Typecheck workspaces
|
- name: Typecheck workspaces
|
||||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||||
|
|
||||||
- name: Check residual JS in TypeScript packages
|
- name: Check repository layout policies
|
||||||
run: pnpm check:residual-js
|
run: pnpm guard
|
||||||
|
|
||||||
# Workspace tests are intentionally not gated here. apps/web's
|
# Workspace tests are intentionally not gated here. apps/web's
|
||||||
# i18n content-coverage tests assert that every locale carries
|
# i18n content-coverage tests assert that every locale carries
|
||||||
|
|
|
||||||
28
AGENTS.md
28
AGENTS.md
|
|
@ -35,7 +35,7 @@ This file is the single source of truth for agents entering this repository. Rea
|
||||||
|
|
||||||
- Runtime target is Node `~24` and `pnpm@10.33.2`; use Corepack so the pnpm version pinned in `package.json` is selected.
|
- Runtime target is Node `~24` and `pnpm@10.33.2`; use Corepack so the pnpm version pinned in `package.json` is selected.
|
||||||
- New project-owned entrypoints, modules, scripts, tests, reporters, and configs should default to TypeScript.
|
- New project-owned entrypoints, modules, scripts, tests, reporters, and configs should default to TypeScript.
|
||||||
- Residual JavaScript is limited to generated output, vendored dependencies, explicitly documented compatibility build artifacts, and the allowlist in `scripts/check-residual-js.ts`.
|
- Residual JavaScript is limited to generated output, vendored dependencies, explicitly documented compatibility build artifacts, and the allowlist in `scripts/guard.ts`.
|
||||||
|
|
||||||
## Local lifecycle
|
## Local lifecycle
|
||||||
|
|
||||||
|
|
@ -44,12 +44,19 @@ This file is the single source of truth for agents entering this repository. Rea
|
||||||
- Ports are governed by `tools-dev` flags: `--daemon-port` and `--web-port`.
|
- Ports are governed by `tools-dev` flags: `--daemon-port` and `--web-port`.
|
||||||
- `tools-dev` exports `OD_PORT` for the web proxy target and `OD_WEB_PORT` for the web listener; do not use `NEXT_PORT`.
|
- `tools-dev` exports `OD_PORT` for the web proxy target and `OD_WEB_PORT` for the web listener; do not use `NEXT_PORT`.
|
||||||
|
|
||||||
|
## Root command boundary
|
||||||
|
|
||||||
|
- Keep root scripts reserved for true repo-level checks and tools control-plane entrypoints: `pnpm guard`, `pnpm typecheck`, `pnpm tools-dev`, and `pnpm tools-pack`.
|
||||||
|
- Do not add root aggregate `pnpm build` or `pnpm test` aliases. Build/test commands must stay package-scoped (`pnpm --filter <package> ...`) or tool-scoped (`pnpm tools-pack ...`).
|
||||||
|
- E2E commands must be run from the e2e package boundary with `pnpm -C e2e ...`; do not add root e2e aliases.
|
||||||
|
|
||||||
## Boundary constraints
|
## Boundary constraints
|
||||||
|
|
||||||
|
- Tests under `apps/`, `packages/`, and `tools/` live in a package/app/tool-level `tests/` directory sibling to `src/`; keep `src/` source-only and do not add new `*.test.ts` or `*.test.tsx` files under `src/`.
|
||||||
- Keep shared API DTOs, SSE event unions, error shapes, task shapes, and example payloads in `packages/contracts`; update contracts before wiring divergent web/daemon request or response shapes.
|
- Keep shared API DTOs, SSE event unions, error shapes, task shapes, and example payloads in `packages/contracts`; update contracts before wiring divergent web/daemon request or response shapes.
|
||||||
- Keep `packages/contracts` pure TypeScript and free of Next.js, Express, Node filesystem/process APIs, browser APIs, SQLite, daemon internals, and sidecar control-plane dependencies.
|
- Keep `packages/contracts` pure TypeScript and free of Next.js, Express, Node filesystem/process APIs, browser APIs, SQLite, daemon internals, and sidecar control-plane dependencies.
|
||||||
- Keep project-owned entrypoints, modules, scripts, tests, reporters, and configs TypeScript-first; generated `dist/*.js` is runtime output, and source edits belong in `.ts` files.
|
- Keep project-owned entrypoints, modules, scripts, tests, reporters, and configs TypeScript-first; generated `dist/*.js` is runtime output, and source edits belong in `.ts` files.
|
||||||
- New `.js`, `.mjs`, or `.cjs` files need an explicit generated/vendor/compatibility reason and must pass `pnpm check:residual-js`.
|
- New `.js`, `.mjs`, or `.cjs` files need an explicit generated/vendor/compatibility reason and must pass `pnpm guard`.
|
||||||
- App business logic must not know about sidecar/control-plane concepts. Keep sidecar awareness in `apps/<app>/sidecar` or the desktop sidecar entry wrapper.
|
- App business logic must not know about sidecar/control-plane concepts. Keep sidecar awareness in `apps/<app>/sidecar` or the desktop sidecar entry wrapper.
|
||||||
- Shared web/daemon app contracts belong in `packages/contracts`; that package must not depend on Next.js, Express, Node filesystem/process APIs, browser APIs, SQLite, daemon internals, or the sidecar control-plane protocol.
|
- Shared web/daemon app contracts belong in `packages/contracts`; that package must not depend on Next.js, Express, Node filesystem/process APIs, browser APIs, SQLite, daemon internals, or the sidecar control-plane protocol.
|
||||||
- Sidecar process stamps must have exactly five fields: `app`, `mode`, `namespace`, `ipc`, and `source`.
|
- Sidecar process stamps must have exactly five fields: `app`, `mode`, `namespace`, `ipc`, and `source`.
|
||||||
|
|
@ -64,7 +71,7 @@ This file is the single source of truth for agents entering this repository. Rea
|
||||||
## Validation strategy
|
## Validation strategy
|
||||||
|
|
||||||
- After package, workspace, or command-entry changes, run `pnpm install` so workspace links and generated dist entries stay fresh.
|
- After package, workspace, or command-entry changes, run `pnpm install` so workspace links and generated dist entries stay fresh.
|
||||||
- Before marking regular work ready, run at least `pnpm typecheck` and `pnpm test`; run `pnpm build` as well when build boundaries are involved.
|
- Before marking regular work ready, run at least `pnpm guard` and `pnpm typecheck`, plus the package-scoped tests/builds that match the files changed. Do not use or add root `pnpm test`/`pnpm build` aliases.
|
||||||
- For the web/e2e loop, prefer `pnpm tools-dev run web --daemon-port <port> --web-port <port>`.
|
- For the web/e2e loop, prefer `pnpm tools-dev run web --daemon-port <port> --web-port <port>`.
|
||||||
- On a GUI-capable machine, validate desktop by running `pnpm tools-dev`, then `pnpm tools-dev inspect desktop status`.
|
- On a GUI-capable machine, validate desktop by running `pnpm tools-dev`, then `pnpm tools-dev inspect desktop status`.
|
||||||
- Stamp/namespace changes must validate two concurrent namespaces and run desktop `inspect eval` plus `inspect screenshot` for each namespace.
|
- Stamp/namespace changes must validate two concurrent namespaces and run desktop `inspect eval` plus `inspect screenshot` for each namespace.
|
||||||
|
|
@ -86,23 +93,22 @@ pnpm tools-dev check
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
pnpm guard
|
||||||
pnpm typecheck
|
pnpm typecheck
|
||||||
pnpm test
|
pnpm -C e2e test:ui
|
||||||
pnpm build
|
pnpm -C e2e test:ui:headed
|
||||||
pnpm test:ui
|
pnpm -C e2e test:e2e:live
|
||||||
pnpm test:ui:headed
|
|
||||||
pnpm test:e2e:live
|
|
||||||
pnpm check:residual-js
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter @open-design/web typecheck
|
pnpm --filter @open-design/web typecheck
|
||||||
|
pnpm --filter @open-design/web test
|
||||||
|
pnpm --filter @open-design/web build
|
||||||
pnpm --filter @open-design/daemon test
|
pnpm --filter @open-design/daemon test
|
||||||
|
pnpm --filter @open-design/daemon build
|
||||||
pnpm --filter @open-design/desktop build
|
pnpm --filter @open-design/desktop build
|
||||||
pnpm --filter @open-design/tools-dev build
|
pnpm --filter @open-design/tools-dev build
|
||||||
pnpm --filter @open-design/tools-pack build
|
pnpm --filter @open-design/tools-pack build
|
||||||
pnpm -r --if-present run typecheck
|
|
||||||
pnpm -r --if-present run test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ corepack enable # wählt das gepinnte pnpm aus packageManager
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # daemon + web foreground loop
|
pnpm tools-dev run web # daemon + web foreground loop
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # production build
|
pnpm --filter @open-design/web build # Web-Paket bei Bedarf bauen
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24` und pnpm `10.33.x` sind erforderlich. `nvm` / `fnm` sind optional; nutzen Sie `nvm install 24 && nvm use 24` oder `fnm install 24 && fnm use 24`, wenn Sie Node so verwalten. macOS, Linux und WSL2 sind die primären Pfade. Windows nativ sollte funktionieren, ist aber kein primäres Ziel.
|
Node `~24` und pnpm `10.33.x` sind erforderlich. `nvm` / `fnm` sind optional; nutzen Sie `nvm install 24 && nvm use 24` oder `fnm install 24 && fnm use 24`, wenn Sie Node so verwalten. macOS, Linux und WSL2 sind die primären Pfade. Windows nativ sollte funktionieren, ist aber kein primäres Ziel.
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ corepack enable # sélectionne la version de pnpm définie par package
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # boucle daemon + web au premier plan
|
pnpm tools-dev run web # boucle daemon + web au premier plan
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # build production
|
pnpm --filter @open-design/web build # build du paquet web si nécessaire
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24` et pnpm `10.33.x` sont requis. `nvm` / `fnm` sont optionnels ;
|
Node `~24` et pnpm `10.33.x` sont requis. `nvm` / `fnm` sont optionnels ;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ corepack enable # packageManager で指定された pnpm を選択
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # daemon + web フォアグラウンドループ
|
pnpm tools-dev run web # daemon + web フォアグラウンドループ
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # プロダクションビルド
|
pnpm --filter @open-design/web build # 必要に応じて web パッケージをビルド
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24` と pnpm `10.33.x` が必要です。`nvm` / `fnm` はオプション。使用する場合は `nvm install 24 && nvm use 24` または `fnm install 24 && fnm use 24` を実行してください。macOS、Linux、WSL2 が主要プラットフォームです。Windows ネイティブでも動作するはずですが、主要ターゲットではありません — 動作しない場合は issue を作成してください。
|
Node `~24` と pnpm `10.33.x` が必要です。`nvm` / `fnm` はオプション。使用する場合は `nvm install 24 && nvm use 24` または `fnm install 24 && fnm use 24` を実行してください。macOS、Linux、WSL2 が主要プラットフォームです。Windows ネイティブでも動作するはずですが、主要ターゲットではありません — 動作しない場合は issue を作成してください。
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ corepack enable # selects the pinned pnpm from packageManager
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # daemon + web foreground loop
|
pnpm tools-dev run web # daemon + web foreground loop
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # production build
|
pnpm --filter @open-design/web build # web package build when needed
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24` and pnpm `10.33.x` are required. `nvm` / `fnm` are optional; use `nvm install 24 && nvm use 24` or `fnm install 24 && fnm use 24` if you prefer managing Node that way. macOS, Linux, and WSL2 are the primary paths. Windows native should work but isn't a primary target — file an issue if it doesn't.
|
Node `~24` and pnpm `10.33.x` are required. `nvm` / `fnm` are optional; use `nvm install 24 && nvm use 24` or `fnm install 24 && fnm use 24` if you prefer managing Node that way. macOS, Linux, and WSL2 are the primary paths. Windows native should work but isn't a primary target — file an issue if it doesn't.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ corepack enable # selects the pinned pnpm from packageManager
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # daemon + web foreground loop
|
pnpm tools-dev run web # daemon + web foreground loop
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # production build
|
pnpm --filter @open-design/web build # build do pacote web quando necessário
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24` e pnpm `10.33.x` são obrigatórios. `nvm` / `fnm` são opcionais; use `nvm install 24 && nvm use 24` ou `fnm install 24 && fnm use 24` se preferir gerenciar Node assim. macOS, Linux e WSL2 são os caminhos principais. Windows nativo costuma funcionar, mas não é alvo principal — abra uma issue se quebrar.
|
Node `~24` e pnpm `10.33.x` são obrigatórios. `nvm` / `fnm` são opcionais; use `nvm install 24 && nvm use 24` ou `fnm install 24 && fnm use 24` se preferir gerenciar Node assim. macOS, Linux e WSL2 são os caminhos principais. Windows nativo costuma funcionar, mas não é alvo principal — abra uma issue se quebrar.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ corepack enable # 使用 packageManager 固定的 pnpm
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm tools-dev run web # daemon + web 前台闭环
|
pnpm tools-dev run web # daemon + web 前台闭环
|
||||||
pnpm typecheck # tsc -b --noEmit
|
pnpm typecheck # tsc -b --noEmit
|
||||||
pnpm build # 生产构建
|
pnpm --filter @open-design/web build # 需要时构建 web package
|
||||||
```
|
```
|
||||||
|
|
||||||
要求 Node `~24` 和 pnpm `10.33.x`。`nvm` / `fnm` 是可选路径;如果你习惯用它们,先执行 `nvm install 24 && nvm use 24` 或 `fnm install 24 && fnm use 24`。macOS、Linux、WSL2 是主要路径。Windows 原生应该能跑但不是主要目标 —— 跑不起来请开 issue。
|
要求 Node `~24` 和 pnpm `10.33.x`。`nvm` / `fnm` 是可选路径;如果你习惯用它们,先执行 `nvm install 24 && nvm use 24` 或 `fnm install 24 && fnm use 24`。macOS、Linux、WSL2 是主要路径。Windows 原生应该能跑但不是主要目标 —— 跑不起来请开 issue。
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pnpm tools-dev logs # daemon/web/desktop logs anzeigen
|
||||||
pnpm tools-dev check # status + aktuelle logs + gängige Diagnosen
|
pnpm tools-dev check # status + aktuelle logs + gängige Diagnosen
|
||||||
pnpm tools-dev stop # verwaltete Runtimes stoppen
|
pnpm tools-dev stop # verwaltete Runtimes stoppen
|
||||||
pnpm --filter @open-design/daemon build # apps/daemon/dist/cli.js für `od` bauen
|
pnpm --filter @open-design/daemon build # apps/daemon/dist/cli.js für `od` bauen
|
||||||
pnpm build # Production Build + static export nach apps/web/out/
|
pnpm --filter @open-design/web build # Web-Paket bei Bedarf bauen
|
||||||
pnpm typecheck # Workspace-Typecheck
|
pnpm typecheck # Workspace-Typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ pnpm tools-dev logs # affiche les logs daemon/web/desktop
|
||||||
pnpm tools-dev check # statut + logs récents + diagnostics courants
|
pnpm tools-dev check # statut + logs récents + diagnostics courants
|
||||||
pnpm tools-dev stop # arrête les runtimes gérés
|
pnpm tools-dev stop # arrête les runtimes gérés
|
||||||
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js pour `od`
|
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js pour `od`
|
||||||
pnpm build # build production + export static vers apps/web/out/
|
pnpm --filter @open-design/web build # build du paquet web si nécessaire
|
||||||
pnpm typecheck # typecheck du workspace
|
pnpm typecheck # typecheck du workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pnpm tools-dev logs # daemon/web/desktop のログを表示
|
||||||
pnpm tools-dev check # status + 最近のログ + 一般的な診断
|
pnpm tools-dev check # status + 最近のログ + 一般的な診断
|
||||||
pnpm tools-dev stop # 管理対象ランタイムを停止
|
pnpm tools-dev stop # 管理対象ランタイムを停止
|
||||||
pnpm --filter @open-design/daemon build # `od` 用に apps/daemon/dist/cli.js をビルド
|
pnpm --filter @open-design/daemon build # `od` 用に apps/daemon/dist/cli.js をビルド
|
||||||
pnpm build # 本番ビルド + apps/web/out/ への静的エクスポート
|
pnpm --filter @open-design/web build # 必要に応じて web パッケージをビルド
|
||||||
pnpm typecheck # workspace の typecheck
|
pnpm typecheck # workspace の typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pnpm tools-dev logs # show daemon/web/desktop logs
|
||||||
pnpm tools-dev check # status + recent logs + common diagnostics
|
pnpm tools-dev check # status + recent logs + common diagnostics
|
||||||
pnpm tools-dev stop # stop managed runtimes
|
pnpm tools-dev stop # stop managed runtimes
|
||||||
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js for `od`
|
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js for `od`
|
||||||
pnpm build # production build + static export to apps/web/out/
|
pnpm --filter @open-design/web build # build the web package when needed
|
||||||
pnpm typecheck # workspace typecheck
|
pnpm typecheck # workspace typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pnpm tools-dev logs # show daemon/web/desktop logs
|
||||||
pnpm tools-dev check # status + recent logs + common diagnostics
|
pnpm tools-dev check # status + recent logs + common diagnostics
|
||||||
pnpm tools-dev stop # stop managed runtimes
|
pnpm tools-dev stop # stop managed runtimes
|
||||||
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js for `od`
|
pnpm --filter @open-design/daemon build # build apps/daemon/dist/cli.js for `od`
|
||||||
pnpm build # production build + static export to apps/web/out/
|
pnpm --filter @open-design/web build # build do pacote web quando necessário
|
||||||
pnpm typecheck # workspace typecheck
|
pnpm typecheck # workspace typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ Follow the root `AGENTS.md` first. This file only records module-level boundarie
|
||||||
- `apps/daemon/sidecar/` contains the daemon sidecar entry.
|
- `apps/daemon/sidecar/` contains the daemon sidecar entry.
|
||||||
- CLI/agent argument changes or stdout parser changes belong in `apps/daemon/src/agents.ts` and the matching parser tests.
|
- CLI/agent argument changes or stdout parser changes belong in `apps/daemon/src/agents.ts` and the matching parser tests.
|
||||||
|
|
||||||
|
## Test layout
|
||||||
|
|
||||||
|
- App tests live in each app's `tests/` directory, sibling to `src/`; preserve source-relative subpaths inside `tests/` when useful.
|
||||||
|
- Keep app `src/` directories source-only; do not add new `*.test.ts` or `*.test.tsx` files under `src/`.
|
||||||
|
|
||||||
## Sidecar awareness
|
## Sidecar awareness
|
||||||
|
|
||||||
- App business layers must not import sidecar packages or branch on `runtime.mode`, `namespace`, `ipc`, or `source`.
|
- App business layers must not import sidecar packages or branch on `runtime.mode`, `namespace`, `ipc`, or `source`.
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||||
"@open-design/contracts": "workspace:0.3.0",
|
"@open-design/contracts": "workspace:*",
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
"@open-design/sidecar-proto": "workspace:*",
|
||||||
"better-sqlite3": "^12.9.0",
|
"better-sqlite3": "^12.9.0",
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
|
|
||||||
|
|
@ -862,7 +862,7 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST
|
||||||
const hints: string[] = [];
|
const hints: string[] = [];
|
||||||
if (!cliExists) {
|
if (!cliExists) {
|
||||||
hints.push(
|
hints.push(
|
||||||
'apps/daemon/dist/cli.js is missing. Run `pnpm --filter @open-design/daemon build` (or just `pnpm build`) and refresh.',
|
'apps/daemon/dist/cli.js is missing. Run `pnpm --filter @open-design/daemon build` and refresh.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!nodeExists) {
|
if (!nodeExists) {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0"
|
"@open-design/sidecar-proto": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@open-design/daemon": "workspace:0.3.0",
|
"@open-design/daemon": "workspace:*",
|
||||||
"@open-design/desktop": "workspace:0.3.0",
|
"@open-design/desktop": "workspace:*",
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
"@open-design/sidecar-proto": "workspace:*",
|
||||||
"@open-design/web": "workspace:0.3.0"
|
"@open-design/web": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.32.1",
|
"@anthropic-ai/sdk": "^0.32.1",
|
||||||
"@open-design/contracts": "workspace:0.3.0",
|
"@open-design/contracts": "workspace:*",
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
"@open-design/sidecar-proto": "workspace:*",
|
||||||
"next": "^16.2.4",
|
"next": "^16.2.4",
|
||||||
"openai": "^6.35.0",
|
"openai": "^6.35.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
|
||||||
|
|
@ -1519,7 +1519,7 @@ function IntegrationsSection() {
|
||||||
: 'Node binary is missing.'}
|
: 'Node binary is missing.'}
|
||||||
</strong>{' '}
|
</strong>{' '}
|
||||||
{info.buildHint ??
|
{info.buildHint ??
|
||||||
'apps/daemon/dist/cli.js is missing. Run `pnpm build` and refresh.'}
|
'apps/daemon/dist/cli.js is missing. Run `pnpm --filter @open-design/daemon build` and refresh.'}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
import { de } from './locales/de';
|
|
||||||
import { en } from './locales/en';
|
|
||||||
import { esES } from './locales/es-ES';
|
|
||||||
import { fa } from './locales/fa';
|
|
||||||
import { fr } from './locales/fr';
|
|
||||||
import { ja } from './locales/ja';
|
|
||||||
import { ptBR } from './locales/pt-BR';
|
|
||||||
import { ru } from './locales/ru';
|
|
||||||
import { zhCN } from './locales/zh-CN';
|
|
||||||
import { zhTW } from './locales/zh-TW';
|
|
||||||
|
|
||||||
const LOCALE_DICTS = {
|
|
||||||
de,
|
|
||||||
en,
|
|
||||||
esES,
|
|
||||||
fa,
|
|
||||||
fr,
|
|
||||||
ja,
|
|
||||||
ptBR,
|
|
||||||
ru,
|
|
||||||
zhCN,
|
|
||||||
zhTW,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Design Files agent copy', () => {
|
|
||||||
it('uses neutral agent wording in shared helper text', () => {
|
|
||||||
for (const [locale, dict] of Object.entries(LOCALE_DICTS)) {
|
|
||||||
expect(dict['designFiles.dropDesc'], locale).not.toMatch(/claude/i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
import { de } from './locales/de';
|
|
||||||
import { en } from './locales/en';
|
|
||||||
import { esES } from './locales/es-ES';
|
|
||||||
import { fa } from './locales/fa';
|
|
||||||
import { fr } from './locales/fr';
|
|
||||||
import { ja } from './locales/ja';
|
|
||||||
import { ptBR } from './locales/pt-BR';
|
|
||||||
import { ru } from './locales/ru';
|
|
||||||
import { zhCN } from './locales/zh-CN';
|
|
||||||
import { zhTW } from './locales/zh-TW';
|
|
||||||
|
|
||||||
const LOCALE_DICTS = {
|
|
||||||
de,
|
|
||||||
en,
|
|
||||||
esES,
|
|
||||||
fa,
|
|
||||||
fr,
|
|
||||||
ja,
|
|
||||||
ptBR,
|
|
||||||
ru,
|
|
||||||
zhCN,
|
|
||||||
zhTW,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Design Files dropzone copy', () => {
|
|
||||||
it('does not advertise unsupported Figma link drops', () => {
|
|
||||||
for (const [locale, dict] of Object.entries(LOCALE_DICTS)) {
|
|
||||||
expect(dict['designFiles.dropDesc'], locale).not.toMatch(/figma/i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
createHtmlArtifactManifest,
|
createHtmlArtifactManifest,
|
||||||
inferLegacyManifest,
|
inferLegacyManifest,
|
||||||
parseArtifactManifest,
|
parseArtifactManifest,
|
||||||
} from './manifest';
|
} from '../../src/artifacts/manifest';
|
||||||
|
|
||||||
describe('parseArtifactManifest', () => {
|
describe('parseArtifactManifest', () => {
|
||||||
it('returns null for malformed json', () => {
|
it('returns null for malformed json', () => {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { renderMarkdownToSafeHtml } from './markdown';
|
import { renderMarkdownToSafeHtml } from '../../src/artifacts/markdown';
|
||||||
|
|
||||||
describe('renderMarkdownToSafeHtml', () => {
|
describe('renderMarkdownToSafeHtml', () => {
|
||||||
it('renders common markdown blocks', () => {
|
it('renders common markdown blocks', () => {
|
||||||
|
|
@ -8,9 +8,9 @@ import {
|
||||||
RendererRegistry,
|
RendererRegistry,
|
||||||
SvgRenderer,
|
SvgRenderer,
|
||||||
artifactRendererRegistry,
|
artifactRendererRegistry,
|
||||||
} from './renderer-registry';
|
} from '../../src/artifacts/renderer-registry';
|
||||||
import { renderMarkdownToSafeHtml } from './markdown';
|
import { renderMarkdownToSafeHtml } from '../../src/artifacts/markdown';
|
||||||
import type { ProjectFile } from '../types';
|
import type { ProjectFile } from '../../src/types';
|
||||||
|
|
||||||
function baseFile(overrides: Partial<ProjectFile> & Pick<ProjectFile, 'name'>): ProjectFile {
|
function baseFile(overrides: Partial<ProjectFile> & Pick<ProjectFile, 'name'>): ProjectFile {
|
||||||
return {
|
return {
|
||||||
|
|
@ -8,8 +8,8 @@ import {
|
||||||
overlayBoundsFromSnapshot,
|
overlayBoundsFromSnapshot,
|
||||||
removeAttachedComment,
|
removeAttachedComment,
|
||||||
targetFromSnapshot,
|
targetFromSnapshot,
|
||||||
} from './comments';
|
} from '../src/comments';
|
||||||
import type { ChatMessage, PreviewComment } from './types';
|
import type { ChatMessage, PreviewComment } from '../src/types';
|
||||||
|
|
||||||
describe('preview comment attachment helpers', () => {
|
describe('preview comment attachment helpers', () => {
|
||||||
it('builds compact target context from an iframe snapshot', () => {
|
it('builds compact target context from an iframe snapshot', () => {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { assistantRoleLabel } from './AssistantMessage';
|
import { assistantRoleLabel } from '../../src/components/AssistantMessage';
|
||||||
import type { ChatMessage } from '../types';
|
import type { ChatMessage } from '../../src/types';
|
||||||
|
|
||||||
const t = () => 'Assistant';
|
const t = () => 'Assistant';
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { STATUS_LABEL_KEYS, STATUS_ORDER } from './DesignsTab';
|
import { STATUS_LABEL_KEYS, STATUS_ORDER } from '../../src/components/DesignsTab';
|
||||||
|
|
||||||
describe('DesignsTab status metadata', () => {
|
describe('DesignsTab status metadata', () => {
|
||||||
it('places awaiting_input between running and succeeded', () => {
|
it('places awaiting_input between running and succeeded', () => {
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { FileViewer, SvgViewer } from './FileViewer';
|
import { FileViewer, SvgViewer } from '../../src/components/FileViewer';
|
||||||
import type { ProjectFile } from '../types';
|
import type { ProjectFile } from '../../src/types';
|
||||||
|
|
||||||
function baseFile(overrides: Partial<ProjectFile>): ProjectFile {
|
function baseFile(overrides: Partial<ProjectFile>): ProjectFile {
|
||||||
return {
|
return {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { FileWorkspace } from './FileWorkspace';
|
import { FileWorkspace } from '../../src/components/FileWorkspace';
|
||||||
|
|
||||||
describe('FileWorkspace upload input', () => {
|
describe('FileWorkspace upload input', () => {
|
||||||
it('keeps the Design Files picker aligned with drag-and-drop file support', () => {
|
it('keeps the Design Files picker aligned with drag-and-drop file support', () => {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { PreviewModal } from './PreviewModal';
|
import { PreviewModal } from '../../src/components/PreviewModal';
|
||||||
|
|
||||||
describe('PreviewModal sandbox isolation', () => {
|
describe('PreviewModal sandbox isolation', () => {
|
||||||
it('renders generated previews without same-origin sandbox access', () => {
|
it('renders generated previews without same-origin sandbox access', () => {
|
||||||
|
|
@ -3,8 +3,8 @@ import {
|
||||||
isValidApiBaseUrl,
|
isValidApiBaseUrl,
|
||||||
switchApiProtocolConfig,
|
switchApiProtocolConfig,
|
||||||
updateCurrentApiProtocolConfig,
|
updateCurrentApiProtocolConfig,
|
||||||
} from './SettingsDialog';
|
} from '../../src/components/SettingsDialog';
|
||||||
import type { AppConfig } from '../types';
|
import type { AppConfig } from '../../src/types';
|
||||||
|
|
||||||
const baseConfig: AppConfig = {
|
const baseConfig: AppConfig = {
|
||||||
mode: 'api',
|
mode: 'api',
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { decideAutoOpenAfterWrite } from './auto-open-file';
|
import { decideAutoOpenAfterWrite } from '../../src/components/auto-open-file';
|
||||||
|
|
||||||
describe('decideAutoOpenAfterWrite', () => {
|
describe('decideAutoOpenAfterWrite', () => {
|
||||||
it('returns shouldOpen=false when filePath is empty', () => {
|
it('returns shouldOpen=false when filePath is empty', () => {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { parseForceInline, shouldUrlLoadHtmlPreview } from './file-viewer-render-mode';
|
import { parseForceInline, shouldUrlLoadHtmlPreview } from '../../src/components/file-viewer-render-mode';
|
||||||
|
|
||||||
describe('shouldUrlLoadHtmlPreview', () => {
|
describe('shouldUrlLoadHtmlPreview', () => {
|
||||||
const base = { mode: 'preview' as const, isDeck: false, commentMode: false, forceInline: false };
|
const base = { mode: 'preview' as const, isDeck: false, commentMode: false, forceInline: false };
|
||||||
|
|
@ -3,7 +3,7 @@ import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { parseFrontmatter } from '../../../daemon/src/frontmatter';
|
import { parseFrontmatter } from '../../../daemon/src/frontmatter';
|
||||||
import { LOCALIZED_CONTENT_IDS } from './content';
|
import { LOCALIZED_CONTENT_IDS } from '../../src/i18n/content';
|
||||||
|
|
||||||
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url));
|
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url));
|
||||||
|
|
||||||
33
apps/web/tests/i18n/design-files-agent-copy.test.ts
Normal file
33
apps/web/tests/i18n/design-files-agent-copy.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { de } from '../../src/i18n/locales/de';
|
||||||
|
import { en } from '../../src/i18n/locales/en';
|
||||||
|
import { esES } from '../../src/i18n/locales/es-ES';
|
||||||
|
import { fa } from '../../src/i18n/locales/fa';
|
||||||
|
import { fr } from '../../src/i18n/locales/fr';
|
||||||
|
import { ja } from '../../src/i18n/locales/ja';
|
||||||
|
import { ptBR } from '../../src/i18n/locales/pt-BR';
|
||||||
|
import { ru } from '../../src/i18n/locales/ru';
|
||||||
|
import { zhCN } from '../../src/i18n/locales/zh-CN';
|
||||||
|
import { zhTW } from '../../src/i18n/locales/zh-TW';
|
||||||
|
|
||||||
|
const LOCALE_DICTS = {
|
||||||
|
de,
|
||||||
|
en,
|
||||||
|
esES,
|
||||||
|
fa,
|
||||||
|
fr,
|
||||||
|
ja,
|
||||||
|
ptBR,
|
||||||
|
ru,
|
||||||
|
zhCN,
|
||||||
|
zhTW,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Design Files agent copy', () => {
|
||||||
|
it('uses neutral agent wording in shared helper text', () => {
|
||||||
|
for (const [locale, dict] of Object.entries(LOCALE_DICTS)) {
|
||||||
|
expect(dict['designFiles.dropDesc'], locale).not.toMatch(/claude/i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
33
apps/web/tests/i18n/design-files-dropzone-copy.test.ts
Normal file
33
apps/web/tests/i18n/design-files-dropzone-copy.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { de } from '../../src/i18n/locales/de';
|
||||||
|
import { en } from '../../src/i18n/locales/en';
|
||||||
|
import { esES } from '../../src/i18n/locales/es-ES';
|
||||||
|
import { fa } from '../../src/i18n/locales/fa';
|
||||||
|
import { fr } from '../../src/i18n/locales/fr';
|
||||||
|
import { ja } from '../../src/i18n/locales/ja';
|
||||||
|
import { ptBR } from '../../src/i18n/locales/pt-BR';
|
||||||
|
import { ru } from '../../src/i18n/locales/ru';
|
||||||
|
import { zhCN } from '../../src/i18n/locales/zh-CN';
|
||||||
|
import { zhTW } from '../../src/i18n/locales/zh-TW';
|
||||||
|
|
||||||
|
const LOCALE_DICTS = {
|
||||||
|
de,
|
||||||
|
en,
|
||||||
|
esES,
|
||||||
|
fa,
|
||||||
|
fr,
|
||||||
|
ja,
|
||||||
|
ptBR,
|
||||||
|
ru,
|
||||||
|
zhCN,
|
||||||
|
zhTW,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Design Files dropzone copy', () => {
|
||||||
|
it('does not advertise unsupported Figma link drops', () => {
|
||||||
|
for (const [locale, dict] of Object.entries(LOCALE_DICTS)) {
|
||||||
|
expect(dict['designFiles.dropDesc'], locale).not.toMatch(/figma/i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { en } from './locales/en';
|
import { en } from '../../src/i18n/locales/en';
|
||||||
import { LOCALES, LOCALE_LABEL, type Dict, type Locale } from './types';
|
import { LOCALES, LOCALE_LABEL, type Dict, type Locale } from '../../src/i18n/types';
|
||||||
|
|
||||||
const EXPECTED_LOCALES = ['en', 'de', 'zh-CN', 'zh-TW', 'pt-BR', 'es-ES', 'ru', 'fa', 'ar', 'ja', 'ko', 'pl', 'hu', 'fr', 'uk'];
|
const EXPECTED_LOCALES = ['en', 'de', 'zh-CN', 'zh-TW', 'pt-BR', 'es-ES', 'ru', 'fa', 'ar', 'ja', 'ko', 'pl', 'hu', 'fr', 'uk'];
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ function placeholders(value: string): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDict(locale: Locale): Promise<Dict> {
|
async function loadDict(locale: Locale): Promise<Dict> {
|
||||||
const module = await import(`./locales/${locale}.ts`);
|
const module = await import(`../../src/i18n/locales/${locale}.ts`);
|
||||||
const dict = Object.values(module).find((value): value is Dict => {
|
const dict = Object.values(module).find((value): value is Dict => {
|
||||||
return Boolean(value) && typeof value === 'object';
|
return Boolean(value) && typeof value === 'object';
|
||||||
});
|
});
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { isOpenAICompatible } from './openai-compatible';
|
import { isOpenAICompatible } from '../../src/providers/openai-compatible';
|
||||||
|
|
||||||
describe('isOpenAICompatible', () => {
|
describe('isOpenAICompatible', () => {
|
||||||
it('preserves explicit OpenAI model routing when the URL contains anthropic', () => {
|
it('preserves explicit OpenAI model routing when the URL contains anthropic', () => {
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
createProjectEventsConnection,
|
createProjectEventsConnection,
|
||||||
projectEventsUrl,
|
projectEventsUrl,
|
||||||
type ProjectFileChangeEvent,
|
type ProjectFileChangeEvent,
|
||||||
} from './project-events';
|
} from '../../src/providers/project-events';
|
||||||
|
|
||||||
type Listener = (evt: unknown) => void;
|
type Listener = (evt: unknown) => void;
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { fetchAppVersionInfo, fetchProjectFileText, uploadProjectFiles } from './registry';
|
import { fetchAppVersionInfo, fetchProjectFileText, uploadProjectFiles } from '../../src/providers/registry';
|
||||||
|
|
||||||
describe('fetchAppVersionInfo', () => {
|
describe('fetchAppVersionInfo', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { reattachDaemonRun, streamViaDaemon } from './daemon';
|
import { reattachDaemonRun, streamViaDaemon } from '../../src/providers/daemon';
|
||||||
import { streamMessageOpenAI } from './openai-compatible';
|
import { streamMessageOpenAI } from '../../src/providers/openai-compatible';
|
||||||
import { parseSseFrame } from './sse';
|
import { parseSseFrame } from '../../src/providers/sse';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
exportAsMd,
|
exportAsMd,
|
||||||
exportAsPdf,
|
exportAsPdf,
|
||||||
openSandboxedPreviewInNewTab,
|
openSandboxedPreviewInNewTab,
|
||||||
} from './exports';
|
} from '../../src/runtime/exports';
|
||||||
|
|
||||||
function mockResponse(headers: Record<string, string>): Response {
|
function mockResponse(headers: Record<string, string>): Response {
|
||||||
return { headers: new Headers(headers) } as Response;
|
return { headers: new Headers(headers) } as Response;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { buildReactComponentSrcdoc, prepareReactComponentSource } from './react-component';
|
import { buildReactComponentSrcdoc, prepareReactComponentSource } from '../../src/runtime/react-component';
|
||||||
|
|
||||||
describe('prepareReactComponentSource', () => {
|
describe('prepareReactComponentSource', () => {
|
||||||
it('adapts a default function export for iframe rendering', () => {
|
it('adapts a default function export for iframe rendering', () => {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { buildSrcdoc } from './srcdoc';
|
import { buildSrcdoc } from '../../src/runtime/srcdoc';
|
||||||
|
|
||||||
const deckHtml = `<!doctype html>
|
const deckHtml = `<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
|
|
@ -2,16 +2,16 @@ import { useState } from 'react';
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { ToolCard } from '../components/ToolCard';
|
import { ToolCard } from '../../src/components/ToolCard';
|
||||||
import {
|
import {
|
||||||
clearToolRenderers,
|
clearToolRenderers,
|
||||||
deriveToolStatus,
|
deriveToolStatus,
|
||||||
getToolRenderer,
|
getToolRenderer,
|
||||||
registerToolRenderer,
|
registerToolRenderer,
|
||||||
toRenderProps,
|
toRenderProps,
|
||||||
} from './tool-renderers';
|
} from '../../src/runtime/tool-renderers';
|
||||||
import type { ToolRenderProps } from './tool-renderers';
|
import type { ToolRenderProps } from '../../src/runtime/tool-renderers';
|
||||||
import type { AgentEvent } from '../types';
|
import type { AgentEvent } from '../../src/types';
|
||||||
|
|
||||||
type ToolUse = Extract<AgentEvent, { kind: 'tool_use' }>;
|
type ToolUse = Extract<AgentEvent, { kind: 'tool_use' }>;
|
||||||
type ToolResult = Extract<AgentEvent, { kind: 'tool_result' }>;
|
type ToolResult = Extract<AgentEvent, { kind: 'tool_result' }>;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { DEFAULT_CONFIG, loadConfig } from './config';
|
import { DEFAULT_CONFIG, loadConfig } from '../../src/state/config';
|
||||||
import type { AppConfig } from '../types';
|
import type { AppConfig } from '../../src/types';
|
||||||
|
|
||||||
const store = new Map<string, string>();
|
const store = new Map<string, string>();
|
||||||
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import litellmData from './litellm-models.json';
|
import litellmData from '../../src/state/litellm-models.json';
|
||||||
import {
|
import {
|
||||||
effectiveMaxTokens,
|
effectiveMaxTokens,
|
||||||
FALLBACK_MAX_TOKENS,
|
FALLBACK_MAX_TOKENS,
|
||||||
MAX_MAX_TOKENS,
|
MAX_MAX_TOKENS,
|
||||||
MIN_MAX_TOKENS,
|
MIN_MAX_TOKENS,
|
||||||
modelMaxTokensDefault,
|
modelMaxTokensDefault,
|
||||||
} from './maxTokens';
|
} from '../../src/state/maxTokens';
|
||||||
|
|
||||||
describe('modelMaxTokensDefault', () => {
|
describe('modelMaxTokensDefault', () => {
|
||||||
it('falls through to LiteLLM data for canonical Anthropic ids', () => {
|
it('falls through to LiteLLM data for canonical Anthropic ids', () => {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { apiProtocolLabel, apiProtocolModelLabel } from './apiProtocol';
|
import { apiProtocolLabel, apiProtocolModelLabel } from '../../src/utils/apiProtocol';
|
||||||
import { agentModelDisplayName } from './agentLabels';
|
import { agentModelDisplayName } from '../../src/utils/agentLabels';
|
||||||
|
|
||||||
describe('api protocol labels', () => {
|
describe('api protocol labels', () => {
|
||||||
it('labels the selected API protocol instead of assuming Anthropic', () => {
|
it('labels the selected API protocol instead of assuming Anthropic', () => {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import type { ChatMessage } from '../types';
|
import type { ChatMessage } from '../../src/types';
|
||||||
import { messageTime } from './chatTime';
|
import { messageTime } from '../../src/utils/chatTime';
|
||||||
|
|
||||||
describe('messageTime', () => {
|
describe('messageTime', () => {
|
||||||
it('uses assistant startedAt before persisted createdAt', () => {
|
it('uses assistant startedAt before persisted createdAt', () => {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { showCompletionNotification } from './notifications';
|
import { showCompletionNotification } from '../../src/utils/notifications';
|
||||||
|
|
||||||
type NotificationOptionsWithRenotify = NotificationOptions & { renotify?: boolean };
|
type NotificationOptionsWithRenotify = NotificationOptions & { renotify?: boolean };
|
||||||
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"app/**/*",
|
"app/**/*",
|
||||||
"sidecar/**/*",
|
"sidecar/**/*",
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
|
"tests/**/*",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
".next/dev/types/**/*.ts",
|
".next/dev/types/**/*.ts",
|
||||||
"out/types/**/*.ts",
|
"out/types/**/*.ts",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ import { defineConfig } from 'vitest/config';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
include: ['src/**/*.test.{ts,tsx,js,mjs,cjs}'],
|
include: ['tests/**/*.test.{ts,tsx,js,mjs,cjs}'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,7 @@ Evite priorizar:
|
||||||
## Como executar
|
## Como executar
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run test:ui
|
pnpm -C e2e test:ui
|
||||||
```
|
|
||||||
|
|
||||||
Ou direto dentro do pacote de teste:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm --filter @open-design/e2e test:ui
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Após a execução são gerados automaticamente:
|
Após a execução são gerados automaticamente:
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,7 @@
|
||||||
## 运行方式
|
## 运行方式
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run test:ui
|
pnpm -C e2e test:ui
|
||||||
```
|
|
||||||
|
|
||||||
也可以直接在独立测试包内运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm --filter @open-design/e2e test:ui
|
|
||||||
```
|
```
|
||||||
|
|
||||||
运行完成后会自动生成:
|
运行完成后会自动生成:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run -c vitest.config.ts",
|
"test": "vitest run -c vitest.config.ts",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p ../scripts/tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"test:ui:clean": "node --experimental-strip-types scripts/reset-artifacts.ts",
|
"test:ui:clean": "node --experimental-strip-types scripts/reset-artifacts.ts",
|
||||||
"test:ui": "corepack pnpm run test:ui:clean && playwright test -c playwright.config.ts",
|
"test:ui": "corepack pnpm run test:ui:clean && playwright test -c playwright.config.ts",
|
||||||
"test:ui:headed": "corepack pnpm run test:ui:clean && playwright test -c playwright.config.ts --headed",
|
"test:ui:headed": "corepack pnpm run test:ui:clean && playwright test -c playwright.config.ts --headed",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Este diretório guarda os resultados de execução e relatórios legíveis dos t
|
||||||
- `junit.xml`: resultado em JUnit, prático para integrar com CI
|
- `junit.xml`: resultado em JUnit, prático para integrar com CI
|
||||||
- `test-results/`: anexos brutos dos casos com falha (screenshots, traces, error-context)
|
- `test-results/`: anexos brutos dos casos com falha (screenshots, traces, error-context)
|
||||||
|
|
||||||
Antes de cada execução de `pnpm run test:ui` (ou `pnpm --filter @open-design/e2e test:ui`), o sistema limpa automaticamente:
|
Antes de cada execução de `pnpm -C e2e test:ui`, o sistema limpa automaticamente:
|
||||||
|
|
||||||
- `e2e/.od-data/`
|
- `e2e/.od-data/`
|
||||||
- `e2e/reports/test-results/`
|
- `e2e/reports/test-results/`
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
- `junit.xml`:JUnit 格式结果,方便接 CI
|
- `junit.xml`:JUnit 格式结果,方便接 CI
|
||||||
- `test-results/`:失败用例的截图、trace、error-context 等原始附件
|
- `test-results/`:失败用例的截图、trace、error-context 等原始附件
|
||||||
|
|
||||||
每次执行 `pnpm run test:ui`(或 `pnpm --filter @open-design/e2e test:ui`)前,系统会先自动清理旧的:
|
每次执行 `pnpm -C e2e test:ui` 前,系统会先自动清理旧的:
|
||||||
|
|
||||||
- `e2e/.od-data/`
|
- `e2e/.od-data/`
|
||||||
- `e2e/reports/test-results/`
|
- `e2e/reports/test-results/`
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -13,21 +13,18 @@
|
||||||
"postinstall": "node ./scripts/postinstall.mjs",
|
"postinstall": "node ./scripts/postinstall.mjs",
|
||||||
"tools-dev": "pnpm exec tools-dev",
|
"tools-dev": "pnpm exec tools-dev",
|
||||||
"tools-pack": "pnpm exec tools-pack",
|
"tools-pack": "pnpm exec tools-pack",
|
||||||
"build": "pnpm --filter @open-design/web build",
|
"guard": "tsx ./scripts/guard.ts",
|
||||||
"check:residual-js": "node --experimental-strip-types scripts/check-residual-js.ts",
|
|
||||||
"sync:community-pets": "node --experimental-strip-types scripts/sync-community-pets.ts",
|
"sync:community-pets": "node --experimental-strip-types scripts/sync-community-pets.ts",
|
||||||
"bake:community-pets": "node --experimental-strip-types scripts/bake-community-pets.ts",
|
"bake:community-pets": "node --experimental-strip-types scripts/bake-community-pets.ts",
|
||||||
"seed:test-projects": "node --experimental-strip-types scripts/seed-test-projects.ts",
|
"seed:test-projects": "node --experimental-strip-types scripts/seed-test-projects.ts",
|
||||||
"test:e2e:live": "pnpm --filter @open-design/e2e test:e2e:live",
|
"typecheck": "pnpm -r --workspace-concurrency=1 --if-present run typecheck && tsc -p scripts/tsconfig.json --noEmit"
|
||||||
"test": "pnpm -r --workspace-concurrency=1 --if-present run test",
|
|
||||||
"test:ui:clean": "pnpm --filter @open-design/e2e test:ui:clean",
|
|
||||||
"test:ui": "pnpm --filter @open-design/e2e test:ui",
|
|
||||||
"test:ui:headed": "pnpm --filter @open-design/e2e test:ui:headed",
|
|
||||||
"typecheck": "pnpm -r --workspace-concurrency=1 --if-present run typecheck && pnpm --filter @open-design/daemon build && pnpm check:residual-js"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@open-design/tools-dev": "workspace:0.3.0",
|
"@open-design/tools-dev": "workspace:*",
|
||||||
"@open-design/tools-pack": "workspace:0.3.0"
|
"@open-design/tools-pack": "workspace:*",
|
||||||
|
"@types/node": "^20.17.10",
|
||||||
|
"tsx": "4.21.0",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "~24",
|
"node": "~24",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Follow the root `AGENTS.md` first. This file only records module-level boundarie
|
||||||
|
|
||||||
## Boundary checklist
|
## Boundary checklist
|
||||||
|
|
||||||
|
- Package tests live in each package's `tests/` directory, sibling to `src/`; keep `src/` source-only and do not add new `*.test.ts` or `*.test.tsx` files under `src/`.
|
||||||
- Do not move runtime validation/schema enforcement into `contracts` prematurely; current contracts define the typed target shape only.
|
- Do not move runtime validation/schema enforcement into `contracts` prematurely; current contracts define the typed target shape only.
|
||||||
- Do not let app packages depend directly on sidecar control-plane details.
|
- Do not let app packages depend directly on sidecar control-plane details.
|
||||||
- Do not hard-code Open Design app/source/mode constants in `sidecar` or `platform`.
|
- Do not hard-code Open Design app/source/mode constants in `sidecar` or `platform`.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
defaultCritiqueConfig,
|
defaultCritiqueConfig,
|
||||||
isPanelEvent,
|
isPanelEvent,
|
||||||
type PanelEvent,
|
type PanelEvent,
|
||||||
} from './critique';
|
} from '../src/critique';
|
||||||
|
|
||||||
describe('CritiqueConfig', () => {
|
describe('CritiqueConfig', () => {
|
||||||
it('defaults validate against the schema', () => {
|
it('defaults validate against the schema', () => {
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { PanelEvent } from '../critique';
|
import type { PanelEvent } from '../../src/critique';
|
||||||
import {
|
import {
|
||||||
panelEventToSse,
|
panelEventToSse,
|
||||||
type CritiqueSseEvent,
|
type CritiqueSseEvent,
|
||||||
CRITIQUE_SSE_EVENT_NAMES,
|
CRITIQUE_SSE_EVENT_NAMES,
|
||||||
} from './critique';
|
} from '../../src/sse/critique';
|
||||||
|
|
||||||
describe('CritiqueSseEvent', () => {
|
describe('CritiqueSseEvent', () => {
|
||||||
it('panelEventToSse maps PanelEvent.type "run_started" to event "critique.run_started"', () => {
|
it('panelEventToSse maps PanelEvent.type "run_started" to event "critique.run_started"', () => {
|
||||||
9
packages/contracts/tsconfig.tests.json
Normal file
9
packages/contracts/tsconfig.tests.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
matchesStampedProcess,
|
matchesStampedProcess,
|
||||||
readProcessStampFromCommand,
|
readProcessStampFromCommand,
|
||||||
type ProcessStampContract,
|
type ProcessStampContract,
|
||||||
} from "./index.js";
|
} from "../src/index.js";
|
||||||
|
|
||||||
type FakeStamp = {
|
type FakeStamp = {
|
||||||
app: "api" | "ui";
|
app: "api" | "ui";
|
||||||
9
packages/platform/tsconfig.tests.json
Normal file
9
packages/platform/tsconfig.tests.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
STAMP_MODE_FLAG,
|
STAMP_MODE_FLAG,
|
||||||
STAMP_NAMESPACE_FLAG,
|
STAMP_NAMESPACE_FLAG,
|
||||||
STAMP_SOURCE_FLAG,
|
STAMP_SOURCE_FLAG,
|
||||||
} from "./index.js";
|
} from "../src/index.js";
|
||||||
|
|
||||||
const validStamp = {
|
const validStamp = {
|
||||||
app: APP_KEYS.WEB,
|
app: APP_KEYS.WEB,
|
||||||
9
packages/sidecar-proto/tsconfig.tests.json
Normal file
9
packages/sidecar-proto/tsconfig.tests.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
resolveSourceRuntimeRoot,
|
resolveSourceRuntimeRoot,
|
||||||
type SidecarContractDescriptor,
|
type SidecarContractDescriptor,
|
||||||
type SidecarStampShape,
|
type SidecarStampShape,
|
||||||
} from "./index.js";
|
} from "../src/index.js";
|
||||||
|
|
||||||
type FakeStamp = SidecarStampShape & {
|
type FakeStamp = SidecarStampShape & {
|
||||||
app: "api" | "ui";
|
app: "api" | "ui";
|
||||||
9
packages/sidecar/tsconfig.tests.json
Normal file
9
packages/sidecar/tsconfig.tests.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
|
|
@ -9,11 +9,20 @@ importers:
|
||||||
.:
|
.:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@open-design/tools-dev':
|
'@open-design/tools-dev':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:tools/dev
|
version: link:tools/dev
|
||||||
'@open-design/tools-pack':
|
'@open-design/tools-pack':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:tools/pack
|
version: link:tools/pack
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20.17.10
|
||||||
|
version: 20.19.39
|
||||||
|
tsx:
|
||||||
|
specifier: 4.21.0
|
||||||
|
version: 4.21.0
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.6.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
apps/daemon:
|
apps/daemon:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -21,16 +30,16 @@ importers:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.29.0(zod@4.4.2)
|
version: 1.29.0(zod@4.4.2)
|
||||||
'@open-design/contracts':
|
'@open-design/contracts':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/contracts
|
version: link:../../packages/contracts
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
better-sqlite3:
|
better-sqlite3:
|
||||||
specifier: ^12.9.0
|
specifier: ^12.9.0
|
||||||
|
|
@ -70,13 +79,13 @@ importers:
|
||||||
apps/desktop:
|
apps/desktop:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
|
|
@ -123,22 +132,22 @@ importers:
|
||||||
apps/packaged:
|
apps/packaged:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@open-design/daemon':
|
'@open-design/daemon':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../daemon
|
version: link:../daemon
|
||||||
'@open-design/desktop':
|
'@open-design/desktop':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../desktop
|
version: link:../desktop
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
'@open-design/web':
|
'@open-design/web':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../web
|
version: link:../web
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
|
|
@ -160,16 +169,16 @@ importers:
|
||||||
specifier: ^0.32.1
|
specifier: ^0.32.1
|
||||||
version: 0.32.1
|
version: 0.32.1
|
||||||
'@open-design/contracts':
|
'@open-design/contracts':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/contracts
|
version: link:../../packages/contracts
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
next:
|
next:
|
||||||
specifier: ^16.2.4
|
specifier: ^16.2.4
|
||||||
|
|
@ -288,13 +297,13 @@ importers:
|
||||||
tools/dev:
|
tools/dev:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
cac:
|
cac:
|
||||||
specifier: 6.7.14
|
specifier: 6.7.14
|
||||||
|
|
@ -319,13 +328,13 @@ importers:
|
||||||
specifier: 3.1.0
|
specifier: 3.1.0
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
'@open-design/platform':
|
'@open-design/platform':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/platform
|
version: link:../../packages/platform
|
||||||
'@open-design/sidecar':
|
'@open-design/sidecar':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar
|
version: link:../../packages/sidecar
|
||||||
'@open-design/sidecar-proto':
|
'@open-design/sidecar-proto':
|
||||||
specifier: workspace:0.3.0
|
specifier: workspace:*
|
||||||
version: link:../../packages/sidecar-proto
|
version: link:../../packages/sidecar-proto
|
||||||
cac:
|
cac:
|
||||||
specifier: 6.7.14
|
specifier: 6.7.14
|
||||||
|
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
import { readdir } from "node:fs/promises";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
||||||
const residualExtensions = new Set([".js", ".mjs", ".cjs"]);
|
|
||||||
|
|
||||||
const skippedDirectories = new Set([
|
|
||||||
".agents",
|
|
||||||
".astro",
|
|
||||||
".claude",
|
|
||||||
".claude-sessions",
|
|
||||||
".codex",
|
|
||||||
".cursor",
|
|
||||||
".git",
|
|
||||||
".od",
|
|
||||||
".od-e2e",
|
|
||||||
".opencode",
|
|
||||||
".task",
|
|
||||||
".tmp",
|
|
||||||
".vite",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"out",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const allowedExactPaths = new Set([
|
|
||||||
"packages/platform/esbuild.config.mjs",
|
|
||||||
"packages/sidecar/esbuild.config.mjs",
|
|
||||||
"packages/sidecar-proto/esbuild.config.mjs",
|
|
||||||
// Maintainer utility scripts ported from the media branch. They are
|
|
||||||
// executed directly by Node and are not loaded by the app runtime.
|
|
||||||
"scripts/import-prompt-templates.mjs",
|
|
||||||
"scripts/postinstall.mjs",
|
|
||||||
"apps/packaged/esbuild.config.mjs",
|
|
||||||
// Browser service workers must be served as JavaScript files.
|
|
||||||
"apps/web/public/od-notifications-sw.js",
|
|
||||||
"scripts/bake-html-ppt-examples.mjs",
|
|
||||||
"scripts/scaffold-html-ppt-skills.mjs",
|
|
||||||
"scripts/sync-hyperframes-skill.mjs",
|
|
||||||
"scripts/verify-media-models.mjs",
|
|
||||||
"tools/dev/bin/tools-dev.mjs",
|
|
||||||
"tools/dev/esbuild.config.mjs",
|
|
||||||
"tools/pack/bin/tools-pack.mjs",
|
|
||||||
"tools/pack/esbuild.config.mjs",
|
|
||||||
"tools/pack/resources/mac/notarize.cjs",
|
|
||||||
// electron-builder hook path; CJS compatibility entry used by tools-pack mac builds.
|
|
||||||
"tools/pack/resources/mac/web-standalone-after-pack.cjs",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const allowedPathPrefixes = [
|
|
||||||
"apps/daemon/dist/",
|
|
||||||
"apps/web/.next/",
|
|
||||||
"apps/web/out/",
|
|
||||||
"generated/",
|
|
||||||
"e2e/playwright-report/",
|
|
||||||
"e2e/reports/html/",
|
|
||||||
"e2e/reports/playwright-html-report/",
|
|
||||||
"e2e/reports/test-results/",
|
|
||||||
// Vendored upstream HyperFrames skill helper scripts.
|
|
||||||
"skills/hyperframes/scripts/",
|
|
||||||
// Vendored upstream html-ppt skill runtime assets (lewislulu/html-ppt-skill).
|
|
||||||
"skills/html-ppt/assets/",
|
|
||||||
"test-results/",
|
|
||||||
"vendor/",
|
|
||||||
];
|
|
||||||
|
|
||||||
function toRepositoryPath(filePath: string): string {
|
|
||||||
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAllowedOutputPath(repositoryPath: string): boolean {
|
|
||||||
if (allowedExactPaths.has(repositoryPath)) return true;
|
|
||||||
return allowedPathPrefixes.some((prefix) => repositoryPath.startsWith(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSkippedDirectoryName(directoryName: string): boolean {
|
|
||||||
return skippedDirectories.has(directoryName) || directoryName === ".next" || directoryName.startsWith(".next-");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function collectResidualJavaScript(directory: string): Promise<string[]> {
|
|
||||||
const entries = await readdir(directory, { withFileTypes: true });
|
|
||||||
const residualFiles: string[] = [];
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = path.join(directory, entry.name);
|
|
||||||
const repositoryPath = toRepositoryPath(fullPath);
|
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
if (isSkippedDirectoryName(entry.name) || isAllowedOutputPath(`${repositoryPath}/`)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
residualFiles.push(...(await collectResidualJavaScript(fullPath)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry.isFile() || !residualExtensions.has(path.extname(entry.name))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedOutputPath(repositoryPath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
residualFiles.push(repositoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return residualFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
const residualFiles = await collectResidualJavaScript(repoRoot);
|
|
||||||
|
|
||||||
if (residualFiles.length > 0) {
|
|
||||||
console.error("Residual project-owned JavaScript files found:");
|
|
||||||
for (const filePath of residualFiles) {
|
|
||||||
console.error(`- ${filePath}`);
|
|
||||||
}
|
|
||||||
console.error("Convert these files to TypeScript or add a documented generated/vendor/output allowlist entry.");
|
|
||||||
process.exitCode = 1;
|
|
||||||
} else {
|
|
||||||
console.log("Residual JavaScript check passed: project-owned code is TypeScript-only.");
|
|
||||||
}
|
|
||||||
223
scripts/guard.ts
Normal file
223
scripts/guard.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||||
|
|
||||||
|
type GuardCheck = {
|
||||||
|
name: string;
|
||||||
|
run: () => Promise<boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function toRepositoryPath(filePath: string): string {
|
||||||
|
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const residualExtensions = new Set([".js", ".mjs", ".cjs"]);
|
||||||
|
|
||||||
|
const residualSkippedDirectories = new Set([
|
||||||
|
".agents",
|
||||||
|
".astro",
|
||||||
|
".claude",
|
||||||
|
".claude-sessions",
|
||||||
|
".codex",
|
||||||
|
".cursor",
|
||||||
|
".git",
|
||||||
|
".od",
|
||||||
|
".od-e2e",
|
||||||
|
".opencode",
|
||||||
|
".task",
|
||||||
|
".tmp",
|
||||||
|
".vite",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"out",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const residualAllowedExactPaths = new Set([
|
||||||
|
"packages/platform/esbuild.config.mjs",
|
||||||
|
"packages/sidecar/esbuild.config.mjs",
|
||||||
|
"packages/sidecar-proto/esbuild.config.mjs",
|
||||||
|
// Maintainer utility scripts ported from the media branch. They are
|
||||||
|
// executed directly by Node and are not loaded by the app runtime.
|
||||||
|
"scripts/import-prompt-templates.mjs",
|
||||||
|
"scripts/postinstall.mjs",
|
||||||
|
"apps/packaged/esbuild.config.mjs",
|
||||||
|
// Browser service workers must be served as JavaScript files.
|
||||||
|
"apps/web/public/od-notifications-sw.js",
|
||||||
|
"scripts/bake-html-ppt-examples.mjs",
|
||||||
|
"scripts/scaffold-html-ppt-skills.mjs",
|
||||||
|
"scripts/sync-hyperframes-skill.mjs",
|
||||||
|
"scripts/verify-media-models.mjs",
|
||||||
|
"tools/dev/bin/tools-dev.mjs",
|
||||||
|
"tools/dev/esbuild.config.mjs",
|
||||||
|
"tools/pack/bin/tools-pack.mjs",
|
||||||
|
"tools/pack/esbuild.config.mjs",
|
||||||
|
"tools/pack/resources/mac/notarize.cjs",
|
||||||
|
// electron-builder hook path; CJS compatibility entry used by tools-pack mac builds.
|
||||||
|
"tools/pack/resources/mac/web-standalone-after-pack.cjs",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const residualAllowedPathPrefixes = [
|
||||||
|
"apps/daemon/dist/",
|
||||||
|
"apps/web/.next/",
|
||||||
|
"apps/web/out/",
|
||||||
|
"generated/",
|
||||||
|
"e2e/playwright-report/",
|
||||||
|
"e2e/reports/html/",
|
||||||
|
"e2e/reports/playwright-html-report/",
|
||||||
|
"e2e/reports/test-results/",
|
||||||
|
// Vendored upstream HyperFrames skill helper scripts.
|
||||||
|
"skills/hyperframes/scripts/",
|
||||||
|
// Vendored upstream html-ppt skill runtime assets (lewislulu/html-ppt-skill).
|
||||||
|
"skills/html-ppt/assets/",
|
||||||
|
"test-results/",
|
||||||
|
"vendor/",
|
||||||
|
];
|
||||||
|
|
||||||
|
function isResidualAllowedPath(repositoryPath: string): boolean {
|
||||||
|
if (residualAllowedExactPaths.has(repositoryPath)) return true;
|
||||||
|
return residualAllowedPathPrefixes.some((prefix) => repositoryPath.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResidualSkippedDirectoryName(directoryName: string): boolean {
|
||||||
|
return (
|
||||||
|
residualSkippedDirectories.has(directoryName) || directoryName === ".next" || directoryName.startsWith(".next-")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectResidualJavaScript(directory: string): Promise<string[]> {
|
||||||
|
const entries = await readdir(directory, { withFileTypes: true });
|
||||||
|
const residualFiles: string[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(directory, entry.name);
|
||||||
|
const repositoryPath = toRepositoryPath(fullPath);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (isResidualSkippedDirectoryName(entry.name) || isResidualAllowedPath(`${repositoryPath}/`)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
residualFiles.push(...(await collectResidualJavaScript(fullPath)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.isFile() || !residualExtensions.has(path.extname(entry.name))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResidualAllowedPath(repositoryPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
residualFiles.push(repositoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return residualFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkResidualJavaScript(): Promise<boolean> {
|
||||||
|
const residualFiles = await collectResidualJavaScript(repoRoot);
|
||||||
|
|
||||||
|
if (residualFiles.length > 0) {
|
||||||
|
console.error("Residual project-owned JavaScript files found:");
|
||||||
|
for (const filePath of residualFiles) {
|
||||||
|
console.error(`- ${filePath}`);
|
||||||
|
}
|
||||||
|
console.error("Convert these files to TypeScript or add a documented generated/vendor/output allowlist entry.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Residual JavaScript check passed: project-owned code is TypeScript-only.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testLayoutScopedDirectories = ["apps", "packages", "tools"];
|
||||||
|
const testLayoutSkippedDirectories = new Set([".next", "dist", "node_modules", "out"]);
|
||||||
|
|
||||||
|
function isTestFile(fileName: string): boolean {
|
||||||
|
return /\.test\.tsx?$/.test(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectedTestPath(repositoryPath: string): string {
|
||||||
|
const [scope, project, ...relativeParts] = repositoryPath.split("/");
|
||||||
|
if (!testLayoutScopedDirectories.includes(scope ?? "") || project == null || relativeParts.length === 0) {
|
||||||
|
return repositoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedRelativeParts = relativeParts[0] === "src" ? relativeParts.slice(1) : relativeParts;
|
||||||
|
return [scope, project, "tests", ...normalizedRelativeParts].join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAllowedScopedTestPath(repositoryPath: string): boolean {
|
||||||
|
const [scope, project, directory] = repositoryPath.split("/");
|
||||||
|
return testLayoutScopedDirectories.includes(scope ?? "") && project != null && directory === "tests";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectTestLayoutViolations(directory: string): Promise<string[]> {
|
||||||
|
const entries = await readdir(directory, { withFileTypes: true });
|
||||||
|
const violations: string[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(directory, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (testLayoutSkippedDirectories.has(entry.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
violations.push(...(await collectTestLayoutViolations(fullPath)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.isFile() || !isTestFile(entry.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const repositoryPath = toRepositoryPath(fullPath);
|
||||||
|
if (!isAllowedScopedTestPath(repositoryPath)) {
|
||||||
|
violations.push(repositoryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return violations;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkTestLayout(): Promise<boolean> {
|
||||||
|
const violations = (
|
||||||
|
await Promise.all(
|
||||||
|
testLayoutScopedDirectories.map((directory) => collectTestLayoutViolations(path.join(repoRoot, directory))),
|
||||||
|
)
|
||||||
|
).flat();
|
||||||
|
|
||||||
|
if (violations.length > 0) {
|
||||||
|
console.error("Test files under apps/, packages/, and tools/ must live in tests/ sibling to src/:");
|
||||||
|
for (const violation of violations) {
|
||||||
|
console.error(`- ${violation} -> ${expectedTestPath(violation)}`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Test layout check passed: apps/packages/tools tests live in sibling tests directories.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checks: GuardCheck[] = [
|
||||||
|
{ name: "residual JavaScript", run: checkResidualJavaScript },
|
||||||
|
{ name: "test layout", run: checkTestLayout },
|
||||||
|
];
|
||||||
|
|
||||||
|
const results: boolean[] = [];
|
||||||
|
for (const check of checks) {
|
||||||
|
try {
|
||||||
|
results.push(await check.run());
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Guard check failed unexpectedly: ${check.name}`);
|
||||||
|
console.error(error);
|
||||||
|
results.push(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.some((passed) => !passed)) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"typeRoots": ["../e2e/node_modules/@types"],
|
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"include": ["./**/*.ts"]
|
"include": ["./**/*.ts"]
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,9 @@ Expected: pnpm 10.33.2, no errors, all workspace packages linked.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm typecheck
|
pnpm typecheck
|
||||||
pnpm test
|
pnpm guard
|
||||||
pnpm check:residual-js
|
pnpm --filter @open-design/web test
|
||||||
|
pnpm --filter @open-design/daemon test
|
||||||
```
|
```
|
||||||
Expected: all pass on the unmodified `feat/critique-theater` branch.
|
Expected: all pass on the unmodified `feat/critique-theater` branch.
|
||||||
|
|
||||||
|
|
@ -74,12 +75,12 @@ Expected: build completes; capture bundle size baseline for the size-limit gate
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `packages/contracts/src/critique.ts`
|
- Create: `packages/contracts/src/critique.ts`
|
||||||
- Test: `packages/contracts/src/critique.test.ts`
|
- Test: `packages/contracts/tests/critique.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing test**
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// packages/contracts/src/critique.test.ts
|
// packages/contracts/tests/critique.test.ts
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
CritiqueConfigSchema,
|
CritiqueConfigSchema,
|
||||||
|
|
@ -204,7 +205,7 @@ Expected: PASS, 5/5.
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add packages/contracts/src/critique.ts packages/contracts/src/critique.test.ts
|
git add packages/contracts/src/critique.ts packages/contracts/tests/critique.test.ts
|
||||||
git commit -m "feat(contracts): add CritiqueConfig schema and defaults"
|
git commit -m "feat(contracts): add CritiqueConfig schema and defaults"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -212,11 +213,11 @@ git commit -m "feat(contracts): add CritiqueConfig schema and defaults"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `packages/contracts/src/critique.ts`
|
- Modify: `packages/contracts/src/critique.ts`
|
||||||
- Test: `packages/contracts/src/critique.test.ts`
|
- Test: `packages/contracts/tests/critique.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Add failing tests for the union exhaustiveness**
|
- [ ] **Step 1: Add failing tests for the union exhaustiveness**
|
||||||
|
|
||||||
Append to `packages/contracts/src/critique.test.ts`:
|
Append to `packages/contracts/tests/critique.test.ts`:
|
||||||
```ts
|
```ts
|
||||||
import { isPanelEvent, type PanelEvent } from './critique';
|
import { isPanelEvent, type PanelEvent } from './critique';
|
||||||
|
|
||||||
|
|
@ -316,7 +317,7 @@ Expected: PASS, all assertions.
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add packages/contracts/src/critique.ts packages/contracts/src/critique.test.ts
|
git add packages/contracts/src/critique.ts packages/contracts/tests/critique.test.ts
|
||||||
git commit -m "feat(contracts): add PanelEvent discriminated union and isPanelEvent guard"
|
git commit -m "feat(contracts): add PanelEvent discriminated union and isPanelEvent guard"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -325,7 +326,7 @@ git commit -m "feat(contracts): add PanelEvent discriminated union and isPanelEv
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `packages/contracts/src/sse.ts` (existing)
|
- Modify: `packages/contracts/src/sse.ts` (existing)
|
||||||
- Modify: `packages/contracts/src/index.ts` (re-export critique)
|
- Modify: `packages/contracts/src/index.ts` (re-export critique)
|
||||||
- Test: `packages/contracts/src/sse.test.ts`
|
- Test: `packages/contracts/tests/sse.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Inspect the existing `sse.ts` to learn its pattern**
|
- [ ] **Step 1: Inspect the existing `sse.ts` to learn its pattern**
|
||||||
|
|
||||||
|
|
@ -337,7 +338,7 @@ Expected: existing `SseEvent` discriminated union pattern. Match it exactly when
|
||||||
- [ ] **Step 2: Write the failing test**
|
- [ ] **Step 2: Write the failing test**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// packages/contracts/src/sse.test.ts (append, do not overwrite if file exists)
|
// packages/contracts/tests/sse.test.ts (append, do not overwrite if file exists)
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { isSseEvent, panelEventToSse, type SseEvent } from './sse';
|
import { isSseEvent, panelEventToSse, type SseEvent } from './sse';
|
||||||
|
|
||||||
|
|
@ -401,7 +402,7 @@ pnpm --filter @open-design/contracts test
|
||||||
Expected: all sse tests pass.
|
Expected: all sse tests pass.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add packages/contracts/src/sse.ts packages/contracts/src/sse.test.ts packages/contracts/src/index.ts
|
git add packages/contracts/src/sse.ts packages/contracts/tests/sse.test.ts packages/contracts/src/index.ts
|
||||||
git commit -m "feat(contracts): extend SseEvent with critique.* variants and panelEventToSse mapper"
|
git commit -m "feat(contracts): extend SseEvent with critique.* variants and panelEventToSse mapper"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -449,12 +450,12 @@ git commit -m "test(critique): add v1 wire-protocol golden fixtures"
|
||||||
- Create: `apps/daemon/src/critique/parser.ts`
|
- Create: `apps/daemon/src/critique/parser.ts`
|
||||||
- Create: `apps/daemon/src/critique/parsers/v1.ts`
|
- Create: `apps/daemon/src/critique/parsers/v1.ts`
|
||||||
- Create: `apps/daemon/src/critique/errors.ts`
|
- Create: `apps/daemon/src/critique/errors.ts`
|
||||||
- Test: `apps/daemon/src/critique/__tests__/parser.test.ts`
|
- Test: `apps/daemon/tests/critique/parser.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing test against the happy fixture**
|
- [ ] **Step 1: Write the failing test against the happy fixture**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// apps/daemon/src/critique/__tests__/parser.test.ts
|
// apps/daemon/tests/critique/parser.test.ts
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
@ -776,7 +777,7 @@ git commit -m "feat(daemon): add v1 streaming parser for Critique Theater wire p
|
||||||
### Task 2.3: Cover failure-mode fixtures
|
### Task 2.3: Cover failure-mode fixtures
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `apps/daemon/src/critique/__tests__/parser.test.ts`
|
- Modify: `apps/daemon/tests/critique/parser.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Add failing tests for malformed inputs**
|
- [ ] **Step 1: Add failing tests for malformed inputs**
|
||||||
|
|
||||||
|
|
@ -843,12 +844,12 @@ git commit -m "test(daemon): cover parser failure modes with golden fixtures"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/critique/scoreboard.ts`
|
- Create: `apps/daemon/src/critique/scoreboard.ts`
|
||||||
- Test: `apps/daemon/src/critique/__tests__/scoreboard.test.ts`
|
- Test: `apps/daemon/tests/critique/scoreboard.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing test**
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// apps/daemon/src/critique/__tests__/scoreboard.test.ts
|
// apps/daemon/tests/critique/scoreboard.test.ts
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { defaultCritiqueConfig } from '@open-design/contracts/critique';
|
import { defaultCritiqueConfig } from '@open-design/contracts/critique';
|
||||||
import { computeComposite } from '../scoreboard';
|
import { computeComposite } from '../scoreboard';
|
||||||
|
|
@ -909,7 +910,7 @@ pnpm --filter @open-design/daemon test scoreboard.test.ts
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/daemon/src/critique/scoreboard.ts apps/daemon/src/critique/__tests__/scoreboard.test.ts
|
git add apps/daemon/src/critique/scoreboard.ts apps/daemon/tests/critique/scoreboard.test.ts
|
||||||
git commit -m "feat(daemon): scoreboard composite formula with weight redistribution"
|
git commit -m "feat(daemon): scoreboard composite formula with weight redistribution"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -917,7 +918,7 @@ git commit -m "feat(daemon): scoreboard composite formula with weight redistribu
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `apps/daemon/src/critique/scoreboard.ts`
|
- Modify: `apps/daemon/src/critique/scoreboard.ts`
|
||||||
- Modify: `apps/daemon/src/critique/__tests__/scoreboard.test.ts`
|
- Modify: `apps/daemon/tests/critique/scoreboard.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing test**
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
|
@ -980,7 +981,7 @@ pnpm --filter @open-design/daemon test scoreboard.test.ts
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/daemon/src/critique/scoreboard.ts apps/daemon/src/critique/__tests__/scoreboard.test.ts
|
git add apps/daemon/src/critique/scoreboard.ts apps/daemon/tests/critique/scoreboard.test.ts
|
||||||
git commit -m "feat(daemon): scoreboard round-end gate with maxRounds fallback"
|
git commit -m "feat(daemon): scoreboard round-end gate with maxRounds fallback"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -988,7 +989,7 @@ git commit -m "feat(daemon): scoreboard round-end gate with maxRounds fallback"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `apps/daemon/src/critique/scoreboard.ts`
|
- Modify: `apps/daemon/src/critique/scoreboard.ts`
|
||||||
- Modify: `apps/daemon/src/critique/__tests__/scoreboard.test.ts`
|
- Modify: `apps/daemon/tests/critique/scoreboard.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write failing test**
|
- [ ] **Step 1: Write failing test**
|
||||||
|
|
||||||
|
|
@ -1054,7 +1055,7 @@ git commit -m "feat(daemon): fallback-policy round selector"
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/db/migrations/0042_critique_rounds.up.sql` (number after the latest existing migration; rename if collides)
|
- Create: `apps/daemon/src/db/migrations/0042_critique_rounds.up.sql` (number after the latest existing migration; rename if collides)
|
||||||
- Create: `apps/daemon/src/db/migrations/0042_critique_rounds.down.sql`
|
- Create: `apps/daemon/src/db/migrations/0042_critique_rounds.down.sql`
|
||||||
- Test: `apps/daemon/src/db/__tests__/migrations.test.ts` (extend existing)
|
- Test: `apps/daemon/tests/db/migrations.test.ts` (extend existing)
|
||||||
|
|
||||||
- [ ] **Step 1: Inspect current migration list to pick the next ordinal**
|
- [ ] **Step 1: Inspect current migration list to pick the next ordinal**
|
||||||
|
|
||||||
|
|
@ -1089,7 +1090,7 @@ ALTER TABLE artifacts DROP COLUMN critique_score;
|
||||||
- [ ] **Step 3: Add a migration test that exercises up/down round-trip**
|
- [ ] **Step 3: Add a migration test that exercises up/down round-trip**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// apps/daemon/src/db/__tests__/migrations.test.ts (append)
|
// apps/daemon/tests/db/migrations.test.ts (append)
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
import { runMigrationsTo, migrationIds } from '../runner';
|
import { runMigrationsTo, migrationIds } from '../runner';
|
||||||
|
|
||||||
|
|
@ -1122,7 +1123,7 @@ git commit -m "feat(daemon): add critique_* columns to artifacts via reversible
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/critique/transcript.ts`
|
- Create: `apps/daemon/src/critique/transcript.ts`
|
||||||
- Test: `apps/daemon/src/critique/__tests__/transcript.test.ts`
|
- Test: `apps/daemon/tests/critique/transcript.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Failing test**
|
- [ ] **Step 1: Failing test**
|
||||||
|
|
||||||
|
|
@ -1193,7 +1194,7 @@ export async function writeTranscript(
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/daemon/src/critique/transcript.ts apps/daemon/src/critique/__tests__/transcript.test.ts
|
git add apps/daemon/src/critique/transcript.ts apps/daemon/tests/critique/transcript.test.ts
|
||||||
git commit -m "feat(daemon): transcript writer with ndjson + gzip threshold"
|
git commit -m "feat(daemon): transcript writer with ndjson + gzip threshold"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1201,7 +1202,7 @@ git commit -m "feat(daemon): transcript writer with ndjson + gzip threshold"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/critique/orchestrator.ts`
|
- Create: `apps/daemon/src/critique/orchestrator.ts`
|
||||||
- Test: `apps/daemon/src/critique/__tests__/orchestrator.test.ts`
|
- Test: `apps/daemon/tests/critique/orchestrator.test.ts`
|
||||||
- Modify: `apps/daemon/src/agents/spawn.ts` (existing) to call orchestrator when `enabled`
|
- Modify: `apps/daemon/src/agents/spawn.ts` (existing) to call orchestrator when `enabled`
|
||||||
|
|
||||||
- [ ] **Step 1: Failing test against the happy fixture wired through orchestrator**
|
- [ ] **Step 1: Failing test against the happy fixture wired through orchestrator**
|
||||||
|
|
@ -1365,7 +1366,7 @@ function writeTranscriptSync(dir: string, events: PanelEvent[]): string {
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/daemon/src/critique/orchestrator.ts apps/daemon/src/critique/__tests__/orchestrator.test.ts
|
git add apps/daemon/src/critique/orchestrator.ts apps/daemon/tests/critique/orchestrator.test.ts
|
||||||
git commit -m "feat(daemon): orchestrator wires parser, scoreboard, SSE, and persistence"
|
git commit -m "feat(daemon): orchestrator wires parser, scoreboard, SSE, and persistence"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1389,7 +1390,7 @@ In `spawn.ts`, after stdout is established, branch on `cfg.enabled`:
|
||||||
- [ ] **Step 3: Add an integration test**
|
- [ ] **Step 3: Add an integration test**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// apps/daemon/src/agents/__tests__/spawn-critique.test.ts
|
// apps/daemon/tests/agents/spawn-critique.test.ts
|
||||||
import { spawnAgent } from '../spawn';
|
import { spawnAgent } from '../spawn';
|
||||||
|
|
||||||
it('routes through critique orchestrator when OD_CRITIQUE_ENABLED=true', async () => {
|
it('routes through critique orchestrator when OD_CRITIQUE_ENABLED=true', async () => {
|
||||||
|
|
@ -1421,7 +1422,7 @@ git commit -m "feat(daemon): branch agent spawn through critique orchestrator wh
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/web/src/prompts/panel.ts`
|
- Create: `apps/web/src/prompts/panel.ts`
|
||||||
- Test: `apps/web/src/prompts/__tests__/panel.test.ts`
|
- Test: `apps/web/tests/prompts/panel.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Failing snapshot test**
|
- [ ] **Step 1: Failing snapshot test**
|
||||||
|
|
||||||
|
|
@ -1529,7 +1530,7 @@ Skill: ${skill.id}.
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/web/src/prompts/panel.ts apps/web/src/prompts/__tests__/panel.test.ts
|
git add apps/web/src/prompts/panel.ts apps/web/tests/prompts/panel.test.ts
|
||||||
git commit -m "feat(web): add Critique Theater prompt protocol addendum"
|
git commit -m "feat(web): add Critique Theater prompt protocol addendum"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1547,7 +1548,7 @@ grep -n "compose\|render\|prompt" apps/web/src/prompts/discovery.ts | head -20
|
||||||
- [ ] **Step 2: Add failing test that final composed prompt contains PROTOCOL block**
|
- [ ] **Step 2: Add failing test that final composed prompt contains PROTOCOL block**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// apps/web/src/prompts/__tests__/discovery.test.ts (extend)
|
// apps/web/tests/prompts/discovery.test.ts (extend)
|
||||||
it('appends Critique Theater protocol when cfg.enabled', () => {
|
it('appends Critique Theater protocol when cfg.enabled', () => {
|
||||||
const out = composeDiscoveryPrompt({ ...input, critique: { enabled: true } });
|
const out = composeDiscoveryPrompt({ ...input, critique: { enabled: true } });
|
||||||
expect(out).toContain('<CRITIQUE_RUN');
|
expect(out).toContain('<CRITIQUE_RUN');
|
||||||
|
|
@ -1593,7 +1594,7 @@ git commit -m "feat(web): wire panel prompt addendum into discovery composer"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/api/projects/critique/interrupt.ts`
|
- Create: `apps/daemon/src/api/projects/critique/interrupt.ts`
|
||||||
- Test: `apps/daemon/src/api/projects/critique/__tests__/interrupt.test.ts`
|
- Test: `apps/daemon/tests/api/projects/critique/interrupt.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Failing test**
|
- [ ] **Step 1: Failing test**
|
||||||
|
|
||||||
|
|
@ -1641,7 +1642,7 @@ git commit -m "feat(daemon): /api/projects/:id/critique/:runId/interrupt endpoin
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/daemon/src/api/projects/critique/rerun.ts`
|
- Create: `apps/daemon/src/api/projects/critique/rerun.ts`
|
||||||
- Test: `apps/daemon/src/api/projects/critique/__tests__/rerun.test.ts`
|
- Test: `apps/daemon/tests/api/projects/critique/rerun.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1–5: Same TDD shape as 6.1.** Endpoint resolves the original brief, builds a new artifact row (immutable original), and starts a fresh run with the previous artifact attached as prior-art context.
|
- [ ] **Step 1–5: Same TDD shape as 6.1.** Endpoint resolves the original brief, builds a new artifact row (immutable original), and starts a fresh run with the previous artifact attached as prior-art context.
|
||||||
|
|
||||||
|
|
@ -1657,7 +1658,7 @@ git commit -m "feat(daemon): /api/projects/:id/artifacts/:artifactId/critique/re
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/web/src/components/Theater/state/reducer.ts`
|
- Create: `apps/web/src/components/Theater/state/reducer.ts`
|
||||||
- Test: `apps/web/src/components/Theater/state/__tests__/reducer.test.ts`
|
- Test: `apps/web/tests/components/Theater/state/reducer.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Write failing reducer tests**
|
- [ ] **Step 1: Write failing reducer tests**
|
||||||
|
|
||||||
|
|
@ -1800,7 +1801,7 @@ git commit -m "feat(web): pure reducer for Critique Theater states"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/web/src/components/Theater/hooks/useCritiqueStream.ts`
|
- Create: `apps/web/src/components/Theater/hooks/useCritiqueStream.ts`
|
||||||
- Test: `apps/web/src/components/Theater/hooks/__tests__/useCritiqueStream.test.tsx`
|
- Test: `apps/web/tests/components/Theater/hooks/useCritiqueStream.test.tsx`
|
||||||
|
|
||||||
- [ ] **Step 1–5:** Standard React hook TDD. Hook subscribes to the existing `useProjectEvents()` SSE bus, filters to `critique.*` events, feeds them into the reducer via `useReducer`, and returns `[state, dispatch]`. Use RTL with a stub event source to drive the test.
|
- [ ] **Step 1–5:** Standard React hook TDD. Hook subscribes to the existing `useProjectEvents()` SSE bus, filters to `critique.*` events, feeds them into the reducer via `useReducer`, and returns `[state, dispatch]`. Use RTL with a stub event source to drive the test.
|
||||||
|
|
||||||
|
|
@ -1812,7 +1813,7 @@ git commit -m "feat(web): useCritiqueStream hook subscribes to SSE and feeds red
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `apps/web/src/components/Theater/hooks/useCritiqueReplay.ts`
|
- Create: `apps/web/src/components/Theater/hooks/useCritiqueReplay.ts`
|
||||||
- Test: same `__tests__/`
|
- Test: same `tests/` component area
|
||||||
|
|
||||||
- [ ] **Step 1–5:** Hook fetches `transcript_path`, decompresses if `.gz`, splits ndjson lines, dispatches into the reducer at the chosen speed. Test with a fixture transcript on disk.
|
- [ ] **Step 1–5:** Hook fetches `transcript_path`, decompresses if `.gz`, splits ndjson lines, dispatches into the reducer at the chosen speed. Test with a fixture transcript on disk.
|
||||||
|
|
||||||
|
|
@ -1839,7 +1840,7 @@ For each of `PanelistLane.tsx`, `ScoreTicker.tsx`, `RoundDivider.tsx`, `TheaterS
|
||||||
- [ ] **Step 5: Commit.** One component per commit:
|
- [ ] **Step 5: Commit.** One component per commit:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add apps/web/src/components/Theater/<Component>.tsx apps/web/src/components/Theater/__tests__/<Component>.test.tsx
|
git add apps/web/src/components/Theater/<Component>.tsx apps/web/tests/components/Theater/<Component>.test.tsx
|
||||||
git commit -m "feat(web): Theater <Component>"
|
git commit -m "feat(web): Theater <Component>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1944,7 +1945,7 @@ The conformance harness runs against every adapter listed `status: production` i
|
||||||
- Create: `apps/daemon/src/critique/__fixtures__/adapters/synthetic-good.ts` — child-process stub that writes `happy-3-rounds.txt`.
|
- Create: `apps/daemon/src/critique/__fixtures__/adapters/synthetic-good.ts` — child-process stub that writes `happy-3-rounds.txt`.
|
||||||
- Create: `apps/daemon/src/critique/__fixtures__/adapters/synthetic-bad.ts` — stub that writes `malformed-unbalanced.txt`.
|
- Create: `apps/daemon/src/critique/__fixtures__/adapters/synthetic-bad.ts` — stub that writes `malformed-unbalanced.txt`.
|
||||||
|
|
||||||
- [ ] **Step 1–5:** Write each as a tiny Node script invoked through the daemon's existing CLI-spawn primitive. Tests in `apps/daemon/src/critique/__tests__/conformance.test.ts` register both as fake adapters and assert good ⇒ shipped, bad ⇒ degraded with `critique:degraded` mark and 24h TTL.
|
- [ ] **Step 1–5:** Write each as a tiny Node script invoked through the daemon's existing CLI-spawn primitive. Tests in `apps/daemon/tests/critique/conformance.test.ts` register both as fake adapters and assert good ⇒ shipped, bad ⇒ degraded with `critique:degraded` mark and 24h TTL.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git commit -m "feat(daemon): adapter conformance synthetic fixtures and degraded TTL"
|
git commit -m "feat(daemon): adapter conformance synthetic fixtures and degraded TTL"
|
||||||
|
|
@ -2012,7 +2013,7 @@ git commit -m "test(a11y): Theater self-audits to WCAG AA"
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `apps/daemon/src/metrics/index.ts` (existing)
|
- Modify: `apps/daemon/src/metrics/index.ts` (existing)
|
||||||
- Test: `apps/daemon/src/metrics/__tests__/critique.test.ts`
|
- Test: `apps/daemon/tests/metrics/critique.test.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: Failing test.** Register the metrics, drive a synthetic run through the orchestrator, scrape `/api/metrics`, assert the named series exist with sane labels.
|
- [ ] **Step 1: Failing test.** Register the metrics, drive a synthetic run through the orchestrator, scrape `/api/metrics`, assert the named series exist with sane labels.
|
||||||
|
|
||||||
|
|
@ -2194,7 +2195,7 @@ git commit -m "chore(rollout): M0 ships behind OD_CRITIQUE_ENABLED=false"
|
||||||
|
|
||||||
### Task 15.2: Final validation matrix
|
### Task 15.2: Final validation matrix
|
||||||
|
|
||||||
- [ ] **Step 1: Run** `pnpm typecheck`, `pnpm test`, `pnpm test:ui`, `pnpm test:e2e:live`, `pnpm build`, `pnpm check:residual-js`, `pnpm check:dead-exports`, `pnpm check:critique-coverage`, `pnpm size-limit`. All must pass.
|
- [ ] **Step 1: Run** `pnpm guard`, `pnpm typecheck`, package-scoped tests/builds for changed packages, `pnpm -C e2e test:ui`, `pnpm -C e2e test:e2e:live`, `pnpm check:dead-exports`, `pnpm check:critique-coverage`, `pnpm size-limit`. All must pass.
|
||||||
|
|
||||||
- [ ] **Step 2: Run** `pnpm tools-dev run web --daemon-port 17456 --web-port 17573` and validate live happy path with a real CLI on PATH.
|
- [ ] **Step 2: Run** `pnpm tools-dev run web --daemon-port 17456 --web-port 17573` and validate live happy path with a real CLI on PATH.
|
||||||
|
|
||||||
|
|
@ -2213,8 +2214,8 @@ gh pr create --title "feat: Critique Theater (panel-tempered, scored, replayable
|
||||||
- Zero new processes; same BYOK story; works across all 12 adapters with conformance grading.
|
- Zero new processes; same BYOK story; works across all 12 adapters with conformance grading.
|
||||||
|
|
||||||
## Test plan
|
## Test plan
|
||||||
- [ ] pnpm typecheck && pnpm test && pnpm test:ui
|
- [ ] pnpm guard && pnpm typecheck && pnpm -C e2e test:ui
|
||||||
- [ ] pnpm test:e2e:live (Playwright happy + interrupt + visual + a11y)
|
- [ ] pnpm -C e2e test:e2e:live (Playwright happy + interrupt + visual + a11y)
|
||||||
- [ ] pnpm size-limit (Theater bundle < 18 KiB gz)
|
- [ ] pnpm size-limit (Theater bundle < 18 KiB gz)
|
||||||
- [ ] pnpm check:critique-coverage (no orphan surfaces)
|
- [ ] pnpm check:critique-coverage (no orphan surfaces)
|
||||||
- [ ] manual: enable in Settings, submit a brief, watch Theater, ship at >= 8.0
|
- [ ] manual: enable in Settings, submit a brief, watch Theater, ship at >= 8.0
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ The optimization work should proceed in dependency order. Some items can run in
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| W1 | Completed | Confirm architecture and capability boundaries | R4, R15 | — | Written ownership rules for web, daemon, shared contracts, and dangerous local capabilities. See `specs/current/architecture-boundaries.md`. |
|
| W1 | Completed | Confirm architecture and capability boundaries | R4, R15 | — | Written ownership rules for web, daemon, shared contracts, and dangerous local capabilities. See `specs/current/architecture-boundaries.md`. |
|
||||||
| W2 | Completed | Define API, SSE, and error contracts | R2, R7, R8 | W1 | `packages/contracts` now provides shared request/response types, SSE event unions, and error model helpers consumed by web and daemon. |
|
| W2 | Completed | Define API, SSE, and error contracts | R2, R7, R8 | W1 | `packages/contracts` now provides shared request/response types, SSE event unions, and error model helpers consumed by web and daemon. |
|
||||||
| W3 | Completed | Migrate project-owned code to TypeScript | R1 | W2 for highest-value shared types | Daemon, root scripts, and e2e support now use TypeScript sources; daemon compiles to `apps/daemon/dist`; residual JS is checked by `pnpm check:residual-js`. |
|
| W3 | Completed | Migrate project-owned code to TypeScript | R1 | W2 for highest-value shared types | Daemon, root scripts, and e2e support now use TypeScript sources; daemon compiles to `apps/daemon/dist`; residual JS is checked by `pnpm guard`. |
|
||||||
| W4 | Planned | Add runtime validation at daemon boundaries | R3, R4 | W2 | Schemas for HTTP requests, paths, agents, models, uploads, task IDs, and command args. |
|
| W4 | Planned | Add runtime validation at daemon boundaries | R3, R4 | W2 | Schemas for HTTP requests, paths, agents, models, uploads, task IDs, and command args. |
|
||||||
| W5 | Planned | Modularize `server.ts` | R6 | W2, W3, W4 | Thin route handlers plus services/adapters for agents, DB, FS, streams, and artifacts. |
|
| W5 | Planned | Modularize `server.ts` | R6 | W2, W3, W4 | Thin route handlers plus services/adapters for agents, DB, FS, streams, and artifacts. |
|
||||||
| W6 | Planned | Introduce agent process/task manager | R5, R8, R11 | W2, W5 | Task state machine, cancellation, timeout, cleanup, exit handling, and concurrency controls. |
|
| W6 | Planned | Introduce agent process/task manager | R5, R8, R11 | W2, W5 | Task state machine, cancellation, timeout, cleanup, exit handling, and concurrency controls. |
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ Follow the root `AGENTS.md` first. This file only records module-level boundarie
|
||||||
- Keep `tools-pack` focused on packaging/runtime control and release artifact preparation. Runtime updater product integration remains a later phase.
|
- Keep `tools-pack` focused on packaging/runtime control and release artifact preparation. Runtime updater product integration remains a later phase.
|
||||||
- Pack-specific Electron builder resources belong under `tools/pack/resources/`; do not reference app/docs/download assets directly from pack logic.
|
- Pack-specific Electron builder resources belong under `tools/pack/resources/`; do not reference app/docs/download assets directly from pack logic.
|
||||||
- Namespace controls packaged data/log/runtime/cache paths. Ports are transient transport details and must not participate in path decisions.
|
- Namespace controls packaged data/log/runtime/cache paths. Ports are transient transport details and must not participate in path decisions.
|
||||||
- The package/build boundary of root `pnpm build` is intentionally unchanged in this round and should be handled by the future `tools-pack` task.
|
- There is no root `pnpm build` aggregate. Use package-scoped builds for source packages and `pnpm tools-pack ...` for packaged artifact build/install/release flows.
|
||||||
|
|
||||||
## Orchestration boundary
|
## Orchestration boundary
|
||||||
|
|
||||||
|
- Tool tests live in each tool's `tests/` directory, sibling to `src/`; keep `src/` source-only and do not add new `*.test.ts` or `*.test.tsx` files under `src/`.
|
||||||
- Orchestration layers must consume primitives from `@open-design/sidecar-proto`, `@open-design/sidecar`, and `@open-design/platform`.
|
- Orchestration layers must consume primitives from `@open-design/sidecar-proto`, `@open-design/sidecar`, and `@open-design/platform`.
|
||||||
- Do not hand-build `--od-stamp-*` args, process-scan regexes, runtime tokens, process roles, or duplicate namespace/source args in `tools/dev`, future `tools/pack`, or packaged launchers.
|
- Do not hand-build `--od-stamp-*` args, process-scan regexes, runtime tokens, process roles, or duplicate namespace/source args in `tools/dev`, future `tools/pack`, or packaged launchers.
|
||||||
- Port flags are authoritative inputs: `--daemon-port` and `--web-port`. Internal env vars are `OD_PORT` and `OD_WEB_PORT`; do not introduce `NEXT_PORT`.
|
- Port flags are authoritative inputs: `--daemon-port` and `--web-port`. Internal env vars are `OD_PORT` and `OD_WEB_PORT`; do not introduce `NEXT_PORT`.
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./esbuild.config.mjs",
|
"build": "node ./esbuild.config.mjs",
|
||||||
"dev": "tsx ./src/index.ts",
|
"dev": "tsx ./src/index.ts",
|
||||||
"test": "node --import tsx --test src/*.test.ts",
|
"test": "node --import tsx --test tests/*.test.ts",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
"@open-design/sidecar-proto": "workspace:*",
|
||||||
"cac": "6.7.14"
|
"cac": "6.7.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -909,7 +909,7 @@ function addPortOptions(command: ReturnType<typeof cli.command>) {
|
||||||
return command
|
return command
|
||||||
.option("--daemon-port <port>", "force daemon port; conflict quick-fails")
|
.option("--daemon-port <port>", "force daemon port; conflict quick-fails")
|
||||||
.option("--web-port <port>", "force web port; conflict quick-fails")
|
.option("--web-port <port>", "force web port; conflict quick-fails")
|
||||||
.option("--prod", "use production build (requires pnpm build first)");
|
.option("--prod", "use production build (requires pnpm --filter @open-design/web build first)");
|
||||||
}
|
}
|
||||||
|
|
||||||
addPortOptions(addSharedOptions(cli.command("start [app]", "Start daemon, web, desktop, or all when app is omitted"))).action(
|
addPortOptions(addSharedOptions(cli.command("start [app]", "Start daemon, web, desktop, or all when app is omitted"))).action(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
appendStartupLogDiagnostics,
|
appendStartupLogDiagnostics,
|
||||||
createStartupLogDiagnostics,
|
createStartupLogDiagnostics,
|
||||||
detectLogDiagnostics,
|
detectLogDiagnostics,
|
||||||
} from "./diagnostics.js";
|
} from "../src/diagnostics.js";
|
||||||
|
|
||||||
describe("tools-dev diagnostics", () => {
|
describe("tools-dev diagnostics", () => {
|
||||||
it("detects native addon ABI mismatches", () => {
|
it("detects native addon ABI mismatches", () => {
|
||||||
|
|
@ -14,5 +14,5 @@
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "esbuild.config.mjs"]
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "tests/**/*.ts", "esbuild.config.mjs"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@
|
||||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||||
"dev": "tsx ./src/index.ts",
|
"dev": "tsx ./src/index.ts",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@open-design/platform": "workspace:0.3.0",
|
"@open-design/platform": "workspace:*",
|
||||||
"@open-design/sidecar": "workspace:0.3.0",
|
"@open-design/sidecar": "workspace:*",
|
||||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
"@open-design/sidecar-proto": "workspace:*",
|
||||||
"@electron/notarize": "3.1.0",
|
"@electron/notarize": "3.1.0",
|
||||||
"cac": "6.7.14",
|
"cac": "6.7.14",
|
||||||
"electron-builder": "26.8.1"
|
"electron-builder": "26.8.1"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import type { ToolPackConfig } from "./config.js";
|
import type { ToolPackConfig } from "../src/config.js";
|
||||||
import {
|
import {
|
||||||
buildDockerArgs,
|
buildDockerArgs,
|
||||||
matchesAppImageProcess,
|
matchesAppImageProcess,
|
||||||
renderDesktopTemplate,
|
renderDesktopTemplate,
|
||||||
sanitizeNamespace,
|
sanitizeNamespace,
|
||||||
} from "./linux.js";
|
} from "../src/linux.js";
|
||||||
|
|
||||||
function makeConfig(): ToolPackConfig {
|
function makeConfig(): ToolPackConfig {
|
||||||
return {
|
return {
|
||||||
|
|
@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile, mkdir } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
import { copyBundledResourceTrees } from "./resources.js";
|
import { copyBundledResourceTrees } from "../src/resources.js";
|
||||||
|
|
||||||
describe("copyBundledResourceTrees", () => {
|
describe("copyBundledResourceTrees", () => {
|
||||||
it("includes prompt templates", async () => {
|
it("includes prompt templates", async () => {
|
||||||
9
tools/pack/tsconfig.tests.json
Normal file
9
tools/pack/tsconfig.tests.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue