mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +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
|
||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||
|
||||
- name: Check residual JS in TypeScript packages
|
||||
run: pnpm check:residual-js
|
||||
- name: Check repository layout policies
|
||||
run: pnpm guard
|
||||
|
||||
- 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
|
||||
# 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
|
||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||
|
||||
- name: Check residual JS in TypeScript packages
|
||||
run: pnpm check:residual-js
|
||||
- name: Check repository layout policies
|
||||
run: pnpm guard
|
||||
|
||||
# Workspace tests are intentionally not gated here. apps/web's
|
||||
# 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.
|
||||
- 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
|
||||
|
||||
|
|
@ -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`.
|
||||
- `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
|
||||
|
||||
- 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 `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.
|
||||
- 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.
|
||||
- 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`.
|
||||
|
|
@ -64,7 +71,7 @@ This file is the single source of truth for agents entering this repository. Rea
|
|||
## Validation strategy
|
||||
|
||||
- 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>`.
|
||||
- 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.
|
||||
|
|
@ -86,23 +93,22 @@ pnpm tools-dev check
|
|||
```
|
||||
|
||||
```bash
|
||||
pnpm guard
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm build
|
||||
pnpm test:ui
|
||||
pnpm test:ui:headed
|
||||
pnpm test:e2e:live
|
||||
pnpm check:residual-js
|
||||
pnpm -C e2e test:ui
|
||||
pnpm -C e2e test:ui:headed
|
||||
pnpm -C e2e test:e2e:live
|
||||
```
|
||||
|
||||
```bash
|
||||
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 build
|
||||
pnpm --filter @open-design/desktop build
|
||||
pnpm --filter @open-design/tools-dev build
|
||||
pnpm --filter @open-design/tools-pack build
|
||||
pnpm -r --if-present run typecheck
|
||||
pnpm -r --if-present run test
|
||||
```
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ corepack enable # wählt das gepinnte pnpm aus packageManager
|
|||
pnpm install
|
||||
pnpm tools-dev run web # daemon + web foreground loop
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ corepack enable # sélectionne la version de pnpm définie par package
|
|||
pnpm install
|
||||
pnpm tools-dev run web # boucle daemon + web au premier plan
|
||||
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 ;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ corepack enable # packageManager で指定された pnpm を選択
|
|||
pnpm install
|
||||
pnpm tools-dev run web # daemon + web フォアグラウンドループ
|
||||
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 を作成してください。
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ corepack enable # selects the pinned pnpm from packageManager
|
|||
pnpm install
|
||||
pnpm tools-dev run web # daemon + web foreground loop
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ corepack enable # selects the pinned pnpm from packageManager
|
|||
pnpm install
|
||||
pnpm tools-dev run web # daemon + web foreground loop
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ corepack enable # 使用 packageManager 固定的 pnpm
|
|||
pnpm install
|
||||
pnpm tools-dev run web # daemon + web 前台闭环
|
||||
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。
|
||||
|
|
|
|||
|
|
@ -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 stop # verwaltete Runtimes stoppen
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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 stop # arrête les runtimes gérés
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pnpm tools-dev logs # daemon/web/desktop のログを表示
|
|||
pnpm tools-dev check # status + 最近のログ + 一般的な診断
|
||||
pnpm tools-dev stop # 管理対象ランタイムを停止
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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 stop # stop managed runtimes
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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 stop # stop managed runtimes
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
- 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
|
||||
|
||||
- App business layers must not import sidecar packages or branch on `runtime.mode`, `namespace`, `ipc`, or `source`.
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@open-design/contracts": "workspace:0.3.0",
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
||||
"@open-design/contracts": "workspace:*",
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*",
|
||||
"better-sqlite3": "^12.9.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"express": "^4.19.2",
|
||||
|
|
|
|||
|
|
@ -862,7 +862,7 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST
|
|||
const hints: string[] = [];
|
||||
if (!cliExists) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0"
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@
|
|||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-design/daemon": "workspace:0.3.0",
|
||||
"@open-design/desktop": "workspace:0.3.0",
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
||||
"@open-design/web": "workspace:0.3.0"
|
||||
"@open-design/daemon": "workspace:*",
|
||||
"@open-design/desktop": "workspace:*",
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*",
|
||||
"@open-design/web": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.32.1",
|
||||
"@open-design/contracts": "workspace:0.3.0",
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
||||
"@open-design/contracts": "workspace:*",
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*",
|
||||
"next": "^16.2.4",
|
||||
"openai": "^6.35.0",
|
||||
"react": "^18.3.1",
|
||||
|
|
|
|||
|
|
@ -1519,7 +1519,7 @@ function IntegrationsSection() {
|
|||
: 'Node binary is missing.'}
|
||||
</strong>{' '}
|
||||
{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>
|
||||
) : 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,
|
||||
inferLegacyManifest,
|
||||
parseArtifactManifest,
|
||||
} from './manifest';
|
||||
} from '../../src/artifacts/manifest';
|
||||
|
||||
describe('parseArtifactManifest', () => {
|
||||
it('returns null for malformed json', () => {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { renderMarkdownToSafeHtml } from './markdown';
|
||||
import { renderMarkdownToSafeHtml } from '../../src/artifacts/markdown';
|
||||
|
||||
describe('renderMarkdownToSafeHtml', () => {
|
||||
it('renders common markdown blocks', () => {
|
||||
|
|
@ -8,9 +8,9 @@ import {
|
|||
RendererRegistry,
|
||||
SvgRenderer,
|
||||
artifactRendererRegistry,
|
||||
} from './renderer-registry';
|
||||
import { renderMarkdownToSafeHtml } from './markdown';
|
||||
import type { ProjectFile } from '../types';
|
||||
} from '../../src/artifacts/renderer-registry';
|
||||
import { renderMarkdownToSafeHtml } from '../../src/artifacts/markdown';
|
||||
import type { ProjectFile } from '../../src/types';
|
||||
|
||||
function baseFile(overrides: Partial<ProjectFile> & Pick<ProjectFile, 'name'>): ProjectFile {
|
||||
return {
|
||||
|
|
@ -8,8 +8,8 @@ import {
|
|||
overlayBoundsFromSnapshot,
|
||||
removeAttachedComment,
|
||||
targetFromSnapshot,
|
||||
} from './comments';
|
||||
import type { ChatMessage, PreviewComment } from './types';
|
||||
} from '../src/comments';
|
||||
import type { ChatMessage, PreviewComment } from '../src/types';
|
||||
|
||||
describe('preview comment attachment helpers', () => {
|
||||
it('builds compact target context from an iframe snapshot', () => {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { assistantRoleLabel } from './AssistantMessage';
|
||||
import type { ChatMessage } from '../types';
|
||||
import { assistantRoleLabel } from '../../src/components/AssistantMessage';
|
||||
import type { ChatMessage } from '../../src/types';
|
||||
|
||||
const t = () => 'Assistant';
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
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', () => {
|
||||
it('places awaiting_input between running and succeeded', () => {
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { FileViewer, SvgViewer } from './FileViewer';
|
||||
import type { ProjectFile } from '../types';
|
||||
import { FileViewer, SvgViewer } from '../../src/components/FileViewer';
|
||||
import type { ProjectFile } from '../../src/types';
|
||||
|
||||
function baseFile(overrides: Partial<ProjectFile>): ProjectFile {
|
||||
return {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { FileWorkspace } from './FileWorkspace';
|
||||
import { FileWorkspace } from '../../src/components/FileWorkspace';
|
||||
|
||||
describe('FileWorkspace upload input', () => {
|
||||
it('keeps the Design Files picker aligned with drag-and-drop file support', () => {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { PreviewModal } from './PreviewModal';
|
||||
import { PreviewModal } from '../../src/components/PreviewModal';
|
||||
|
||||
describe('PreviewModal sandbox isolation', () => {
|
||||
it('renders generated previews without same-origin sandbox access', () => {
|
||||
|
|
@ -3,8 +3,8 @@ import {
|
|||
isValidApiBaseUrl,
|
||||
switchApiProtocolConfig,
|
||||
updateCurrentApiProtocolConfig,
|
||||
} from './SettingsDialog';
|
||||
import type { AppConfig } from '../types';
|
||||
} from '../../src/components/SettingsDialog';
|
||||
import type { AppConfig } from '../../src/types';
|
||||
|
||||
const baseConfig: AppConfig = {
|
||||
mode: 'api',
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { decideAutoOpenAfterWrite } from './auto-open-file';
|
||||
import { decideAutoOpenAfterWrite } from '../../src/components/auto-open-file';
|
||||
|
||||
describe('decideAutoOpenAfterWrite', () => {
|
||||
it('returns shouldOpen=false when filePath is empty', () => {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
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', () => {
|
||||
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 { describe, expect, it } from 'vitest';
|
||||
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));
|
||||
|
||||
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 { en } from './locales/en';
|
||||
import { LOCALES, LOCALE_LABEL, type Dict, type Locale } from './types';
|
||||
import { en } from '../../src/i18n/locales/en';
|
||||
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'];
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ function placeholders(value: string): string[] {
|
|||
}
|
||||
|
||||
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 => {
|
||||
return Boolean(value) && typeof value === 'object';
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { isOpenAICompatible } from './openai-compatible';
|
||||
import { isOpenAICompatible } from '../../src/providers/openai-compatible';
|
||||
|
||||
describe('isOpenAICompatible', () => {
|
||||
it('preserves explicit OpenAI model routing when the URL contains anthropic', () => {
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
createProjectEventsConnection,
|
||||
projectEventsUrl,
|
||||
type ProjectFileChangeEvent,
|
||||
} from './project-events';
|
||||
} from '../../src/providers/project-events';
|
||||
|
||||
type Listener = (evt: unknown) => void;
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { fetchAppVersionInfo, fetchProjectFileText, uploadProjectFiles } from './registry';
|
||||
import { fetchAppVersionInfo, fetchProjectFileText, uploadProjectFiles } from '../../src/providers/registry';
|
||||
|
||||
describe('fetchAppVersionInfo', () => {
|
||||
afterEach(() => {
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { reattachDaemonRun, streamViaDaemon } from './daemon';
|
||||
import { streamMessageOpenAI } from './openai-compatible';
|
||||
import { parseSseFrame } from './sse';
|
||||
import { reattachDaemonRun, streamViaDaemon } from '../../src/providers/daemon';
|
||||
import { streamMessageOpenAI } from '../../src/providers/openai-compatible';
|
||||
import { parseSseFrame } from '../../src/providers/sse';
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
|
|
@ -6,7 +6,7 @@ import {
|
|||
exportAsMd,
|
||||
exportAsPdf,
|
||||
openSandboxedPreviewInNewTab,
|
||||
} from './exports';
|
||||
} from '../../src/runtime/exports';
|
||||
|
||||
function mockResponse(headers: Record<string, string>): Response {
|
||||
return { headers: new Headers(headers) } as Response;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildReactComponentSrcdoc, prepareReactComponentSource } from './react-component';
|
||||
import { buildReactComponentSrcdoc, prepareReactComponentSource } from '../../src/runtime/react-component';
|
||||
|
||||
describe('prepareReactComponentSource', () => {
|
||||
it('adapts a default function export for iframe rendering', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { buildSrcdoc } from './srcdoc';
|
||||
import { buildSrcdoc } from '../../src/runtime/srcdoc';
|
||||
|
||||
const deckHtml = `<!doctype html>
|
||||
<html>
|
||||
|
|
@ -2,16 +2,16 @@ import { useState } from 'react';
|
|||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ToolCard } from '../components/ToolCard';
|
||||
import { ToolCard } from '../../src/components/ToolCard';
|
||||
import {
|
||||
clearToolRenderers,
|
||||
deriveToolStatus,
|
||||
getToolRenderer,
|
||||
registerToolRenderer,
|
||||
toRenderProps,
|
||||
} from './tool-renderers';
|
||||
import type { ToolRenderProps } from './tool-renderers';
|
||||
import type { AgentEvent } from '../types';
|
||||
} from '../../src/runtime/tool-renderers';
|
||||
import type { ToolRenderProps } from '../../src/runtime/tool-renderers';
|
||||
import type { AgentEvent } from '../../src/types';
|
||||
|
||||
type ToolUse = Extract<AgentEvent, { kind: 'tool_use' }>;
|
||||
type ToolResult = Extract<AgentEvent, { kind: 'tool_result' }>;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DEFAULT_CONFIG, loadConfig } from './config';
|
||||
import type { AppConfig } from '../types';
|
||||
import { DEFAULT_CONFIG, loadConfig } from '../../src/state/config';
|
||||
import type { AppConfig } from '../../src/types';
|
||||
|
||||
const store = new Map<string, string>();
|
||||
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import litellmData from './litellm-models.json';
|
||||
import litellmData from '../../src/state/litellm-models.json';
|
||||
import {
|
||||
effectiveMaxTokens,
|
||||
FALLBACK_MAX_TOKENS,
|
||||
MAX_MAX_TOKENS,
|
||||
MIN_MAX_TOKENS,
|
||||
modelMaxTokensDefault,
|
||||
} from './maxTokens';
|
||||
} from '../../src/state/maxTokens';
|
||||
|
||||
describe('modelMaxTokensDefault', () => {
|
||||
it('falls through to LiteLLM data for canonical Anthropic ids', () => {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { apiProtocolLabel, apiProtocolModelLabel } from './apiProtocol';
|
||||
import { agentModelDisplayName } from './agentLabels';
|
||||
import { apiProtocolLabel, apiProtocolModelLabel } from '../../src/utils/apiProtocol';
|
||||
import { agentModelDisplayName } from '../../src/utils/agentLabels';
|
||||
|
||||
describe('api protocol labels', () => {
|
||||
it('labels the selected API protocol instead of assuming Anthropic', () => {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { ChatMessage } from '../types';
|
||||
import { messageTime } from './chatTime';
|
||||
import type { ChatMessage } from '../../src/types';
|
||||
import { messageTime } from '../../src/utils/chatTime';
|
||||
|
||||
describe('messageTime', () => {
|
||||
it('uses assistant startedAt before persisted createdAt', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { showCompletionNotification } from './notifications';
|
||||
import { showCompletionNotification } from '../../src/utils/notifications';
|
||||
|
||||
type NotificationOptionsWithRenotify = NotificationOptions & { renotify?: boolean };
|
||||
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
"app/**/*",
|
||||
"sidecar/**/*",
|
||||
"src/**/*",
|
||||
"tests/**/*",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"out/types/**/*.ts",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ import { defineConfig } from 'vitest/config';
|
|||
export default defineConfig({
|
||||
test: {
|
||||
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
|
||||
|
||||
```bash
|
||||
pnpm run test:ui
|
||||
```
|
||||
|
||||
Ou direto dentro do pacote de teste:
|
||||
|
||||
```bash
|
||||
pnpm --filter @open-design/e2e test:ui
|
||||
pnpm -C e2e test:ui
|
||||
```
|
||||
|
||||
Após a execução são gerados automaticamente:
|
||||
|
|
|
|||
|
|
@ -91,13 +91,7 @@
|
|||
## 运行方式
|
||||
|
||||
```bash
|
||||
pnpm run test:ui
|
||||
```
|
||||
|
||||
也可以直接在独立测试包内运行:
|
||||
|
||||
```bash
|
||||
pnpm --filter @open-design/e2e test:ui
|
||||
pnpm -C e2e test:ui
|
||||
```
|
||||
|
||||
运行完成后会自动生成:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"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": "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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
- `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/reports/test-results/`
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
- `junit.xml`:JUnit 格式结果,方便接 CI
|
||||
- `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/reports/test-results/`
|
||||
|
|
|
|||
17
package.json
17
package.json
|
|
@ -13,21 +13,18 @@
|
|||
"postinstall": "node ./scripts/postinstall.mjs",
|
||||
"tools-dev": "pnpm exec tools-dev",
|
||||
"tools-pack": "pnpm exec tools-pack",
|
||||
"build": "pnpm --filter @open-design/web build",
|
||||
"check:residual-js": "node --experimental-strip-types scripts/check-residual-js.ts",
|
||||
"guard": "tsx ./scripts/guard.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",
|
||||
"seed:test-projects": "node --experimental-strip-types scripts/seed-test-projects.ts",
|
||||
"test:e2e:live": "pnpm --filter @open-design/e2e test:e2e:live",
|
||||
"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"
|
||||
"typecheck": "pnpm -r --workspace-concurrency=1 --if-present run typecheck && tsc -p scripts/tsconfig.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@open-design/tools-dev": "workspace:0.3.0",
|
||||
"@open-design/tools-pack": "workspace:0.3.0"
|
||||
"@open-design/tools-dev": "workspace:*",
|
||||
"@open-design/tools-pack": "workspace:*",
|
||||
"@types/node": "^20.17.10",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~24",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Follow the root `AGENTS.md` first. This file only records module-level boundarie
|
|||
|
||||
## 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 let app packages depend directly on sidecar control-plane details.
|
||||
- Do not hard-code Open Design app/source/mode constants in `sidecar` or `platform`.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
defaultCritiqueConfig,
|
||||
isPanelEvent,
|
||||
type PanelEvent,
|
||||
} from './critique';
|
||||
} from '../src/critique';
|
||||
|
||||
describe('CritiqueConfig', () => {
|
||||
it('defaults validate against the schema', () => {
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import type { PanelEvent } from '../critique';
|
||||
import type { PanelEvent } from '../../src/critique';
|
||||
import {
|
||||
panelEventToSse,
|
||||
type CritiqueSseEvent,
|
||||
CRITIQUE_SSE_EVENT_NAMES,
|
||||
} from './critique';
|
||||
} from '../../src/sse/critique';
|
||||
|
||||
describe('CritiqueSseEvent', () => {
|
||||
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": {
|
||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
matchesStampedProcess,
|
||||
readProcessStampFromCommand,
|
||||
type ProcessStampContract,
|
||||
} from "./index.js";
|
||||
} from "../src/index.js";
|
||||
|
||||
type FakeStamp = {
|
||||
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": {
|
||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
STAMP_MODE_FLAG,
|
||||
STAMP_NAMESPACE_FLAG,
|
||||
STAMP_SOURCE_FLAG,
|
||||
} from "./index.js";
|
||||
} from "../src/index.js";
|
||||
|
||||
const validStamp = {
|
||||
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": {
|
||||
"build": "node ./esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
resolveSourceRuntimeRoot,
|
||||
type SidecarContractDescriptor,
|
||||
type SidecarStampShape,
|
||||
} from "./index.js";
|
||||
} from "../src/index.js";
|
||||
|
||||
type FakeStamp = SidecarStampShape & {
|
||||
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:
|
||||
'@open-design/tools-dev':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:tools/dev
|
||||
'@open-design/tools-pack':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
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:
|
||||
dependencies:
|
||||
|
|
@ -21,16 +30,16 @@ importers:
|
|||
specifier: ^1.0.0
|
||||
version: 1.29.0(zod@4.4.2)
|
||||
'@open-design/contracts':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/contracts
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
better-sqlite3:
|
||||
specifier: ^12.9.0
|
||||
|
|
@ -70,13 +79,13 @@ importers:
|
|||
apps/desktop:
|
||||
dependencies:
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
|
|
@ -123,22 +132,22 @@ importers:
|
|||
apps/packaged:
|
||||
dependencies:
|
||||
'@open-design/daemon':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../daemon
|
||||
'@open-design/desktop':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../desktop
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
'@open-design/web':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../web
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
|
|
@ -160,16 +169,16 @@ importers:
|
|||
specifier: ^0.32.1
|
||||
version: 0.32.1
|
||||
'@open-design/contracts':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/contracts
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
next:
|
||||
specifier: ^16.2.4
|
||||
|
|
@ -288,13 +297,13 @@ importers:
|
|||
tools/dev:
|
||||
dependencies:
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
cac:
|
||||
specifier: 6.7.14
|
||||
|
|
@ -319,13 +328,13 @@ importers:
|
|||
specifier: 3.1.0
|
||||
version: 3.1.0
|
||||
'@open-design/platform':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/platform
|
||||
'@open-design/sidecar':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar
|
||||
'@open-design/sidecar-proto':
|
||||
specifier: workspace:0.3.0
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sidecar-proto
|
||||
cac:
|
||||
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,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": ["../e2e/node_modules/@types"],
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ Expected: pnpm 10.33.2, no errors, all workspace packages linked.
|
|||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm check:residual-js
|
||||
pnpm guard
|
||||
pnpm --filter @open-design/web test
|
||||
pnpm --filter @open-design/daemon test
|
||||
```
|
||||
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:**
|
||||
- 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**
|
||||
|
||||
```ts
|
||||
// packages/contracts/src/critique.test.ts
|
||||
// packages/contracts/tests/critique.test.ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
CritiqueConfigSchema,
|
||||
|
|
@ -204,7 +205,7 @@ Expected: PASS, 5/5.
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -212,11 +213,11 @@ git commit -m "feat(contracts): add CritiqueConfig schema and defaults"
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
Append to `packages/contracts/src/critique.test.ts`:
|
||||
Append to `packages/contracts/tests/critique.test.ts`:
|
||||
```ts
|
||||
import { isPanelEvent, type PanelEvent } from './critique';
|
||||
|
||||
|
|
@ -316,7 +317,7 @@ Expected: PASS, all assertions.
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -325,7 +326,7 @@ git commit -m "feat(contracts): add PanelEvent discriminated union and isPanelEv
|
|||
**Files:**
|
||||
- Modify: `packages/contracts/src/sse.ts` (existing)
|
||||
- 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**
|
||||
|
||||
|
|
@ -337,7 +338,7 @@ Expected: existing `SseEvent` discriminated union pattern. Match it exactly when
|
|||
- [ ] **Step 2: Write the failing test**
|
||||
|
||||
```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 { isSseEvent, panelEventToSse, type SseEvent } from './sse';
|
||||
|
||||
|
|
@ -401,7 +402,7 @@ pnpm --filter @open-design/contracts test
|
|||
Expected: all sse tests pass.
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -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/parsers/v1.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**
|
||||
|
||||
```ts
|
||||
// apps/daemon/src/critique/__tests__/parser.test.ts
|
||||
// apps/daemon/tests/critique/parser.test.ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
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
|
||||
|
||||
**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**
|
||||
|
||||
|
|
@ -843,12 +844,12 @@ git commit -m "test(daemon): cover parser failure modes with golden fixtures"
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
```ts
|
||||
// apps/daemon/src/critique/__tests__/scoreboard.test.ts
|
||||
// apps/daemon/tests/critique/scoreboard.test.ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { defaultCritiqueConfig } from '@open-design/contracts/critique';
|
||||
import { computeComposite } from '../scoreboard';
|
||||
|
|
@ -909,7 +910,7 @@ pnpm --filter @open-design/daemon test scoreboard.test.ts
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -917,7 +918,7 @@ git commit -m "feat(daemon): scoreboard composite formula with weight redistribu
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -980,7 +981,7 @@ pnpm --filter @open-design/daemon test scoreboard.test.ts
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -988,7 +989,7 @@ git commit -m "feat(daemon): scoreboard round-end gate with maxRounds fallback"
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -1054,7 +1055,7 @@ git commit -m "feat(daemon): fallback-policy round selector"
|
|||
**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.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**
|
||||
|
||||
|
|
@ -1089,7 +1090,7 @@ ALTER TABLE artifacts DROP COLUMN critique_score;
|
|||
- [ ] **Step 3: Add a migration test that exercises up/down round-trip**
|
||||
|
||||
```ts
|
||||
// apps/daemon/src/db/__tests__/migrations.test.ts (append)
|
||||
// apps/daemon/tests/db/migrations.test.ts (append)
|
||||
import Database from 'better-sqlite3';
|
||||
import { runMigrationsTo, migrationIds } from '../runner';
|
||||
|
||||
|
|
@ -1122,7 +1123,7 @@ git commit -m "feat(daemon): add critique_* columns to artifacts via reversible
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -1193,7 +1194,7 @@ export async function writeTranscript(
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -1201,7 +1202,7 @@ git commit -m "feat(daemon): transcript writer with ndjson + gzip threshold"
|
|||
|
||||
**Files:**
|
||||
- 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`
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -1389,7 +1390,7 @@ In `spawn.ts`, after stdout is established, branch on `cfg.enabled`:
|
|||
- [ ] **Step 3: Add an integration test**
|
||||
|
||||
```ts
|
||||
// apps/daemon/src/agents/__tests__/spawn-critique.test.ts
|
||||
// apps/daemon/tests/agents/spawn-critique.test.ts
|
||||
import { spawnAgent } from '../spawn';
|
||||
|
||||
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:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -1529,7 +1530,7 @@ Skill: ${skill.id}.
|
|||
- [ ] **Step 5: Commit**
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -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**
|
||||
|
||||
```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', () => {
|
||||
const out = composeDiscoveryPrompt({ ...input, critique: { enabled: true } });
|
||||
expect(out).toContain('<CRITIQUE_RUN');
|
||||
|
|
@ -1593,7 +1594,7 @@ git commit -m "feat(web): wire panel prompt addendum into discovery composer"
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -1641,7 +1642,7 @@ git commit -m "feat(daemon): /api/projects/:id/critique/:runId/interrupt endpoin
|
|||
|
||||
**Files:**
|
||||
- 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.
|
||||
|
||||
|
|
@ -1657,7 +1658,7 @@ git commit -m "feat(daemon): /api/projects/:id/artifacts/:artifactId/critique/re
|
|||
|
||||
**Files:**
|
||||
- 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**
|
||||
|
||||
|
|
@ -1800,7 +1801,7 @@ git commit -m "feat(web): pure reducer for Critique Theater states"
|
|||
|
||||
**Files:**
|
||||
- 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.
|
||||
|
||||
|
|
@ -1812,7 +1813,7 @@ git commit -m "feat(web): useCritiqueStream hook subscribes to SSE and feeds red
|
|||
|
||||
**Files:**
|
||||
- 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.
|
||||
|
||||
|
|
@ -1839,7 +1840,7 @@ For each of `PanelistLane.tsx`, `ScoreTicker.tsx`, `RoundDivider.tsx`, `TheaterS
|
|||
- [ ] **Step 5: Commit.** One component per commit:
|
||||
|
||||
```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>"
|
||||
```
|
||||
|
||||
|
|
@ -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-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
|
||||
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:**
|
||||
- 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.
|
||||
|
||||
|
|
@ -2194,7 +2195,7 @@ git commit -m "chore(rollout): M0 ships behind OD_CRITIQUE_ENABLED=false"
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
## Test plan
|
||||
- [ ] pnpm typecheck && pnpm test && pnpm test:ui
|
||||
- [ ] pnpm test:e2e:live (Playwright happy + interrupt + visual + a11y)
|
||||
- [ ] pnpm guard && pnpm typecheck && pnpm -C e2e test:ui
|
||||
- [ ] pnpm -C e2e test:e2e:live (Playwright happy + interrupt + visual + a11y)
|
||||
- [ ] pnpm size-limit (Theater bundle < 18 KiB gz)
|
||||
- [ ] pnpm check:critique-coverage (no orphan surfaces)
|
||||
- [ ] 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`. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
- 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`.
|
||||
- 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`.
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
"scripts": {
|
||||
"build": "node ./esbuild.config.mjs",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*",
|
||||
"cac": "6.7.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -909,7 +909,7 @@ function addPortOptions(command: ReturnType<typeof cli.command>) {
|
|||
return command
|
||||
.option("--daemon-port <port>", "force daemon 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(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
appendStartupLogDiagnostics,
|
||||
createStartupLogDiagnostics,
|
||||
detectLogDiagnostics,
|
||||
} from "./diagnostics.js";
|
||||
} from "../src/diagnostics.js";
|
||||
|
||||
describe("tools-dev diagnostics", () => {
|
||||
it("detects native addon ABI mismatches", () => {
|
||||
|
|
@ -14,5 +14,5 @@
|
|||
"target": "ES2024",
|
||||
"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",
|
||||
"dev": "tsx ./src/index.ts",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-design/platform": "workspace:0.3.0",
|
||||
"@open-design/sidecar": "workspace:0.3.0",
|
||||
"@open-design/sidecar-proto": "workspace:0.3.0",
|
||||
"@open-design/platform": "workspace:*",
|
||||
"@open-design/sidecar": "workspace:*",
|
||||
"@open-design/sidecar-proto": "workspace:*",
|
||||
"@electron/notarize": "3.1.0",
|
||||
"cac": "6.7.14",
|
||||
"electron-builder": "26.8.1"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ToolPackConfig } from "./config.js";
|
||||
import type { ToolPackConfig } from "../src/config.js";
|
||||
import {
|
||||
buildDockerArgs,
|
||||
matchesAppImageProcess,
|
||||
renderDesktopTemplate,
|
||||
sanitizeNamespace,
|
||||
} from "./linux.js";
|
||||
} from "../src/linux.js";
|
||||
|
||||
function makeConfig(): ToolPackConfig {
|
||||
return {
|
||||
|
|
@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile, mkdir } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { copyBundledResourceTrees } from "./resources.js";
|
||||
import { copyBundledResourceTrees } from "../src/resources.js";
|
||||
|
||||
describe("copyBundledResourceTrees", () => {
|
||||
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