@erclx/canon 5.68.0 → 5.69.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 (92) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/claude/.claude-plugin/plugin.json +1 -1
  4. package/claude/skills/app-drive/REQUIREMENT.md +5 -5
  5. package/claude/skills/app-drive/SKILL.md +14 -4
  6. package/claude/skills/app-drive/references/drive-record.md +12 -1
  7. package/claude/skills/canon-cli/SKILL.md +2 -2
  8. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
  9. package/claude/skills/canon-feedback/SKILL.md +1 -1
  10. package/claude/skills/design-identity/REQUIREMENT.md +4 -4
  11. package/claude/skills/design-identity/SKILL.md +7 -7
  12. package/claude/skills/{pick-by-eye → design-pick}/REQUIREMENT.md +6 -6
  13. package/claude/skills/{pick-by-eye → design-pick}/SKILL.md +18 -10
  14. package/claude/skills/{pick-by-eye → design-pick}/references/board.md +17 -11
  15. package/claude/skills/design-pick/references/design-config.md +33 -0
  16. package/claude/skills/design-record/REQUIREMENT.md +2 -2
  17. package/claude/skills/design-record/SKILL.md +1 -1
  18. package/claude/skills/design-taste/REQUIREMENT.md +2 -2
  19. package/claude/skills/design-taste/SKILL.md +3 -3
  20. package/claude/skills/design-taste/references/preflight.md +1 -1
  21. package/claude/skills/git-issue/REQUIREMENT.md +1 -1
  22. package/claude/skills/git-issue/references/issue.md +1 -1
  23. package/claude/skills/plan-feature/SKILL.md +1 -1
  24. package/claude/skills/plan-feature/references/plan.md +21 -6
  25. package/claude/skills/plan-groundwork/SKILL.md +1 -1
  26. package/claude/skills/pr-evidence/references/evidence.md +2 -2
  27. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +3 -3
  28. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +1 -1
  29. package/claude/skills/role-orchestrator/references/orchestrator-planning-wait.md +24 -0
  30. package/claude/skills/role-planner/SKILL.md +4 -2
  31. package/claude/skills/role-worker/SKILL.md +5 -6
  32. package/claude/skills/task-board/references/tasks.md +1 -1
  33. package/claude/skills/task-complete/references/receipt-sweep.md +1 -1
  34. package/claude/skills/{ux-audit → ui-audit}/REQUIREMENT.md +3 -3
  35. package/claude/skills/{ux-audit → ui-audit}/SKILL.md +7 -7
  36. package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
  37. package/claude/skills/ui-craft/SKILL.md +1 -1
  38. package/claude/skills/{ui-record → ui-film}/REQUIREMENT.md +2 -2
  39. package/claude/skills/{ui-record → ui-film}/SKILL.md +2 -2
  40. package/claude/skills/{ui-record → ui-film}/references/config.md +3 -3
  41. package/claude/skills/{ui-record → ui-film}/references/handoff.md +4 -4
  42. package/claude/skills/{ui-record → ui-film}/references/pr-clip.md +5 -5
  43. package/claude/skills/{ui-record → ui-film}/references/take-craft.md +1 -1
  44. package/claude/skills/{ux-measure → ui-measure}/REQUIREMENT.md +3 -3
  45. package/claude/skills/{ux-measure → ui-measure}/SKILL.md +6 -6
  46. package/docs/agents/audits.md +1 -1
  47. package/docs/agents/migrate.md +13 -13
  48. package/docs/target/migrations.md +12 -10
  49. package/docs/target/projects.md +1 -1
  50. package/docs/target/sync.md +1 -1
  51. package/docs/workflow/skill-map-on-request.md +1 -2
  52. package/docs/workflow/skill-map.md +9 -10
  53. package/docs/workflow/visual-design-workflow.md +6 -1
  54. package/governance/ledger/claude/background.md +26 -0
  55. package/governance/rules/claude/background.md +19 -0
  56. package/governance/stacks/base.toml +1 -0
  57. package/package.json +4 -4
  58. package/scripts/ci/install-playwright-deps.sh +52 -0
  59. package/src/claude/cases/authoring.ts +2 -2
  60. package/src/claude/cases/misc.ts +1 -1
  61. package/src/claude/cases/setup.ts +0 -5
  62. package/src/claude/cases/workflow.ts +6 -6
  63. package/src/commands/fixtures/gh.sh +64 -0
  64. package/src/commands/gov.ts +33 -3
  65. package/src/commands/migrate.ts +12 -2
  66. package/src/commands/tasks.ts +183 -0
  67. package/src/gate/stages.ts +2 -2
  68. package/src/gov/audit.ts +48 -11
  69. package/src/gov/detect.ts +1 -1
  70. package/src/markdown/scope.ts +9 -14
  71. package/src/migrate/record-layout.ts +5 -3
  72. package/src/migrate/skill-names.ts +21 -4
  73. package/src/records/validate.ts +1 -0
  74. package/src/sandbox/headless/dispatch-shim.sh +15 -0
  75. package/src/sandbox/headless/dispatch.ts +12 -14
  76. package/src/sandbox/stub-gh.sh +26 -0
  77. package/src/sandbox/stub-remote.ts +8 -29
  78. package/src/sync/engine.ts +1 -1
  79. package/src/take/camera.ts +2 -2
  80. package/src/take/film.ts +20 -2
  81. package/src/take/pointer.ts +2 -1
  82. package/src/take/stepper.ts +19 -5
  83. package/src/take/zoom/NOTICE.md +1 -1
  84. package/src/tasks/launch-wait.ts +160 -0
  85. package/tooling/claude/reference.md +1 -1
  86. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +0 -34
  87. package/claude/skills/canon-feedback-triage/SKILL.md +0 -98
  88. package/claude/skills/plan-ready/REQUIREMENT.md +0 -35
  89. package/claude/skills/plan-ready/SKILL.md +0 -136
  90. package/claude/skills/plan-ready/references/assembly.md +0 -75
  91. package/claude/skills/plan-ready/references/ready.md +0 -128
  92. /package/claude/skills/{pick-by-eye → design-pick}/references/live-arms.md +0 -0
@@ -56,7 +56,7 @@ Keep the `## Scripts` table in `canon/context/development/local-loop.md` current
56
56
  Before the first feature session:
57
57
 
58
58
  1. Fill `canon/REQUIREMENTS.md` with the problem, goals, and non-goals. The seed's 600-word cap gates under `canon context audit`.
59
- 2. For a UI project, write `canon/DESIGN.md` from the seed and name the stylesheet in its lead, or work the direction out with `canon:pick-by-eye`
59
+ 2. For a UI project, write `canon/DESIGN.md` from the seed and name the stylesheet in its lead, or work the direction out with `canon:design-pick`
60
60
  3. Optionally invoke `canon:draw-figure` for an architecture view. The Mermaid CLI downloads on first use.
61
61
  4. Start the feature loop in [AI workflow](../workflow/ai-workflow.md)
62
62
 
@@ -48,7 +48,7 @@ Add `--json` for a machine-readable report and `--exit-code` to fail CI when a t
48
48
  - Tooling dependencies: a dependency on a later major than the stack pins reads as `ahead`, and sync leaves it
49
49
  - Index regeneration: `canon indexes regen`
50
50
 
51
- After `canon gov sync` applies, it lists each rule it copied with its globs and how many project files match. A `ui/` rule adds a pointer to `/canon:ux-audit`. `--json` writes `{ "coverage": [{ "rule", "paths", "files", "ui" }] }` to stdout, empty when nothing was copied, with `files` null outside git. The report never changes the exit code.
51
+ After `canon gov sync` applies, it lists each rule it copied with its globs and how many project files match. A `ui/` rule adds a pointer to `/canon:ui-audit`. `--json` writes `{ "coverage": [{ "rule", "paths", "files", "ui" }] }` to stdout, empty when nothing was copied, with `files` null outside git. The report never changes the exit code.
52
52
 
53
53
  Try a scenario from [sandbox](../../canon/context/development/sandbox.md) before syncing a real project.
54
54
 
@@ -15,11 +15,10 @@ These skills serve a request rather than a moment. With the [skill map](skill-ma
15
15
  | `canon:create-rule` | A project rule the toolkit does not ship |
16
16
  | `canon:create-skill` | A new `SKILL.md` |
17
17
  | `canon:write-doc` | A new `docs/` page, context entry, or `README.md` |
18
- | `canon:plan-ready` | Finished files a worker should copy |
19
18
  | `canon:draw-figure` | A figure or architecture view inside a doc |
20
19
  | `canon:ci-workflow` | A GitHub Actions workflow file |
21
20
  | `canon:design-identity` | A logo mark and social card |
22
- | `canon:ui-record` | Real footage of the app for a video |
21
+ | `canon:ui-film` | Real footage of the app for a video |
23
22
 
24
23
  ## Answer a question at any point
25
24
 
@@ -24,7 +24,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
24
24
  | `canon:backlog-triage` | The backlog needs pruning |
25
25
  | `canon:plan-groundwork` | The state is unmeasured and approaches compete |
26
26
  | `canon:decision-escalate` | Open decisions turn on your preference |
27
- | `canon:pick-by-eye` | The call is taste, wanting candidates side by side |
27
+ | `canon:design-pick` | The call is taste, wanting candidates side by side |
28
28
  | `canon:task-board` | A decided item needs a file on the board |
29
29
  | `canon:plan-feature` | The approach is settled and needs a plan |
30
30
  | `canon:codebase-layout` | A change adds a file and needs a folder |
@@ -53,8 +53,8 @@ Groups run in the order a project meets them. Every name `canon claude skills li
53
53
  | Skill | When to use |
54
54
  | ----------------------- | ---------------------------------------------- |
55
55
  | `canon:document-health` | Documents must answer for length and staleness |
56
- | `canon:ux-audit` | To read UI source for missing states |
57
- | `canon:ux-measure` | To measure paint, processor, and layout cost |
56
+ | `canon:ui-audit` | To read UI source for missing states |
57
+ | `canon:ui-measure` | To measure paint, processor, and layout cost |
58
58
  | `canon:app-drive` | To drive a built app with the operator |
59
59
 
60
60
  ## Ship it
@@ -100,10 +100,9 @@ Groups run in the order a project meets them. Every name `canon claude skills li
100
100
 
101
101
  ## Keep the project current with the toolkit
102
102
 
103
- | Skill | When to use |
104
- | ----------------------------- | ------------------------------------------------------- |
105
- | `canon:target-check` | In a target, to compare each domain with the toolkit |
106
- | `canon:target-update` | After a release, to catch a target up to the toolkit |
107
- | `canon:document-repair` | Rewrite a failing docs folder to the limits and gate it |
108
- | `canon:canon-feedback` | The toolkit is broken, missing, or off |
109
- | `canon:canon-feedback-triage` | In the toolkit repo, to file feedback issues |
103
+ | Skill | When to use |
104
+ | ----------------------- | ------------------------------------------------------- |
105
+ | `canon:target-check` | In a target, to compare each domain with the toolkit |
106
+ | `canon:target-update` | After a release, to catch a target up to the toolkit |
107
+ | `canon:document-repair` | Rewrite a failing docs folder to the limits and gate it |
108
+ | `canon:canon-feedback` | The toolkit is broken, missing, or off |
@@ -72,7 +72,12 @@ A close posts as `## UI review closed`. The reviewers lift the draft mark on the
72
72
 
73
73
  ## Design authoring
74
74
 
75
- Most projects author design in the loop above and need nothing more. Start with `canon:pick-by-eye`, which drafts candidates as frames on a [Frameboard](https://www.npmjs.com/package/frameboard) board and takes your pick, then record the pick with `canon:design-record`. `canon:design-identity` drafts a logo mark and social card the same way.
75
+ Most projects author design in the loop above and need nothing more. Start with `canon:design-pick`, which drafts candidates as frames on a [Frameboard](https://www.npmjs.com/package/frameboard) board and takes your pick, then record the pick with `canon:design-record`. `canon:design-identity` drafts a logo mark and social card the same way.
76
+
77
+ A pick round drafts on `.canon/board/` unless the project names another board in `canon/config/design.toml`.
78
+
79
+ - Set `board` when your design sync reads another Frameboard board, so the picked frame lands without a redraw.
80
+ - Leave `project` unset to keep rounds in their own `picks` project on that board.
76
81
 
77
82
  A graphical tool earns its place when design is the product, a designer owns it, and it changes more often than code. Then the tool is the source of truth, and `canon/DESIGN.md` carries a top note naming the upstream project.
78
83
 
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: Background rule ledger
3
+ description: Decisions behind governance/rules/claude/background.md, one row per bullet
4
+ ---
5
+
6
+ # Background rule ledger
7
+
8
+ The rule is a core rule, so every row is a house choice rather than a citation. Each names what goes wrong without its bullet and the option the bullet beat, with the date it was decided.
9
+
10
+ ## Slow commands
11
+
12
+ | Bullet | Source | Read |
13
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
14
+ | Background past about a minute | House choice: a foreground run holds the conversation, so the operator waits on a check they could have talked over. It beat a warning hook, which would only warn and still let the run block | 2026-10-26 |
15
+ | Name the slow kinds | House choice: a minute is a guess made before the run, and with no cases to match a session reads a full check as quick. It beat the threshold alone | 2026-10-26 |
16
+ | Keep a seconds command foreground | House choice: each wake costs a turn, so backgrounding a quick command spends two turns where one did. It beat backgrounding every command | 2026-10-26 |
17
+ | End the turn on one status line | House choice: a turn that stops silently leaves the operator unable to tell a wait from a stall. It beat ending on the tool call alone | 2026-10-26 |
18
+ | Keep working beside a server | House choice: a dev server or a watch never exits, so a session awaiting its wake waits forever. It beat treating every background command as awaited | 2026-10-26 |
19
+
20
+ ## Waking
21
+
22
+ | Bullet | Source | Read |
23
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
24
+ | Act on the wake, never a guess | House choice: a session guessing a command is done reads a partial output as the result. It beat checking the output file on a hunch | 2026-10-26 |
25
+ | No sleep, poll, or scheduled wait | House choice: a foreground sleep or poll holds the conversation the rule freed, and a scheduled prompt spends a turn on every check. It beat all three as ways to wait on one command | 2026-10-26 |
26
+ | Keep a skill's own wait | House choice: a skill owns the verb and the timeout it waits on, such as a review-state wait, and one rule-wide wait would override it. It beat a single wait for every caller | 2026-10-26 |
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Run a command that may take more than about a minute in the background and resume on the harness wake, so the operator keeps the conversation
3
+ ---
4
+
5
+ # Background standards
6
+
7
+ ## Slow commands
8
+
9
+ - Start a command that may run past about a minute with `run_in_background: true`, since a foreground run holds the conversation. Revisit when a foreground Bash call stops blocking the turn.
10
+ - Treat a full check, a sandbox run, a capture or render, a build, and a wait on another session as slow.
11
+ - Keep a command expected to finish in seconds in the foreground, since each wake costs a turn.
12
+ - End the turn on one line naming what runs, when the next step needs that command's result.
13
+ - Keep working beside a server or a watch the session never waits on, since it never exits and its wake never comes.
14
+
15
+ ## Waking
16
+
17
+ - Act on the wake the harness sends when an awaited command exits, never on a guess about when it is done. Revisit when a background Bash command stops waking the session at exit.
18
+ - Never wait with a foreground `sleep`, a foreground poll loop, or a scheduled prompt for one wait. The background run and its wake replace all three.
19
+ - Keep a skill's own wait as the skill states it, since the skill owns the verb and the timeout it waits on.
@@ -12,4 +12,5 @@ rules = [
12
12
  "tasks",
13
13
  "plan",
14
14
  "markdown",
15
+ "background",
15
16
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.68.0",
4
+ "version": "5.69.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -25,9 +25,9 @@
25
25
  "access": "public"
26
26
  },
27
27
  "scripts": {
28
- "format": "oxfmt . && prettier --write --log-level warn 'web/**/*.astro' && shfmt --write --indent 2 scripts/ tooling/ claude/",
29
- "check:format": "oxfmt --check . && prettier --check --log-level warn 'web/**/*.astro' && shfmt --diff --indent 2 scripts/ tooling/ claude/",
30
- "check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
28
+ "format": "oxfmt . && prettier --write --log-level warn 'web/**/*.astro' && shfmt --write --indent 2 scripts/ tooling/ claude/ src/",
29
+ "check:format": "oxfmt --check . && prettier --check --log-level warn 'web/**/*.astro' && shfmt --diff --indent 2 scripts/ tooling/ claude/ src/",
30
+ "check:shell": "find scripts tooling claude src .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
31
31
  "check:lint": "oxlint --disable-nested-config --max-warnings 0",
32
32
  "check:types": "tsc --noEmit",
33
33
  "check:install": "bun scripts/core/install-check.ts",
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # Installs the Playwright system packages for the named browsers, killing an
3
+ # attempt that outlives its bound and retrying it. A degraded apt mirror hangs
4
+ # one connection rather than failing it, so a step timeout alone turns the
5
+ # hang into a red check and never recovers it, while a fresh attempt usually
6
+ # lands on a healthy backend.
7
+ #
8
+ # Usage: install-playwright-deps.sh [--package <spec>] <browser>...
9
+ # Exits 0 on the first success, 2 when no browser is named, and the last
10
+ # attempt's code when every attempt fails.
11
+ set -e
12
+
13
+ package=playwright
14
+ if [ "$1" = "--package" ]; then
15
+ package=$2
16
+ shift 2
17
+ fi
18
+
19
+ if [ $# -eq 0 ]; then
20
+ echo "Usage: install-playwright-deps.sh [--package <spec>] <browser>..." >&2
21
+ exit 2
22
+ fi
23
+
24
+ attempts=3
25
+ bound=${INSTALL_ATTEMPT_SECONDS:-150}
26
+
27
+ # The runner image sets these, yet a stalled log shows gaps of minutes, so
28
+ # print what apt actually reads to settle whether they took effect
29
+ apt-config dump | grep -E '^Acquire::(Retries|http::Timeout) ' || true
30
+
31
+ status=0
32
+ for attempt in $(seq 1 "$attempts"); do
33
+ if [ "$attempt" -gt 1 ]; then
34
+ # timeout runs as the runner user, and Linux drops its signal to the root
35
+ # apt-get playwright spawns through sudo. sudo relays it only to its own
36
+ # child shell, so a root apt-get or dpkg can outlive the kill and hold the
37
+ # lock the next attempt needs.
38
+ sudo pkill -KILL -x 'apt-get|dpkg' || true
39
+ # A kill during unpack leaves dpkg interrupted, which refuses the next
40
+ # install. Its own failure must not end the loop.
41
+ sudo dpkg --configure -a || echo "dpkg --configure -a failed before attempt ${attempt}" >&2
42
+ fi
43
+
44
+ status=0
45
+ timeout --kill-after=10 "$bound" bunx "$package" install-deps "$@" || status=$?
46
+ if [ "$status" -eq 0 ]; then
47
+ exit 0
48
+ fi
49
+ echo "attempt ${attempt}/${attempts} failed with exit ${status}" >&2
50
+ done
51
+
52
+ exit "$status"
@@ -26,8 +26,8 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
26
26
  },
27
27
  {
28
28
  prompt:
29
- 'I already wrote the finished skill files, package them as a ready folder with a plan and a task for a worker to copy.',
30
- expect: 'plan-ready',
29
+ 'I already wrote the finished wording for a small new reference file, put it in a plan so a worker lands it exactly as written.',
30
+ expect: 'plan-feature',
31
31
  },
32
32
  {
33
33
  prompt: 'This project has no README.md at all, write one from scratch.',
@@ -10,7 +10,7 @@ export const MISC_CASES: readonly SkillCase[] = [
10
10
  {
11
11
  prompt:
12
12
  'Show me a few different treatments for this callout so I can pick one by looking.',
13
- expect: 'pick-by-eye',
13
+ expect: 'design-pick',
14
14
  },
15
15
  {
16
16
  prompt:
@@ -54,11 +54,6 @@ export const SETUP_CASES: readonly SkillCase[] = [
54
54
  "Whatever the right toolkit command is, get this project's rules installed.",
55
55
  expect: 'target-setup',
56
56
  },
57
- {
58
- prompt:
59
- 'File the open feedback issues on the toolkit repo into an intake folder.',
60
- expect: 'canon-feedback-triage',
61
- },
62
57
  // This answers one fixed question across a fixed domain list and repairs
63
58
  // nothing, which is what separates it from the setup skill above.
64
59
  {
@@ -143,11 +143,11 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
143
143
  {
144
144
  prompt:
145
145
  'Look over the interface and tell me what feels unfinished or confusing.',
146
- expect: 'ux-audit',
146
+ expect: 'ui-audit',
147
147
  },
148
148
  {
149
149
  prompt: 'Tell me the paint and layout cost of this page right now.',
150
- expect: 'ux-measure',
150
+ expect: 'ui-measure',
151
151
  },
152
152
  {
153
153
  prompt:
@@ -162,12 +162,12 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
162
162
  {
163
163
  prompt:
164
164
  'I need real footage of the board import flow for the launch video. Film it from our app so HyperFrames can zoom in on the clicks.',
165
- expect: 'ui-record',
165
+ expect: 'ui-film',
166
166
  },
167
167
  {
168
168
  prompt:
169
169
  'Shoot the takes for the storyboard we wrote, one clip per beat of the app doing its thing.',
170
- expect: 'ui-record',
170
+ expect: 'ui-film',
171
171
  },
172
172
  {
173
173
  prompt:
@@ -216,10 +216,10 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
216
216
  },
217
217
  {
218
218
  prompt: 'Find the rough, unfinished-feeling spots in this interface.',
219
- expect: 'ux-audit',
219
+ expect: 'ui-audit',
220
220
  },
221
221
  {
222
222
  prompt: 'Tell me the render cost of this page in the browser.',
223
- expect: 'ux-measure',
223
+ expect: 'ui-measure',
224
224
  },
225
225
  ]
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # Fake gh for src/commands/pr.test.ts. Reads FAKE_GH_FX, FAKE_GH_COMMENTS, and FAKE_GH_API_LOG.
3
+ set -o pipefail
4
+ fx="$FAKE_GH_FX"
5
+ f=.
6
+ p=
7
+ for a in "$@"; do
8
+ [ "$p" = --jq ] && f=$a
9
+ p=$a
10
+ done
11
+ case "$*" in
12
+ *graphql* | "pr "* | "repo view"*) exit 1 ;;
13
+ api*pulls/7/files*)
14
+ echo "$*" >>"$fx/files-calls.log"
15
+ [ -f "$fx/files-fail" ] && exit 1
16
+ {
17
+ if [ -f "$fx/files.tsv" ]; then
18
+ cat "$fx/files.tsv"
19
+ else
20
+ git diff --name-status --no-renames main...HEAD | sed -e 's/^A/added/' -e 's/^M/modified/' -e 's/^D/removed/'
21
+ fi
22
+ } | jq -Rn '[inputs | select(length > 0) | split("\t") | {status: .[0], filename: .[1]}]' | jq -r "$f"
23
+ ;;
24
+ api*git/trees/*)
25
+ [ -f "$fx/base-tree.json" ] || exit 1
26
+ jq -r "$f" "$fx/base-tree.json"
27
+ ;;
28
+ api*contents/*)
29
+ a="$*"
30
+ p=${a##*contents/}
31
+ p=${p%%\?*}
32
+ r=${a##*ref=}
33
+ [ -f "$fx/img/$r-$(basename "$p")" ] && cat "$fx/img/$r-$(basename "$p")" || exit 1
34
+ ;;
35
+ api*compare/*)
36
+ if [ -f "$fx/merge-base" ]; then cat "$fx/merge-base"; else git merge-base main HEAD; fi
37
+ ;;
38
+ "api -X PUT repos/{owner}/{repo}/pulls/7/reviews/"*)
39
+ id=${4##*/}
40
+ echo "$id" >>"$fx/review-puts.log"
41
+ [ -f "$fx/review-put-fail-$id" ] && exit 1
42
+ printf '%s\n' "${@: -1}" >>"$fx/review-bodies.log"
43
+ echo "{}"
44
+ ;;
45
+ api*pulls/7/reviews*) [ -f "$fx/ui-reviews.json" ] && jq -r "$f" "$fx/ui-reviews.json" ;;
46
+ api*issues/7/comments*) jq '[.comments[] | {html_url: .url, body}]' "$FAKE_GH_COMMENTS" | jq -r "$f" ;;
47
+ "api -X PATCH repos/{owner}/{repo}/pulls/7 "*)
48
+ [ -f "$fx/body-fail" ] && exit 1
49
+ printf '%s' "${@: -1}" >"$fx/body-write.log"
50
+ echo "{}"
51
+ ;;
52
+ "api repos/{owner}/{repo}/pulls/7")
53
+ if [ -f "$fx/head" ]; then h=$(cat "$fx/head"); else h=$(git rev-parse HEAD); fi
54
+ b=
55
+ [ -f "$fx/pr-body" ] && b=$(cat "$fx/pr-body")
56
+ jq -n --arg h "$h" --arg b "$b" '{number:7,head:{ref:"feat/x",sha:$h},base:{ref:"main"},mergeable_state:"clean",body:$b}'
57
+ ;;
58
+ "api repos/{owner}/{repo}") echo "{\"full_name\":\"o/r\"}" ;;
59
+ api*)
60
+ printf '%s' "${@: -1}" >"$FAKE_GH_API_LOG"
61
+ echo "{}"
62
+ ;;
63
+ *) exit 1 ;;
64
+ esac
@@ -34,6 +34,7 @@ import {
34
34
  } from '@/gov/restated'
35
35
  import {
36
36
  auditExit,
37
+ CORE_FOLDERS,
37
38
  hasFinding,
38
39
  PRACTICE_FOLDERS,
39
40
  readRuleAudit,
@@ -62,6 +63,11 @@ import {
62
63
  readTestOrder,
63
64
  type TestOrderReport,
64
65
  } from '@/gov/test-order'
66
+ import {
67
+ type ChangedSet,
68
+ collectChangedFiles,
69
+ commandRunner,
70
+ } from '@/gate/sequencer'
65
71
  import { recordStamp, runDomainSync } from '@/sync/engine'
66
72
  import { detectRules } from '@/gov/detect'
67
73
  import { writeChainStamp } from '@/sync/stamp'
@@ -138,6 +144,7 @@ interface AuditOptions {
138
144
  readonly root?: string
139
145
  readonly json?: boolean
140
146
  readonly gate?: boolean
147
+ readonly touched?: boolean
141
148
  }
142
149
 
143
150
  /** What a reader does about each way the sweep produced no reading. */
@@ -613,6 +620,10 @@ export function register(program: Command): void {
613
620
  '--gate',
614
621
  'Set 2 only for a finding in a gated folder, which the push stage reads',
615
622
  )
623
+ .option(
624
+ '--touched',
625
+ 'Owe a ledger for each core rule the branch changed against origin/main',
626
+ )
616
627
  .addHelpText(
617
628
  'after',
618
629
  [
@@ -632,6 +643,9 @@ export function register(program: Command): void {
632
643
  ` asks a ledger of each rule under ${PRACTICE_FOLDERS.join(', ')}`,
633
644
  ' at governance/ledger/<folder>/<slug>.md, one table row per bullet',
634
645
  ' under a heading of the same name.',
646
+ ` A rule under ${CORE_FOLDERS.join(', ')} has its ledger checked where`,
647
+ ' one exists, and under --touched owes one once the branch changed it.',
648
+ ' With no baseline against main, --touched reads no rule as touched.',
635
649
  ' Any other tree reads .claude/rules/project/ alone and asks no ledger.',
636
650
  ' .claude/rules/canon/ is fixed at the toolkit source and never read',
637
651
  ' unless named. A named path resolves against --root and must sit',
@@ -654,6 +668,7 @@ export function register(program: Command): void {
654
668
  ' canon gov audit',
655
669
  ' canon gov audit .claude/rules/project/api.md',
656
670
  ' canon gov audit --json',
671
+ ' canon gov audit --gate --touched',
657
672
  '',
658
673
  ].join('\n'),
659
674
  )
@@ -672,7 +687,13 @@ async function runAudit(
672
687
  opts: AuditOptions,
673
688
  ): Promise<number> {
674
689
  const root = resolve(opts.root ?? process.cwd())
675
- const report = await readRuleAudit(root, paths)
690
+ const changed = opts.touched
691
+ ? await collectChangedFiles(commandRunner(root))
692
+ : undefined
693
+ // An unscoped set lists nothing, and reading it as every file would owe a
694
+ // ledger of every core rule on a branch that touched none of them.
695
+ const touched = changed?.scoped ? changed.files : []
696
+ const report = await readRuleAudit(root, paths, { touched })
676
697
  const emitJson = opts.json ?? false
677
698
 
678
699
  if (report.kind === 'unreadable') {
@@ -690,7 +711,7 @@ async function runAudit(
690
711
  return 1
691
712
  }
692
713
 
693
- reportAudit(report, root, paths.length > 0)
714
+ reportAudit(report, root, paths.length > 0, changed)
694
715
 
695
716
  if (emitJson) {
696
717
  process.stdout.write(
@@ -700,6 +721,9 @@ async function runAudit(
700
721
  limits: RULE_LIMITS,
701
722
  gated: RULE_GATED_FOLDERS,
702
723
  exempt: RULE_GATE_EXEMPT,
724
+ ...(changed === undefined
725
+ ? {}
726
+ : { touched: { scoped: changed.scoped } }),
703
727
  ...report,
704
728
  })}\n`,
705
729
  )
@@ -728,6 +752,7 @@ function reportAudit(
728
752
  report: Extract<RuleAuditReport, { kind: 'measured' }>,
729
753
  root: string,
730
754
  named: boolean,
755
+ changed: ChangedSet | undefined,
731
756
  ): void {
732
757
  intro('canon gov audit')
733
758
 
@@ -761,10 +786,15 @@ function reportAudit(
761
786
  }
762
787
 
763
788
  logStep('Ledgers')
789
+ if (changed !== undefined && !changed.scoped) {
790
+ logInfo(
791
+ 'no baseline against main, so --touched reads no core rule as touched',
792
+ )
793
+ }
764
794
  if (report.scope === 'target') {
765
795
  logInfo('not read, since ledgers live in the toolkit and never ship')
766
796
  } else if (ledgers.length === 0) {
767
- logInfo('every outside-practice rule has a ledger row per bullet')
797
+ logInfo('every rule owing a ledger has one, with a row per bullet')
768
798
  } else {
769
799
  for (const line of ledgers) logWarn(line)
770
800
  }
@@ -1309,6 +1309,13 @@ export function register(program: Command): void {
1309
1309
  'any ordinal evidence/ already carries. The dry run labels every',
1310
1310
  'derived destination, so check the split before passing --write.',
1311
1311
  '',
1312
+ 'Moves the design board to the folder Frameboard names:',
1313
+ ' canvas/ -> board/',
1314
+ '',
1315
+ 'Stop a frameboard serve on canvas/ before --write. A project holding',
1316
+ 'both folders gets a collision, and a design.toml board key naming',
1317
+ '.canon/canvas is left for you to update.',
1318
+ '',
1312
1319
  'A citation reaching into a moved folder or naming a moved file is',
1313
1320
  'repointed, live or archived. A bare mention of an emptied folder, such',
1314
1321
  'as review/evidence/ with no slug, matches no row and stays as written.',
@@ -1389,8 +1396,11 @@ export function register(program: Command): void {
1389
1396
  `It also carries ${Object.keys(SKILL_CITATION_MAP).length} namespaced-only rows for a skill renamed from a single word.`,
1390
1397
  'The seven draft- skills move onto verb-first names, such as',
1391
1398
  // The old spellings below are the rename itself. canon-keep-retired
1392
- 'draft-doc to write-doc and draft-and-pick to pick-by-eye, and a',
1393
- 'pointer left under each old name is never moved.',
1399
+ 'draft-doc to write-doc and draft-slides to make-deck, and a',
1400
+ 'pointer left under each old name is never moved. Four skills then',
1401
+ 'moved into the family their job belongs to, such as',
1402
+ // canon-keep-retired
1403
+ 'ux-audit to ui-audit and pick-by-eye to design-pick.',
1394
1404
  'It takes no scope. The skill folders it moves are authored in the',
1395
1405
  'toolkit and no target holds a copy of that catalog, so in a target it',
1396
1406
  'rewrites citations of a renamed skill and moves nothing.',