@erclx/canon 5.40.0 → 5.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +34 -17
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  4. package/claude/skills/auto-ship/SKILL.md +6 -0
  5. package/claude/skills/auto-ship/references/fan-in.md +59 -0
  6. package/claude/skills/create-skill/references/skill.md +1 -1
  7. package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
  8. package/claude/skills/decision-escalate/SKILL.md +1 -2
  9. package/claude/skills/git-branch/REQUIREMENT.md +17 -6
  10. package/claude/skills/git-branch/SKILL.md +63 -7
  11. package/claude/skills/plan-feature/references/batches.md +48 -1
  12. package/claude/skills/plan-feature/references/plan.md +2 -0
  13. package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
  14. package/claude/skills/plan-intake-answer/SKILL.md +1 -2
  15. package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
  16. package/claude/skills/review-ui/SKILL.md +2 -0
  17. package/claude/skills/review-ui/references/close-out.md +47 -0
  18. package/claude/skills/review-ui/references/post.md +3 -1
  19. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
  20. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  21. package/claude/skills/role-worker/SKILL.md +9 -0
  22. package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
  23. package/claude/skills/teach-workspace/SKILL.md +1 -2
  24. package/docs/workflow/parallel-features.md +1 -0
  25. package/docs/workflow/skill-map.md +1 -1
  26. package/package.json +1 -1
  27. package/src/claude/cases/git.ts +4 -0
  28. package/src/commands/pr.ts +32 -9
  29. package/src/commands/records.ts +11 -3
  30. package/src/context/shape-gated.ts +1 -0
  31. package/src/markdown/scope.ts +6 -2
  32. package/src/pr/frames.ts +53 -9
  33. package/src/records/validate.ts +1 -0
  34. package/src/tasks/answers.ts +79 -2
  35. package/src/tasks/fan-in.ts +176 -0
  36. package/src/tasks/stack.ts +1 -1
  37. package/tooling/astro/reference.md +80 -39
  38. package/tooling/base/reference.md +63 -32
  39. package/tooling/base/seeds/canon/context/ci.md +7 -15
  40. package/tooling/base/seeds/canon/context/development.md +14 -14
  41. package/tooling/claude/reference.md +40 -22
  42. package/tooling/cloudflare/reference.md +31 -12
  43. package/tooling/go/reference.md +47 -23
  44. package/tooling/nextjs/reference.md +52 -19
  45. package/tooling/php/reference.md +35 -16
  46. package/tooling/python/reference.md +47 -30
  47. package/tooling/shell/reference.md +6 -5
  48. package/tooling/vite-react/reference.md +49 -21
  49. package/tooling/web/reference.md +130 -70
@@ -6,13 +6,13 @@ The shell stack is an add-on that formats and lints the shell scripts a project
6
6
 
7
7
  ## Install
8
8
 
9
- - Install `shfmt` and `shellcheck` by hand, through `sudo apt-get install shfmt shellcheck` or `brew install shfmt shellcheck`. Injection runs only `bun add -D`, and neither tool ships through the registry.
9
+ - Install `shfmt` and `shellcheck` by hand with `sudo apt-get install shfmt shellcheck` or `brew install shfmt shellcheck`. Injection runs only `bun add -D`, and neither ships through the registry.
10
10
  - `scripts/verify.ts` probes both before the `Shell` phase and names the install command when one is missing, rather than letting `find` report a missing executable.
11
11
 
12
12
  ## shfmt
13
13
 
14
14
  - Indent with 2 spaces, set through `--indent 2` on every call.
15
- - `.editorconfig` carries an `[*.sh]` block with `indent_style = space` and `indent_size = 2`, so the editor and shfmt agree. Dropping the block because shfmt already sets indentation produces spurious diffs.
15
+ - `.editorconfig` carries an `[*.sh]` block with `indent_style = space` and `indent_size = 2`, so the editor and shfmt agree. Dropping it because shfmt sets indentation produces spurious diffs.
16
16
 
17
17
  ## shellcheck
18
18
 
@@ -27,16 +27,17 @@ The shell stack is an add-on that formats and lints the shell scripts a project
27
27
 
28
28
  ## Verify
29
29
 
30
- - `scripts/verify.ts` runs `check:shell` in its `Shell` phase wherever `package.json` declares it, between the format check and the unit tests. No stack's `scripts/verify.json` requires it, so a folder that never synced this stack skips the phase.
30
+ `scripts/verify.ts` runs `check:shell` in its `Shell` phase wherever `package.json` declares it, between the format check and the unit tests. No stack's `scripts/verify.json` requires it, so a folder that never synced this stack skips the phase.
31
31
 
32
32
  ## lint-staged
33
33
 
34
- - `.lintstagedrc` is a base seed this stack cannot reach once written. Add a `**/*.sh` entry by hand, running `shfmt --write --indent 2` then `shellcheck --severity=warning`, to run the checks on commit.
34
+ - `.lintstagedrc` is a base seed this stack cannot reach once written. Add a `**/*.sh` entry by hand to run the checks on commit.
35
+ - That entry runs `shfmt --write --indent 2` then `shellcheck --severity=warning`.
35
36
  - Do NOT copy that glob into a `package.json` script. lint-staged expands its own globs and passes matched files as arguments, which a bare shell script does not.
36
37
 
37
38
  ## CI
38
39
 
39
- - `.github/workflows/shell.yml` runs on pull requests targeting `main` and on `workflow_dispatch`: checkout, setup Bun, `bun install --frozen-lockfile`, install both tools through apt, then `check:shell`.
40
+ `.github/workflows/shell.yml` runs on pull requests targeting `main` and on `workflow_dispatch`. Its steps are checkout, setup Bun, `bun install --frozen-lockfile`, installing both tools through apt, then `check:shell`.
40
41
 
41
42
  ## CLI
42
43
 
@@ -4,44 +4,72 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- The vite-react stack covers Vite + React + TypeScript projects: web apps and Chrome extensions. It ships golden configs for `vite.config.ts`, `vitest.config.ts` (merging from vite config), `playwright.config.ts`, and a unified `tsconfig.json` with `@/` path alias. Shared web tooling (oxlint, screenshot template, VS Code, CI, verify script) comes from the `web` layer.
7
+ The vite-react stack covers Vite + React + TypeScript web apps and Chrome extensions. It ships golden configs for `vite.config.ts`, `vitest.config.ts`, `playwright.config.ts`, and a unified `tsconfig.json` with an `@/` path alias. Shared web tooling (oxlint, screenshot template, VS Code, CI, verify script) comes from the `web` layer.
8
8
 
9
9
  ## Scaffold checklist
10
10
 
11
11
  1. Scaffold with `bunx create-vite@latest <name> --template react-ts` (web apps) or `bunx create-crxjs@latest` (Chrome extensions).
12
- 2. Install base and web tooling: `canon tooling sync web . --write`
13
- 3. Install vite-react deps and configs: `canon tooling sync vite-react . --write`
12
+ 2. Install base and web tooling with `canon tooling sync web . --write`.
13
+ 3. Install vite-react deps and configs with `canon tooling sync vite-react . --write`.
14
14
  4. Extend the `ci` and `development` context entries under `canon/context/` per the web reference's extend sections plus the vite-react rows below.
15
15
  5. Run `bun run lint:fix`, then `bun run format`, then `bun run check`.
16
16
 
17
17
  ## What ships as golden configs
18
18
 
19
- - `vite.config.ts`: `@vitejs/plugin-react`, `@tailwindcss/vite`, `@` path alias to `./src`, `VITE_BASE_URL` env for base path. Dev port `5173` and preview port `4173`, each plus `WORKTREE_PORT_OFFSET`, both with `strictPort`.
20
- - `vitest.config.ts`: merges from `vite.config.ts` (imported with its `.ts` extension, which Vite 8 requires), jsdom, globals, setup file, `passWithNoTests: true`, v8 coverage.
21
- - `playwright.config.ts`: all browsers, `webServer` on `bun run dev` at port `5173` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`, trace under `use`.
22
- - `tsconfig.json`: unified, `noEmit: true`, `skipLibCheck: true`, `@/` paths, `vitest/globals` and `@testing-library/jest-dom/vitest` in types.
19
+ - `vite.config.ts` adds `@vitejs/plugin-react`, `@tailwindcss/vite`, the `@` alias to `./src`, and `VITE_BASE_URL` for the base path.
20
+ - It serves dev on port `5173` and preview on `4173`, each plus `WORKTREE_PORT_OFFSET`, both with `strictPort`.
21
+ - `vitest.config.ts` merges from `vite.config.ts`, imported with its `.ts` extension, which Vite 8 requires. It sets jsdom, globals, the setup file, `passWithNoTests: true`, and v8 coverage.
22
+ - `playwright.config.ts` runs all browsers, with `webServer` on `bun run dev` at port `5173` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`, and trace under `use`.
23
+ - `tsconfig.json` is unified, with `noEmit: true`, `skipLibCheck: true`, `@/` paths, and `vitest/globals` and `@testing-library/jest-dom/vitest` in types.
23
24
 
24
25
  ## Chrome extension variant
25
26
 
26
- When scaffolding a Chrome extension, override the installed golden configs:
27
+ When scaffolding a Chrome extension, override the installed golden configs.
27
28
 
28
- - `vite.config.ts`: use `crx({ manifest })` and `zip()` from `@crxjs/vite-plugin` instead of `react()` alone. Keep the derived `server.port` and `server.strictPort: true`, set `server.hmr.clientPort` to the same derived value, and add `chrome-extension://` to CORS origins. Drop `VITE_BASE_URL`.
29
- - `vitest.config.ts`: use a standalone `defineConfig` (no `mergeConfig`). crxjs plugin breaks Vitest. Declare `@vitejs/plugin-react` and `@tailwindcss/vite` directly. Add `**/release/**` to excludes and `manifest.config.ts`, `**/*.d.ts` to coverage excludes.
30
- - `playwright.config.ts`: chromium-only (Firefox and WebKit cannot run extensions). Bundled `chromium` channel. No `baseURL` or `webServer`. Tests load the built extension directly from `dist/`.
31
- - `e2e/fixtures.ts`: extend Playwright base `test` with `context` (persistent context loading the extension from `dist/`) and `extensionId` (extracted from service worker URL). Rename `use` to `apply` to avoid oxlint's `react/rules-of-hooks`. `waitForEvent('serviceworker')` blocks until the MV3 service worker registers.
32
- - `manifest.config.ts`: use `defineManifest` from `@crxjs/vite-plugin`. Read `name` and `version` from `package.json`. Entry points `src/popup/index.html`, `src/sidepanel/index.html`, `src/background/index.ts`, `src/content/main.tsx`. Permissions `sidePanel`, `contentSettings`, `storage`. Icon `public/logo.png` at size 48.
33
- - `screenshot` script: `bun run build && bun e2e/screenshot.ts` (no preview server).
34
- - Additional gitignore entries: `*.crx`, `*.pem`, `release/`.
29
+ ### Build config
30
+
31
+ The crxjs plugin replaces the plain React build.
32
+
33
+ - `vite.config.ts` uses `crx({ manifest })` and `zip()` from `@crxjs/vite-plugin` instead of `react()` alone. Drop `VITE_BASE_URL`.
34
+ - Keep the derived `server.port` and `server.strictPort: true`, set `server.hmr.clientPort` to the same derived value, and add `chrome-extension://` to CORS origins.
35
+
36
+ ### Test configs
37
+
38
+ The plugin also changes how Vitest and Playwright run.
39
+
40
+ - `vitest.config.ts` uses a standalone `defineConfig` with no `mergeConfig`, since the crxjs plugin breaks Vitest. Declare `@vitejs/plugin-react` and `@tailwindcss/vite` directly.
41
+ - It also adds `**/release/**` to excludes, and `manifest.config.ts` and `**/*.d.ts` to coverage excludes.
42
+ - `playwright.config.ts` is chromium-only, since Firefox and WebKit cannot run extensions.
43
+ - It uses the bundled `chromium` channel with no `baseURL` or `webServer`, and tests load the built extension from `dist/`.
44
+
45
+ ### Fixtures and manifest
46
+
47
+ The extension needs a fixture file and a manifest config.
48
+
49
+ - `e2e/fixtures.ts` extends Playwright's base `test` with `context`, a persistent context loading the extension from `dist/`, and `extensionId`, extracted from the service worker URL.
50
+ - Rename `use` to `apply` in that file to avoid oxlint's `react/rules-of-hooks`. `waitForEvent('serviceworker')` blocks until the MV3 service worker registers.
51
+ - `manifest.config.ts` uses `defineManifest` from `@crxjs/vite-plugin` and reads `name` and `version` from `package.json`.
52
+ - Its entry points are `src/popup/index.html`, `src/sidepanel/index.html`, `src/background/index.ts`, and `src/content/main.tsx`.
53
+ - Its permissions are `sidePanel`, `contentSettings`, and `storage`, with icon `public/logo.png` at size 48.
54
+
55
+ ### Scripts and ignores
56
+
57
+ The extension build changes one script and adds ignores.
58
+
59
+ - The `screenshot` script is `bun run build && bun e2e/screenshot.ts`, with no preview server.
60
+ - Add `*.crx`, `*.pem`, and `release/` to gitignore.
35
61
 
36
62
  ## Development docs (extend)
37
63
 
38
- Append to the `## Scripts` table:
64
+ Append these rows to the `## Scripts` table.
39
65
 
40
- | `bun run dev` | Start the Vite dev server on port 5173, plus this worktree's port offset. |
41
- | `bun run build` | Typecheck then build the production bundle. |
42
- | `bun run preview` | Serve the built bundle locally. |
43
- | `bun run typecheck` | Run `tsc --noEmit`. |
66
+ | Command | Purpose |
67
+ | ------------------- | -------------------------------------------------------------- |
68
+ | `bun run dev` | Start the Vite dev server on 5173 plus this worktree's offset. |
69
+ | `bun run build` | Typecheck then build the production bundle. |
70
+ | `bun run preview` | Serve the built bundle locally. |
71
+ | `bun run typecheck` | Run `tsc --noEmit`. |
44
72
 
45
73
  ## CI docs (extend)
46
74
 
47
- In `canon/context/development/ci.md`, the Typecheck row's assertion reads: `` `tsc --noEmit` passes ``. <!-- audit-ignore-citations: canon/context/development/ci.md -->
75
+ In `canon/context/development/ci.md`, the Typecheck row asserts `` `tsc --noEmit` passes ``. <!-- audit-ignore-citations: canon/context/development/ci.md -->
@@ -4,128 +4,188 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- The web layer covers web-universal tooling shared across Vite + React, Astro, and any future web stack. It ships golden configs for oxlint, Vitest, Playwright, Tailwind v4, screenshots, VS Code integration, and CI. Framework-specific wiring (Vite plugins, Astro islands, Next.js config) lives in per-stack adapter folders that extend this layer.
7
+ The web layer covers the tooling shared across Vite + React, Astro, and any future web stack. It ships golden configs for oxlint, Vitest, Playwright, Tailwind v4, screenshots, VS Code integration, and CI. Framework wiring (Vite plugins, Astro islands, Next.js config) lives in per-stack adapter folders that extend this layer.
8
8
 
9
9
  ## What ships as golden configs
10
10
 
11
- Golden config files live in `tooling/web/configs/` and are copied into the target on `canon tooling sync web . --write`, replacing whatever sits at those paths. They are the source of truth. The reference covers rationale and tradeoffs. Configs show the concrete setup.
11
+ Golden configs live in `tooling/web/configs/`. `canon tooling sync web . --write` copies them into the target and replaces whatever sits at those paths. They are the source of truth, so this reference covers rationale and the configs show the setup.
12
12
 
13
- - `.oxlintrc.json`: migrated from the ESLint config it replaced. Runs the recommended JS and TypeScript rules, the React hooks and refresh rules, test-scoped vitest rules plus `no-focused-tests`, and `check-file` and `simple-import-sort` as JS plugins.
14
- - `src/test/setup.ts`: `@testing-library/jest-dom/vitest` import, `cleanup` after each test.
15
- - `e2e/screenshot.ts`: capture template. A single `CASES` record at the top carries one entry per output file, each naming a section, a theme, and a route, and the loop below crosses every case with the `VIEWPORTS` array and writes `screenshots/<hostname>/<section>/<width>--<theme>.png`, keyed on `SCREENSHOT_BASE_URL`'s hostname so a local and a deployed run land in different folders. `VIEWPORTS` holds four breakpoint buckets named by width, 320, 768, 1280 and 1536, with 320 as the base a page has to hold without wrapping or clipping. A case flagged `evidence: true` additionally writes `evidence/<section>/<width>--<theme>.png`, with no hostname segment, at the viewports flagged `evidence` alone, which are 320 and 1280. Committing all four multiplies the tracked frames by the width count, so the flag is what keeps the tracked set at twice the sections rather than four times. A case may name `excludeViewports` to skip a width, for a route whose long-form prose reflows at 320 instead of breaking. `SCREENSHOT_FILTER` narrows a run to the cases whose label, `<section>/<width>--<theme>`, contains any comma-separated term, exits 1 when nothing matches and lists the labels that exist, and leaves the rest of the sweep alone. `--require-base-url` skips a flagged case entirely, so a production smoke run neither writes to the committed path nor counts an evidence route in its console-clean check, and exits 1 when that leaves zero cases run, so flagging every case cannot silence the production check without saying so. Per-project cases extend the one record. A route's widths and themes sit together under its section folder, so the filename carries both. A case may also name `sections`, one `{ name, selector }` per addressable part of the route, and each writes its own frame at `screenshots/<hostname>/<section>/<name>/<width>--<theme>.png` beside the whole-page one, plus `evidence/<section>/<name>/<width>--<theme>.png` where the case is flagged and the width carries `evidence`. The field is optional, so a case that names none captures the page alone as before, and a selector matching nothing is reported and skipped rather than raised, since `CASES` is a template a project rewrites and a target that syncs this file before editing its own list would otherwise capture nothing at all. Testing that every named section resolves belongs to the project that owns the selectors, beside its own page. A case may also name `mask`, a list of selectors whose boxes are painted over in every frame the case writes, whole-page and section alike, for a value that moves between runs with no code change, so a double capture still compares everything else. A mask selector matching nothing is reported and skipped the same way. Every case settles before anything is shot: the context is created with `reducedMotion: 'reduce'`, each named section is scrolled into view so an `IntersectionObserver` fires and a frame marked `loading="lazy"` loads, the page returns to the top, and fonts, images and same-origin frames are waited out on a bounded condition rather than on a second `networkidle`, which is a lifecycle event of the navigation and does not re-arm for a load a scroll started. A section frame drops the page's floating chrome, being whatever computes to `position: sticky` or `fixed`, because a section taller than the viewport is scrolled into view to be cut and a sticky bar otherwise rides that scroll into the middle of the frame. The whole-page frame is shot first and keeps its chrome. `--check-console-clean` collects `console`-level error messages per case and exits 1 with the list if any fired, turning the capture into a smoke check. `--require-base-url` exits 1 before launching a browser when `SCREENSHOT_BASE_URL` is unset, guarding a script meant to run against a real deployment from silently capturing `localhost`.
16
- - `scripts/screenshot.sh`: sources `scripts/lib/preview-server.sh`, then runs the capture against the served preview.
17
- - `scripts/lib/preview-server.sh`: sourced, never executed. Builds, serves on `PREVIEW_PORT`, blocks until the port answers, and traps the whole process group on exit, including the detached grandchild `lsof` finds. Both capture scripts source it so the startup lives once.
18
- - `scripts/readme-screenshot.sh`: captures one case under `SCREENSHOT_FILTER` and copies its light and dark frames to `assets/evidence/readme/`. `README_FILTER` ships empty with `header/1280` as its example, and the script exits 1 until a project fills it, since a shipped default would capture the wrong part of the page in every project but one. Its copy reads from `screenshots/<hostname>/`, which is where the harness writes.
19
- - `.github/workflows/readme-screenshot.yml`: refreshes that frame on a pull request and commits it back to the branch. The path list ships as `REPLACE_WITH_PROJECT_PATHS`, and the job's first step after checkout exits 1 while it stays, since a job that never fires leaves no run and no diff to notice. The workflow's own path is in the list so the installing pull request reaches that step. When a commit-back is right and when a report is, `governance/rules/tooling/ci-workflow.md` states the test.
20
- - `.github/workflows/verify.yml`: `static-checks`, `unit-tests`, `build-verify`, and `e2e-tests` jobs.
21
- - `scripts/verify.json`: the phases base's verify runner requires here, being typecheck, lint, unit tests, and build. `## Verify script` covers the order and the card exclusion stage.
22
- - `scripts/worktree-port.sh`: prints a base port plus this working directory's offset. Called with no argument it prints the offset alone. It refuses a folder left under the worktrees directory after its worktree was removed, rather than printing a port for it.
13
+ ### Lint and test setup
14
+
15
+ Two configs carry the lint and unit-test baseline.
16
+
17
+ - `.oxlintrc.json` is migrated from the ESLint config it replaced. It runs the recommended, React, and test-scoped vitest rules, with `check-file` and `simple-import-sort` as JS plugins.
18
+ - `src/test/setup.ts` imports `@testing-library/jest-dom/vitest` and runs `cleanup` after each test.
19
+
20
+ ### Screenshot cases
21
+
22
+ `e2e/screenshot.ts` is the capture template. One `CASES` record carries an entry per output file, each naming a section, a theme, and a route, and per-project cases extend it.
23
+
24
+ - The loop crosses every case with `VIEWPORTS` and writes `screenshots/<hostname>/<section>/<width>--<theme>.png`. The hostname of `SCREENSHOT_BASE_URL` keeps a local run and a deployed run apart.
25
+ - `VIEWPORTS` holds 320, 768, 1280, and 1536. Width 320 is the base a page has to hold without wrapping or clipping.
26
+ - `excludeViewports` skips a width, for a route whose long-form prose reflows at 320 instead of breaking.
27
+ - `evidence: true` also writes `evidence/<section>/<width>--<theme>.png` at 320 and 1280 alone, keeping the tracked set at twice the sections rather than four times.
28
+
29
+ ### Sections and masks
30
+
31
+ A case may name `sections`, one `{ name, selector }` per addressable part of the route. Each writes its own frame beside the whole-page one.
32
+
33
+ - A section frame lands at `screenshots/<hostname>/<section>/<name>/<width>--<theme>.png`, plus the same path under `evidence/` where the case and width are flagged.
34
+ - A selector matching nothing is reported and skipped, since a target syncing the template before editing its list would otherwise capture nothing.
35
+ - Testing that every section resolves belongs to the project owning the selectors.
36
+ - `mask` lists selectors painted over in every frame, for a value that moves with no code change, so a double capture compares the rest.
37
+
38
+ ### Settling before a shot
39
+
40
+ Every case settles before anything is shot.
41
+
42
+ - The context uses `reducedMotion: 'reduce'`, and each named section scrolls into view so an `IntersectionObserver` fires and `loading="lazy"` frames load. The page then returns to the top.
43
+ - Fonts, images, and same-origin frames are waited out on a bounded condition. A second `networkidle` does not re-arm for a load a scroll started.
44
+ - A section frame drops floating chrome (`position: sticky` or `fixed`), which would otherwise ride the scroll into the frame. The whole-page frame is shot first and keeps its chrome.
45
+
46
+ ### Run flags
47
+
48
+ Three controls narrow or harden a run.
49
+
50
+ - `SCREENSHOT_FILTER` keeps cases whose `<section>/<width>--<theme>` label contains a comma-separated term. It exits 1 and lists the labels on no match.
51
+ - `--check-console-clean` exits 1 with the list if any `console` error fired, turning the capture into a smoke check.
52
+ - `--require-base-url` exits 1 before launching a browser when `SCREENSHOT_BASE_URL` is unset, so a deployment script cannot capture `localhost`.
53
+ - With a base URL, it skips every flagged case so a production smoke run writes nothing to the committed path, and exits 1 when zero cases run.
54
+
55
+ ### Scripts and workflows
56
+
57
+ The wrappers around the capture and the verify run. `governance/rules/tooling/ci-workflow.md` states when a workflow's commit-back is right and when a report is.
58
+
59
+ - `scripts/screenshot.sh` sources `scripts/lib/preview-server.sh`, never executed itself. It builds, serves on `PREVIEW_PORT`, waits for the port, and traps the whole process group on exit.
60
+ - `scripts/readme-screenshot.sh` captures one case under `SCREENSHOT_FILTER` into `assets/evidence/readme/`. `README_FILTER` ships empty, so it exits 1 until a project fills it.
61
+ - `.github/workflows/readme-screenshot.yml` ships its path list as `REPLACE_WITH_PROJECT_PATHS`, and its first step exits 1 while that stays.
62
+ - `.github/workflows/verify.yml` runs `static-checks`, `unit-tests`, `build-verify`, and `e2e-tests`.
23
63
 
24
64
  ## What stays in per-stack adapters
25
65
 
26
- Framework glue lives in `tooling/vite-react/configs/` or `tooling/astro/configs/` because the merge helpers and config shapes differ:
66
+ Framework glue lives in `tooling/vite-react/configs/` or `tooling/astro/configs/`, because merge helpers and config shapes differ.
27
67
 
28
- - `vite.config.ts` or `astro.config.mjs`: the framework's config.
29
- - `vitest.config.ts`: uses `mergeConfig` in Vite stacks, `getViteConfig` from `astro/config` in Astro.
30
- - `playwright.config.ts`: `webServer` command differs per stack.
31
- - `tsconfig.json`: `extends` target differs. Astro uses `astro/tsconfigs/strict`. Vite projects use scaffold defaults.
68
+ - `vite.config.ts` or `astro.config.mjs` holds the framework's config.
69
+ - `vitest.config.ts` uses `mergeConfig` in Vite stacks and `getViteConfig` from `astro/config` in Astro.
70
+ - `playwright.config.ts` differs in its `webServer` command.
71
+ - `tsconfig.json` differs in its `extends` target. Astro uses `astro/tsconfigs/strict` and Vite projects use scaffold defaults.
32
72
 
33
73
  ## File layout
34
74
 
35
- - `src/` for app code, `e2e/` for Playwright, `scripts/` for shell, `src/test/setup.ts` for Vitest globals.
75
+ - `src/` holds app code, `e2e/` Playwright, `scripts/` shell, and `src/test/setup.ts` the Vitest globals.
36
76
  - Path alias `@` maps to `./src` in both tsconfig and the framework's build config.
37
- - Tsconfig is unified at root with `noEmit: true` in Vite stacks. Astro uses the scaffold default from `@astrojs/check`.
77
+ - Tsconfig sits at root with `noEmit: true` in Vite stacks. Astro keeps the scaffold default from `@astrojs/check`.
38
78
 
39
79
  ## Ports
40
80
 
41
81
  Two worktrees of one repository run the same stack, so a fixed port makes the second one attach to the first.
42
82
 
83
+ ### Deriving the port
84
+
85
+ The helper owns the offset, and a config only adds it.
86
+
43
87
  - Derive every served port from `scripts/worktree-port.sh`. Never write a port literal into a script string.
44
- - Read `WORKTREE_PORT_OFFSET` in a config and add it to the stack's default port. Unset yields the default, so a plain clone keeps the port it has always served on.
45
- - Draw the offset from a band of 50, hashed from the worktree folder name. Two worktrees can hash to one offset, so set `WORKTREE_PORT_OFFSET` by hand to break a tie.
46
- - Expect a non-zero exit and no port from a folder left under `.claude/worktrees/` once its worktree is gone. Git reports the parent repository from inside one, so the helper cannot read it as a worktree and every base it serves would land on the main checkout's port. Both shapes refuse, and they reach differently. A folder whose own `.git` was deleted refuses only under that directory, since location is the only thing separating it from an ordinary subdirectory the base port is correct for. A folder whose `.git` names a pruned administrative directory refuses wherever it sits, because a pointer to nothing is broken regardless of where the folder is.
47
- - Call it as `VAR=$(bash scripts/worktree-port.sh) && export VAR && <server>`, never as the shorter `VAR=$(bash scripts/worktree-port.sh) <server>`. An assignment prefix discards the exit status of its own substitution, so the shorter form starts the server with `VAR` set to the empty string, every config reads that back as an offset of zero, and the refusal lands on the port it was raised to protect. The assignment alone carries the status, which is what the `&&` reads.
48
- - Set `WORKTREE_PORT_OFFSET` by hand to serve from such a folder anyway. That is the one override, and it is checked before any directory test.
49
- - Force-replace `dev` and `preview` through `[scripts.override]`. Both stacks' scaffolds define those keys, and a plain `[scripts]` entry never replaces a key the scaffold already wrote.
50
- - Set `strictPort` on every dev and preview server. A server that walks to the next free port serves where nothing is looking for it.
51
- - Set Playwright `reuseExistingServer: false`. Reuse attaches to whatever answers on the port, which reports a pass against another branch's code and prints nothing to say so.
88
+ - Read `WORKTREE_PORT_OFFSET` in a config and add it to the stack's default port. Unset yields the default, so a plain clone keeps its port.
89
+ - The offset comes from a band of 50 hashed from the worktree folder name. Two worktrees can collide, so set `WORKTREE_PORT_OFFSET` by hand to break a tie.
90
+ - Force-replace `dev` and `preview` through `[scripts.override]`, since a plain `[scripts]` entry never replaces a key the scaffold wrote.
91
+ - Set `strictPort` on every dev and preview server, and Playwright `reuseExistingServer: false`. A server that walks to another port, or a reused one, passes against the wrong code silently.
92
+
93
+ ### Calling the helper
94
+
95
+ Call it as `VAR=$(bash scripts/worktree-port.sh) && export VAR && <server>`, never as `VAR=$(bash scripts/worktree-port.sh) <server>`. An assignment prefix discards the exit status of its own substitution. The shorter form starts the server with `VAR` empty, which every config reads as offset zero.
96
+
97
+ ### Refusal in a removed worktree
98
+
99
+ Expect a non-zero exit and no port from a folder left under `.claude/worktrees/` once its worktree is gone. Git reports the parent repository from inside one, so every base served would land on the main checkout's port.
100
+
101
+ - A folder whose own `.git` was deleted refuses only under that directory, since location alone separates it from an ordinary subdirectory.
102
+ - A folder whose `.git` names a pruned administrative directory refuses wherever it sits.
103
+ - Set `WORKTREE_PORT_OFFSET` by hand to serve from such a folder anyway. It is the one override and is checked before any directory test.
52
104
 
53
105
  ## Anti-patterns
54
106
 
55
- Sticky negative knowledge. Do not relearn.
107
+ Negative knowledge that costs a failed run to relearn.
56
108
 
57
- - Do NOT use `tsc -b` in a Vite project. Composite mode emits `.js` next to `.ts` and the linter lints the emitted files. Use `tsc --noEmit`.
58
- - Do NOT trust `vite-react`'s scaffolded `lint` script. It ships a bare `"lint": "oxlint"`, which exits 0 on warnings. `[scripts.override]` forces `--max-warnings 0` over it, and the golden `.oxlintrc.json` overwrites the scaffold's bare one.
59
- - Do NOT rely on bare-folder exclude globs like `exclude: ["e2e"]`. Use `"e2e/**/*"`.
60
- - Do NOT ship Vitest with no-tests-fail. Fresh scaffolds have zero tests. Use `passWithNoTests: true` or equivalent until the project has at least one test.
61
- - Do NOT put Playwright `trace` at the top level of `defineConfig`. It lives under `use`.
62
- - Do NOT skip `skipLibCheck: true` in Vite + React tsconfig. `@testing-library/jest-dom` and Vite's `module-runner` produce type conflicts otherwise.
63
- - Do NOT run `bunx shadcn@latest init` over a pre-existing `src/styles/global.css`. Shadcn silently overwrites it. Back up first or run shadcn init before writing custom global styles. Use flags `-t <template> -b <base> -p <primary> -y` to avoid interactive prompts.
109
+ | Do NOT | Why and what to do |
110
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
111
+ | Use `exclude: ["e2e"]` | Bare-folder globs miss. Use `"e2e/**/*"`. |
112
+ | Put Playwright `trace` at the top of the config | It lives under `use`. |
113
+ | Ship Vitest failing on no tests | Fresh scaffolds have none. Use `passWithNoTests: true` until one exists. |
114
+ | Skip `skipLibCheck: true` in Vite + React | `@testing-library/jest-dom` and Vite's `module-runner` conflict otherwise. |
115
+ | Use `tsc -b` in a Vite project | Composite mode emits `.js` next to `.ts` and the linter lints it. Use `tsc --noEmit`. |
116
+ | Trust `vite-react`'s scaffolded `lint` script | Its bare `oxlint` exits 0 on warnings. `[scripts.override]` forces `--max-warnings 0` over it. |
117
+ | Run `bunx shadcn@latest init` over `global.css` | It silently overwrites `src/styles/global.css`. Back up first, and pass `-t -b -p -y` to skip prompts. |
64
118
 
65
119
  ## Tool pairing
66
120
 
67
- - Unit tests: Vitest with jsdom, globals on, setup file at `src/test/setup.ts`, `@testing-library/react`, `@testing-library/user-event`.
68
- - E2E: Playwright in `e2e/`. Chromium-only for Chrome extensions, all browsers for web apps.
69
- - Tailwind: v4 via `@tailwindcss/vite`. Never the v3 integration.
70
- - Lint: oxlint at an exact pin, since its JS plugin host is alpha and a minor release could break `check-file` or `simple-import-sort`. Astro keeps ESLint, which its own reference covers.
121
+ - Unit tests use Vitest with jsdom, globals on, `src/test/setup.ts`, `@testing-library/react`, and `@testing-library/user-event`.
122
+ - E2E uses Playwright in `e2e/`, Chromium only for Chrome extensions and all browsers for web apps.
123
+ - Tailwind is v4 through `@tailwindcss/vite`, never the v3 integration.
124
+ - Lint is oxlint at an exact pin, since its JS plugin host is alpha and a minor release could break `check-file` or `simple-import-sort`. Astro keeps ESLint, which its own reference covers.
71
125
 
72
126
  ## oxlint
73
127
 
74
- - Set `lint` and `lint:fix` through `[scripts.override]`, so an already-synced target stops running ESLint on its next sync.
75
- - Delete a leftover `eslint.config.js` or `eslint.config.mjs`, and `bun remove` the ESLint packages this stack stopped shipping, by hand after that sync. Sync deletes nothing a stack stopped shipping.
76
- - Keep import sorting on `simple-import-sort` rather than oxfmt's built-in sort, which orders imports differently and would rewrite every import block.
128
+ - Set `lint` and `lint:fix` through `[scripts.override]`, so a synced target stops running ESLint on its next sync.
129
+ - After that sync, delete a leftover `eslint.config.js` or `eslint.config.mjs` and `bun remove` the ESLint packages by hand. Sync deletes nothing a stack stopped shipping.
130
+ - Keep import sorting on `simple-import-sort`, because oxfmt's built-in sort orders differently and would rewrite every import block.
77
131
 
78
132
  ## lint-staged (extend)
79
133
 
80
- - Add `**/*.{js,jsx,ts,tsx}` glob running `oxlint --fix --max-warnings 0`, and `oxfmt --no-error-on-unmatched-pattern`.
81
- - Extend the oxfmt glob to include `css`: `**/*.{json,css,md}`.
134
+ - Add `**/*.{js,jsx,ts,tsx}` running `oxlint --fix --max-warnings 0` and `oxfmt --no-error-on-unmatched-pattern`.
135
+ - Extend the oxfmt glob to include `css`, giving `**/*.{json,css,md}`.
82
136
 
83
137
  ## CI docs (extend)
84
138
 
85
139
  Extend `canon/context/development/ci.md` so the `## Checks` table reflects the web jobs. <!-- audit-ignore-citations: canon/context/development/ci.md -->
86
140
 
87
- Append rows:
88
-
89
- | Typecheck | `bun run typecheck` | framework-specific typecheck passes |
90
- | Lint | `bun run lint` | oxlint passes with zero warnings |
91
- | Tests | `bun run test:coverage` | Vitest passes with coverage thresholds |
92
- | Build | `bun run build` | Production build succeeds |
93
- | E2E | `bun run test:e2e` | Playwright passes against the built preview |
141
+ | Check | Command | What it asserts |
142
+ | --------- | ----------------------- | ------------------------------------------- |
143
+ | Typecheck | `bun run typecheck` | framework-specific typecheck passes |
144
+ | Lint | `bun run lint` | oxlint passes with zero warnings |
145
+ | Tests | `bun run test:coverage` | Vitest passes with coverage thresholds |
146
+ | Build | `bun run build` | Production build succeeds |
147
+ | E2E | `bun run test:e2e` | Playwright passes against the built preview |
94
148
 
95
149
  Under `## Running CI locally`, document that `bun run check:full` runs verify plus `test:e2e`.
96
150
 
97
151
  ## Development docs (extend)
98
152
 
99
- Extend the `development` context entry under `canon/context/` so the `## Scripts` table lists every web script. Stack adapters add their `dev`, `build`, `preview`, `typecheck` rows.
100
-
101
- Append rows:
153
+ Extend the `development` context entry under `canon/context/` so the `## Scripts` table lists every web script. Stack adapters add their `dev`, `build`, `preview`, and `typecheck` rows.
102
154
 
103
- | `bun run lint` | Run oxlint with zero warnings allowed. |
104
- | `bun run lint:fix` | Auto-fix oxlint issues. |
105
- | `bun run test` | Run Vitest in watch mode. |
106
- | `bun run test:run` | Run Vitest once with verbose reporter. |
107
- | `bun run test:coverage` | Run Vitest with coverage. |
108
- | `bun run test:e2e` | Run Playwright E2E tests. |
109
- | `bun run test:e2e:changed` | Run Playwright E2E tests for specs the import graph reaches from the current diff. |
110
- | `bun run screenshot` | Build, preview, then capture screenshots. |
111
- | `bun run readme-screenshot` | Build, preview, then capture the README frame into `assets/evidence/readme/`. |
112
- | `bun run smoke:prod` | Capture against `SCREENSHOT_BASE_URL`, requiring it set and failing on any console error. |
155
+ | Command | Purpose |
156
+ | --------------------------- | --------------------------------------- |
157
+ | `bun run lint` | Run oxlint, no warnings allowed. |
158
+ | `bun run lint:fix` | Auto-fix oxlint issues. |
159
+ | `bun run test` | Run Vitest in watch mode. |
160
+ | `bun run test:run` | Run Vitest once, verbose. |
161
+ | `bun run test:coverage` | Run Vitest with coverage. |
162
+ | `bun run test:e2e` | Run Playwright E2E tests. |
163
+ | `bun run test:e2e:changed` | Run specs the diff's import graph hits. |
164
+ | `bun run screenshot` | Build, preview, capture screenshots. |
165
+ | `bun run readme-screenshot` | Capture the README frame. |
166
+ | `bun run smoke:prod` | Capture against a required base URL. |
113
167
 
114
- `canon tooling verify <stack>` is the only automated caller of `bun run screenshot`, running it for any stack whose `package.json` declares the script and asserting that PNG files land under `screenshots/`. It counts them with a recursive find carrying no depth limit, so the section folders the seed writes satisfy the assertion without a change to it. Do not flatten the layout to protect that check. No ship chain captures a screenshot, so the output path the seed writes is a contract that one verifier reads rather than a default a ship step depends on.
168
+ ### Screenshot contract
115
169
 
116
- `governance/rules/ui/surface-capture.md` is what asks a session to run the capture after a surface changes. It now fires on every component file too, reversing the route-and-page-only scope this reference once described, after a shipped batch of components carried no capture and a defect went unseen. The rule body states its own exemption for a component the production build strips out, so a reader chasing that case reads it there rather than here.
170
+ `canon tooling verify <stack>` is the only automated caller of `bun run screenshot`. It runs the script for any stack whose `package.json` declares it and asserts that PNG files land under `screenshots/`.
117
171
 
118
- The sweep under `screenshots/` is ignored again, and only a flagged case's `evidence/` output tracks in git, so the first capture a scaffolded target runs after this change is the baseline it commits there. A case naming sections commits a folder per section under that baseline rather than one file per theme, which is what lets a pull request comparison land on the part of the page that moved instead of on one image of the whole of it. The count is the section list times the themes, so a list is worth sizing against how many frames each later change has to regenerate and a reviewer has to read.
172
+ - The count is a recursive find with no depth limit, so the seed's section folders satisfy it. Do not flatten the layout to protect that check.
173
+ - `governance/rules/ui/surface-capture.md` asks a session to run the capture after a surface changes, component files included, with an exemption for a component the production build strips out.
174
+ - The sweep under `screenshots/` is ignored and only a flagged case's `evidence/` output tracks, so a target's first capture is the baseline it commits.
175
+ - A case naming sections commits a folder per section, so size the list against the frames each later change regenerates.
119
176
 
120
177
  ## Gitignore (extend)
121
178
 
122
- `[gitignore]` groups this stack edits, restated here per the manifest-to-reference symmetry:
179
+ These `[gitignore]` groups mirror the manifest.
123
180
 
124
181
  - `"# Playwright" = ["test-results/", "playwright-report/", "blob-report/", "playwright/.cache/"]`
125
182
  - `"# Screenshots" = ["screenshots/"]`
126
183
 
127
184
  ## Verify script
128
185
 
129
- The web layer's `scripts/verify.json` requires `typecheck`, `lint`, `test:run`, and `build`, and base's `scripts/verify.ts` runs them in its fixed order: typecheck, lint, format, shell where the `shell` stack declares it, unit tests, build, markdown bans, then the card exclusion check where one is installed. astro, vite-react, and nextjs inherit the list, since the nearest stack's copy wins. An adapter whose phases differ ships its own `verify.json` rather than a script.
186
+ The web layer's `scripts/verify.json` requires `typecheck`, `lint`, `test:run`, and `build`. Base's `scripts/verify.ts` runs them in its fixed order, being typecheck, lint, format, shell where the `shell` stack declares it, unit tests, build, markdown bans, then the card exclusion check where installed.
130
187
 
131
- The card stage runs `scripts/check-card-exclusion.sh` whenever that file is installed, which keeps the social card route's build exclusion on the default path rather than behind a flag. It tests for the script rather than the stack, so a project with no card route runs one less stage with no override, and the stack shipping a card route owns what the check reads, being the build the stage before it produced. The astro reference carries the route itself.
188
+ - Astro, vite-react, and nextjs inherit the list, since the nearest stack's copy wins. An adapter whose phases differ ships its own `verify.json` rather than a script.
189
+ - The card stage runs `scripts/check-card-exclusion.sh` whenever that file is installed, keeping the social card route's build exclusion on the default path rather than behind a flag.
190
+ - It tests for the script rather than the stack, so a project with no card route runs one less stage with no override.
191
+ - The stack shipping a card route owns what the check reads, being the build the stage before it produced. The astro reference carries the route itself.