@erclx/canon 4.6.1 → 4.8.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 (145) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/canon-feedback-file/SKILL.md +3 -3
  3. package/claude/skills/canon-feedback-triage/SKILL.md +1 -1
  4. package/claude/skills/canon-rollout/SKILL.md +1 -1
  5. package/claude/skills/canon-screencast/SKILL.md +5 -5
  6. package/claude/skills/canon-slides-draft/SKILL.md +1 -1
  7. package/claude/skills/claude-address-review/SKILL.md +4 -4
  8. package/claude/skills/claude-autoship/SKILL.md +17 -17
  9. package/claude/skills/claude-design-extract/SKILL.md +2 -2
  10. package/claude/skills/claude-diagram/SKILL.md +13 -13
  11. package/claude/skills/claude-docs/SKILL.md +23 -23
  12. package/claude/skills/claude-feature/SKILL.md +6 -6
  13. package/claude/skills/claude-groundwork/SKILL.md +16 -16
  14. package/claude/skills/claude-intake/SKILL.md +8 -8
  15. package/claude/skills/claude-intake-answer/SKILL.md +1 -1
  16. package/claude/skills/claude-markdown-propose/REQUIREMENT.md +2 -2
  17. package/claude/skills/claude-markdown-propose/SKILL.md +8 -8
  18. package/claude/skills/claude-markdown-propose/references/format.md +3 -3
  19. package/claude/skills/claude-memory-capture/SKILL.md +13 -13
  20. package/claude/skills/claude-memory-review/SKILL.md +28 -28
  21. package/claude/skills/claude-memory-review/references/receipt-format.md +1 -1
  22. package/claude/skills/claude-orchestrate/SKILL.md +9 -9
  23. package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +1 -1
  24. package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +1 -1
  25. package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +1 -1
  26. package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +1 -1
  27. package/claude/skills/claude-orchestrate/scripts/poll.sh +1 -1
  28. package/claude/skills/claude-planner/REQUIREMENT.md +1 -1
  29. package/claude/skills/claude-planner/SKILL.md +6 -6
  30. package/claude/skills/claude-pr-review/SKILL.md +5 -5
  31. package/claude/skills/claude-review/SKILL.md +4 -4
  32. package/claude/skills/claude-seed-sync/SKILL.md +2 -2
  33. package/claude/skills/claude-tasks/SKILL.md +16 -16
  34. package/claude/skills/claude-teach/SKILL.md +9 -9
  35. package/claude/skills/claude-ui-test/SKILL.md +3 -3
  36. package/claude/skills/claude-ux-audit/SKILL.md +5 -5
  37. package/claude/skills/claude-ux-measure/SKILL.md +3 -3
  38. package/claude/skills/claude-worker/SKILL.md +2 -2
  39. package/claude/skills/claude-worktree/SKILL.md +3 -3
  40. package/claude/skills/git-issue/SKILL.md +4 -4
  41. package/claude/skills/git-pr/SKILL.md +6 -6
  42. package/claude/skills/git-ship/SKILL.md +3 -3
  43. package/claude/skills/git-split/REQUIREMENT.md +1 -1
  44. package/claude/skills/git-split/SKILL.md +20 -20
  45. package/claude/skills/migration-superseded/SKILL.md +4 -4
  46. package/claude/skills/session-map/SKILL.md +2 -2
  47. package/claude/skills/session-resume/SKILL.md +8 -8
  48. package/docs/agents/capture.md +17 -11
  49. package/docs/agents/commands.md +8 -5
  50. package/docs/agents/demo.md +2 -2
  51. package/docs/agents/driver.md +2 -2
  52. package/docs/agents/index.md +1 -1
  53. package/docs/agents/indexes.md +1 -1
  54. package/docs/agents/install-and-sync.md +1 -1
  55. package/docs/agents/intake.md +1 -1
  56. package/docs/agents/key-changes.md +1 -1
  57. package/docs/agents/records.md +12 -9
  58. package/docs/agents/restated.md +2 -2
  59. package/docs/agents/rule-citations.md +1 -1
  60. package/docs/agents/sandbox.md +1 -1
  61. package/docs/agents/state-scoped-risk.md +1 -1
  62. package/docs/agents/tasks.md +6 -6
  63. package/docs/agents/teach.md +4 -4
  64. package/docs/ai-workflow.md +26 -19
  65. package/docs/operating-model.md +3 -3
  66. package/docs/target-projects.md +22 -2
  67. package/docs/visual-design-workflow.md +1 -1
  68. package/governance/rules/claude/555-tasks.md +3 -3
  69. package/governance/rules/claude/556-groundwork.md +2 -2
  70. package/governance/rules/claude/557-intake.md +2 -2
  71. package/governance/rules/claude/558-plan.md +3 -3
  72. package/governance/rules/claude/559-memory.md +2 -2
  73. package/governance/rules/claude/560-diagrams.md +3 -3
  74. package/governance/rules/claude/561-teach.md +2 -2
  75. package/governance/rules/claude/562-session.md +2 -2
  76. package/governance/rules/core/035-tasks.md +2 -2
  77. package/governance/rules/core/045-memory.md +3 -3
  78. package/governance/rules/core/055-scratch.md +2 -1
  79. package/governance/rules/core/085-worktrees.md +1 -1
  80. package/package.json +1 -2
  81. package/scripts/core/check-ignore-parity.sh +24 -51
  82. package/scripts/core/install-check.sh +4 -4
  83. package/scripts/core/regen-hero.sh +6 -3
  84. package/scripts/core/snapshot.sh +1 -1
  85. package/scripts/manage-sandbox.sh +1 -1
  86. package/scripts/tooling/verify.sh +2 -2
  87. package/snippets/claude/decision-memo.md +1 -1
  88. package/snippets/claude/feature-recap.md +1 -1
  89. package/src/audits/catalog.ts +1 -1
  90. package/src/capture/render.ts +166 -0
  91. package/src/capture/sources.ts +42 -0
  92. package/src/capture/stamp.ts +52 -0
  93. package/src/claude/cases/all.ts +1 -1
  94. package/src/claude/seeds.ts +13 -2
  95. package/src/claude/skills-rank.ts +1 -1
  96. package/src/cli.ts +5 -5
  97. package/src/commands/capture.ts +52 -41
  98. package/src/commands/demo.ts +1 -1
  99. package/src/commands/design.ts +1 -1
  100. package/src/commands/feedback.ts +5 -5
  101. package/src/commands/gov.ts +17 -6
  102. package/src/commands/intake.ts +1 -1
  103. package/src/commands/migrate.ts +173 -2
  104. package/src/commands/pr.ts +1 -1
  105. package/src/commands/records.ts +1 -1
  106. package/src/commands/serve.ts +2 -2
  107. package/src/commands/slides.ts +1 -1
  108. package/src/commands/tasks.ts +5 -5
  109. package/src/commands/teach.ts +1 -1
  110. package/src/context/audit.ts +1 -1
  111. package/src/demo/compile.ts +1 -1
  112. package/src/demo/drive.ts +5 -8
  113. package/src/driver/drive.ts +7 -8
  114. package/src/gate/measures.ts +1 -1
  115. package/src/gate/stages.ts +1 -1
  116. package/src/gov/citations.ts +1 -1
  117. package/src/gov/restated.ts +3 -3
  118. package/src/inventory/walk.ts +3 -3
  119. package/src/migrate/apply.ts +67 -1
  120. package/src/migrate/records.ts +276 -0
  121. package/src/paths.ts +1 -1
  122. package/src/pr/paths.ts +1 -1
  123. package/src/record-root.ts +77 -27
  124. package/src/records/backup.ts +71 -17
  125. package/src/records/validate.ts +1 -1
  126. package/src/sync/layout.ts +11 -2
  127. package/src/tasks/answers.ts +1 -1
  128. package/src/tasks/archive.ts +20 -3
  129. package/standards/diagrams.md +4 -4
  130. package/standards/glossary.md +1 -1
  131. package/standards/groundwork.md +3 -3
  132. package/standards/index.md +2 -2
  133. package/standards/intake.md +3 -3
  134. package/standards/memory.md +2 -2
  135. package/standards/plan.md +3 -3
  136. package/standards/session.md +2 -2
  137. package/standards/standard.md +1 -1
  138. package/standards/tasks.md +9 -9
  139. package/standards/teach.md +2 -2
  140. package/standards/versioning.md +1 -1
  141. package/tooling/base/configs/.husky/post-merge +1 -1
  142. package/tooling/base/reference.md +1 -1
  143. package/tooling/claude/manifest.toml +13 -3
  144. package/tooling/claude/reference.md +28 -15
  145. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +8 -8
@@ -23,7 +23,7 @@ Run `git diff --cached --name-only 2>/dev/null` to check for staged files. If ou
23
23
 
24
24
  ## Sequence
25
25
 
26
- 1. Invoke `canon:claude-memory-capture` to route what this session learned to the context entries that own it and write the residue to `.claude/memory/`
26
+ 1. Invoke `canon:claude-memory-capture` to route what this session learned to the context entries that own it and write the residue to `.canon/memory/`
27
27
  2. Invoke `canon:claude-docs` to sync internal planning docs against session decisions, folding in the routed facts
28
28
  3. Invoke `canon:docs-sync` to sync public docs against changes since main
29
29
  4. Run `git add -A` to stage any files the sync skills wrote
@@ -46,8 +46,8 @@ Output up to four lines:
46
46
  ```plaintext
47
47
  ✅ Shipped
48
48
  <N facts routed to context entries>
49
- <N memories captured in .claude/memory/>
50
- <Memory proposal at .claude/review/memory/memory-review-<slug>.md>
49
+ <N memories captured in .canon/memory/>
50
+ <Memory proposal at .canon/review/memory/memory-review-<slug>.md>
51
51
  ```
52
52
 
53
53
  Omit the second line if nothing routed. Omit the third and fourth if `claude-memory-capture` wrote no memory file this session, since an empty pen means no scoped review and no proposal.
@@ -7,7 +7,7 @@ description: What branch splitting is for, the gaps it closes, and how it differ
7
7
 
8
8
  ## Gap
9
9
 
10
- Without this skill, a branch carrying unrelated commits ships as one pull request, which a reviewer reads by skimming because no single concern holds it together. Split by hand, commits get cherry-picked in an order that leaves a branch missing something it depends on. Merge order is the sharpest failure: merging the wrong branch first lands every other branch's content on main and leaves the remaining pull requests empty after rebase, with no signal that anything went wrong. Every body it writes stages under `.claude/.tmp/` and leaves through `gh`, neither of which the audit hook watches, so a banned character survives into a published pull request and has to be edited on the remote.
10
+ Without this skill, a branch carrying unrelated commits ships as one pull request, which a reviewer reads by skimming because no single concern holds it together. Split by hand, commits get cherry-picked in an order that leaves a branch missing something it depends on. Merge order is the sharpest failure: merging the wrong branch first lands every other branch's content on main and leaves the remaining pull requests empty after rebase, with no signal that anything went wrong. Every body it writes stages under `.canon/tmp/` and leaves through `gh`, neither of which the audit hook watches, so a banned character survives into a published pull request and has to be edited on the remote.
11
11
 
12
12
  ## Must
13
13
 
@@ -69,7 +69,7 @@ If Mode is Independent, append this line to the preview:
69
69
 
70
70
  After outputting the preview, execute the final commands immediately. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
71
71
 
72
- Before running them, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against every title and body. Each body stages under `.claude/.tmp/` and leaves through `gh`, so the hook sees neither and this scan is the only gate. Each resulting branch reaches the `pull_request` check the git-pr surface carries once its own pull request opens, but not before, so this scan is what catches a leak ahead of that.
72
+ Before running them, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against every title and body. Each body stages under `.canon/tmp/` and leaves through `gh`, so the hook sees neither and this scan is the only gate. Each resulting branch reaches the `pull_request` check the git-pr surface carries once its own pull request opens, but not before, so this scan is what catches a leak ahead of that.
73
73
 
74
74
  ### Final commands
75
75
 
@@ -80,25 +80,25 @@ For independent mode, base every branch on `main`:
80
80
  git branch -m <current_branch> <new_name>
81
81
 
82
82
  # Create, cherry-pick, push, and open PR for each secondary branch
83
- mkdir -p .claude/.tmp/pr-split
83
+ mkdir -p .canon/tmp/pr-split
84
84
  git switch -c <branch> main && git cherry-pick <sha> <sha> \
85
85
  && git push -u origin <branch> \
86
- && (cat <<'BODY' > .claude/.tmp/pr-split/<branch>.md
86
+ && (cat <<'BODY' > .canon/tmp/pr-split/<branch>.md
87
87
  <body following pr.md template, written from the cherry-picked commits>
88
88
  BODY
89
- ) && gh pr create --title "<title>" --body-file .claude/.tmp/pr-split/<branch>.md \
90
- && rm .claude/.tmp/pr-split/<branch>.md
89
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch>.md \
90
+ && rm .canon/tmp/pr-split/<branch>.md
91
91
 
92
92
  # Return to primary branch, push, and open its PR
93
93
  git checkout <new_name> && git push -u origin <new_name> \
94
- && (cat <<'BODY' > .claude/.tmp/pr-split/<new_name>.md
94
+ && (cat <<'BODY' > .canon/tmp/pr-split/<new_name>.md
95
95
  <body following pr.md template, written from the primary's commits>
96
96
  BODY
97
- ) && gh pr create --title "<title>" --body-file .claude/.tmp/pr-split/<new_name>.md \
98
- && rm .claude/.tmp/pr-split/<new_name>.md
97
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<new_name>.md \
98
+ && rm .canon/tmp/pr-split/<new_name>.md
99
99
 
100
100
  # Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
101
- rmdir .claude/.tmp/pr-split 2>/dev/null || true
101
+ rmdir .canon/tmp/pr-split 2>/dev/null || true
102
102
  ```
103
103
 
104
104
  For stacked mode, base each branch on the previous and cherry-pick only that group's commits:
@@ -107,36 +107,36 @@ For stacked mode, base each branch on the previous and cherry-pick only that gro
107
107
  # Rename current branch to reflect primary concern
108
108
  git branch -m <current_branch> <new_name>
109
109
 
110
- mkdir -p .claude/.tmp/pr-split
110
+ mkdir -p .canon/tmp/pr-split
111
111
 
112
112
  # Group 1: based on main
113
113
  git switch -c <branch-1> main && git cherry-pick <g1-sha> <g1-sha> \
114
114
  && git push -u origin <branch-1> \
115
- && (cat <<'BODY' > .claude/.tmp/pr-split/<branch-1>.md
115
+ && (cat <<'BODY' > .canon/tmp/pr-split/<branch-1>.md
116
116
  <body following pr.md template, written from the cherry-picked commits>
117
117
  BODY
118
- ) && gh pr create --title "<title>" --body-file .claude/.tmp/pr-split/<branch-1>.md \
119
- && rm .claude/.tmp/pr-split/<branch-1>.md
118
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch-1>.md \
119
+ && rm .canon/tmp/pr-split/<branch-1>.md
120
120
 
121
121
  # Group 2: based on <branch-1>, this group's commits only
122
122
  git checkout -b <branch-2> && git cherry-pick <g2-sha> <g2-sha> \
123
123
  && git push -u origin <branch-2> \
124
- && (cat <<'BODY' > .claude/.tmp/pr-split/<branch-2>.md
124
+ && (cat <<'BODY' > .canon/tmp/pr-split/<branch-2>.md
125
125
  <body following pr.md template, written from the cherry-picked commits>
126
126
  BODY
127
- ) && gh pr create --title "<title>" --body-file .claude/.tmp/pr-split/<branch-2>.md \
128
- && rm .claude/.tmp/pr-split/<branch-2>.md
127
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch-2>.md \
128
+ && rm .canon/tmp/pr-split/<branch-2>.md
129
129
 
130
130
  # Return to primary branch, push, and open its PR
131
131
  git checkout <new_name> && git push -u origin <new_name> \
132
- && (cat <<'BODY' > .claude/.tmp/pr-split/<new_name>.md
132
+ && (cat <<'BODY' > .canon/tmp/pr-split/<new_name>.md
133
133
  <body following pr.md template, written from the primary's commits>
134
134
  BODY
135
- ) && gh pr create --title "<title>" --body-file .claude/.tmp/pr-split/<new_name>.md \
136
- && rm .claude/.tmp/pr-split/<new_name>.md
135
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<new_name>.md \
136
+ && rm .canon/tmp/pr-split/<new_name>.md
137
137
 
138
138
  # Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
139
- rmdir .claude/.tmp/pr-split 2>/dev/null || true
139
+ rmdir .canon/tmp/pr-split 2>/dev/null || true
140
140
  ```
141
141
 
142
142
  ## After execution
@@ -35,7 +35,7 @@ No listing substitutes for the field. The pairing runs against the seed folder n
35
35
 
36
36
  Run `canon standards list --json` and match each entry's `replacedBy` against the `appliesTo` array the catalog declares. Resolve from the catalog rather than from the folder stem, so a seed folder the toolkit adds later resolves without an edit here.
37
37
 
38
- Compare on the folder rather than on the exact string. An `appliesTo` value matches when it equals `replacedBy` or begins with `replacedBy` followed by a slash. The report spells the folder `.claude/tasks` and the catalog spells it `.claude/tasks/`, and `memory` declares a filename pattern beneath its folder rather than the folder itself, so string equality matches nothing the catalog actually carries and sends every entry to a decline below.
38
+ Compare on the folder rather than on the exact string. An `appliesTo` value matches when it equals `replacedBy` or begins with `replacedBy` followed by a slash. The report spells the folder `.canon/tasks` and the catalog spells it `.canon/tasks/`, and `memory` declares a filename pattern beneath its folder rather than the folder itself, so string equality matches nothing the catalog actually carries and sends every entry to a decline below.
39
39
 
40
40
  ### Read the standard through the verb
41
41
 
@@ -88,12 +88,12 @@ Print one block per superseded entry, then the shared reminder. Omit empty group
88
88
  ```markdown
89
89
  ## Split
90
90
 
91
- `.claude/TASKS.md` → `.claude/tasks/`, shaped by `canon standards tasks`
91
+ `.claude/TASKS.md` → `.canon/tasks/`, shaped by `canon standards tasks`
92
92
 
93
93
  ## Proposed files
94
94
 
95
- - `.claude/tasks/vXX.Y-<slug>.md` ← the "<heading>" section
96
- - `.claude/tasks/vXX.Y-<slug>.md` ← the "<heading>" section
95
+ - `.canon/tasks/vXX.Y-<slug>.md` ← the "<heading>" section
96
+ - `.canon/tasks/vXX.Y-<slug>.md` ← the "<heading>" section
97
97
 
98
98
  Each carries `title` and `description` frontmatter, an `## Outcomes` heading, and a `## Findings` heading.
99
99
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-map
3
- description: Writes the session map, the pre-compaction handoff at `.claude/tasks/session-<slug>.md`, from any session whatever role it holds, running the skill-drift step the write procedure opens with. Use when asked to "write the handoff", "write the session map", "save the session before it compacts", "we are about to compact", "hand off to the next session", or "leave a note for whoever picks this up". Do NOT use to route session facts to a context entry or the memory folder, which is `claude-memory-capture` and writes a different artifact, and do NOT use to read a handoff back, which is `session-resume`.
3
+ description: Writes the session map, the pre-compaction handoff at `.canon/tasks/session-<slug>.md`, from any session whatever role it holds, running the skill-drift step the write procedure opens with. Use when asked to "write the handoff", "write the session map", "save the session before it compacts", "we are about to compact", "hand off to the next session", or "leave a note for whoever picks this up". Do NOT use to route session facts to a context entry or the memory folder, which is `claude-memory-capture` and writes a different artifact, and do NOT use to read a handoff back, which is `session-resume`.
4
4
  ---
5
5
 
6
6
  # Session map
@@ -56,7 +56,7 @@ The file sits at the main worktree root, which the file-editing tools refuse fro
56
56
  ## Output
57
57
 
58
58
  ```plaintext
59
- ✅ Session map written: .claude/tasks/session-<slug>.md
59
+ ✅ Session map written: .canon/tasks/session-<slug>.md
60
60
  <what the drift verb named, or the boundary it refused at>
61
61
  ```
62
62
 
@@ -7,14 +7,14 @@ description: Resumes a previous session by reading the handoff it left behind, t
7
7
 
8
8
  ## Step 1: read tracked work
9
9
 
10
- Resolve `.claude/plans/`, `.claude/memory/`, and `.claude/tasks/` at the main worktree root per Worktrees in `CLAUDE.md`.
10
+ Resolve `.canon/plans/`, `.canon/memory/`, and `.canon/tasks/` at the main worktree root per Worktrees in `CLAUDE.md`.
11
11
 
12
12
  Read these in parallel, skipping any that do not exist:
13
13
 
14
- - the newest `.claude/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `${CLAUDE_SKILL_DIR}/../../standards/session.md`. It leads the report rather than the reads.
15
- - `.claude/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the task list from it by dropping the `index`, `priority`, `backlog`, and `session-` rows, which are siblings rather than tasks.
16
- - `.claude/plans/*.md`: execution detail for in-progress tasks
17
- - `.claude/memory/index.md` and any memory files relevant to the top backlog item
14
+ - the newest `.canon/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `${CLAUDE_SKILL_DIR}/../../standards/session.md`. It leads the report rather than the reads.
15
+ - `.canon/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the task list from it by dropping the `index`, `priority`, `backlog`, and `session-` rows, which are siblings rather than tasks.
16
+ - `.canon/plans/*.md`: execution detail for in-progress tasks
17
+ - `.canon/memory/index.md` and any memory files relevant to the top backlog item
18
18
 
19
19
  Then read only the task files the summary needs, typically the top one. Do not read the whole folder.
20
20
 
@@ -28,9 +28,9 @@ Output these sections, omitting the first when no handoff was found:
28
28
 
29
29
  **Carried over:** the reasoning the handoff holds, as its writer stated it. Attribute it to the map rather than restating it as fact, and re-measure any count, size, or cost before acting on one.
30
30
 
31
- **Up next:** one line per task row in `.claude/tasks/index.md`, preserving order. A board carrying handoffs has a row per session that wrote one, so a report listing every row queues work nobody filed.
31
+ **Up next:** one line per task row in `.canon/tasks/index.md`, preserving order. A board carrying handoffs has a row per session that wrote one, so a report listing every row queues work nobody filed.
32
32
 
33
- **Active plans:** one line per file in `.claude/plans/`, linking each to its task file in `.claude/tasks/`. Say "None" if empty.
33
+ **Active plans:** one line per file in `.canon/plans/`, linking each to its task file in `.canon/tasks/`. Say "None" if empty.
34
34
 
35
35
  **Relevant context:** two or three memory entries that inform the top backlog item. Skip if none apply.
36
36
 
@@ -40,7 +40,7 @@ End with one line: `Start with: <first Up next item>` and note whether it has a
40
40
 
41
41
  When the board is empty and a handoff was found, name what the handoff leaves open instead: `Start with: <the open thread the handoff names>`. A recommendation slot filled with nothing reads as a failed run, and a handoff on an empty board is the shape a session leaves when it was reasoning rather than shipping.
42
42
 
43
- Do not offer to remove entries. A completed task is archived out of `.claude/tasks/` when work ships. The git log is the authoritative record of shipped work. Plan files are archived per the plan lifecycle rule in `CLAUDE.md`.
43
+ Do not offer to remove entries. A completed task is archived out of `.canon/tasks/` when work ships. The git log is the authoritative record of shipped work. Plan files are archived per the plan lifecycle rule in `CLAUDE.md`.
44
44
 
45
45
  Memory is updated only when a recorded fact becomes wrong, never on resume. A domain fact reaches a session through `.claude/context/`, which `claude-memory-capture` routes to and the three-tier model loads on demand, so the memory folder read here is the residue no context entry owns.
46
46
 
@@ -1,28 +1,30 @@
1
1
  ---
2
2
  title: Capture
3
- description: Rendering committed HTML sources to PNG, what the command asserts about fonts, and why it is toolkit-only
3
+ description: Rendering HTML sources to PNG, what the command asserts about fonts, and why the selector has no default
4
4
  ---
5
5
 
6
6
  # Capture
7
7
 
8
- `canon capture [source]` renders HTML capture sources to PNG, which is how a committed documentation image regenerates from its committed source. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same bare command.
8
+ `canon capture [source] --selector <sel>` renders HTML sources to PNG, which is how a generated documentation image is rebuilt from the markup it was generated out of. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same command.
9
9
 
10
10
  ```bash
11
- canon capture
12
- canon capture assets/install.html
13
- canon capture assets --out .claude/review/captures
11
+ canon capture --selector .window
12
+ canon capture assets/install.html --selector .window
13
+ canon capture assets --selector .window --out .canon/review/captures
14
14
  ```
15
15
 
16
- `assets/` now holds two sources, so a bare `canon capture` rebuilds both. `install.html` is hand-authored and its text came from a real run. `hero.html` is generated by `scripts/core/regen-hero.sh` from the CLI catalogs and must not be edited by hand, since `bun run check` regenerates it and fails on the difference.
16
+ `--selector` is required and every example above passes it. The element a capture crops to belongs to the page, not to the command, so there is no value that could be right for an arbitrary project's markup. `.window` is what this repository's own two sources declare, and a project renders its own pages by naming whatever theirs declare.
17
17
 
18
- Only the HTML is asserted for drift. The PNG is a chromium render whose bytes move with the browser version, so rebuild it with `canon capture assets/hero.html` when the check reports the HTML changed.
18
+ `assets/` here holds two sources, so one run over the folder rebuilds both. `install.html` is hand-authored and its text came from a real run. `hero.html` is generated by `scripts/core/regen-hero.sh` from the CLI catalogs and must not be edited by hand, since `bun run check` regenerates it and fails on the difference.
19
+
20
+ Only the HTML is asserted for drift. The PNG is a chromium render whose bytes move with the browser version, so rebuild it with `canon capture assets/hero.html --selector .window` when the check reports the HTML changed.
19
21
 
20
22
  Every render writes a stamp beside its PNG, `hero.png` next to `hero.stamp`, holding the source filename, a `source-sha256` over the markup bytes it read, and an `image-sha256` over the image bytes it wrote. Both digests are what `bun run check` compares, so a markup edit committed without a capture and a PNG swapped under unchanged markup each fail. The stamp is tracked and commits alongside the pair. Nothing hand-edits it, and a capture that cannot write it reports that source as failed and exits 1, so an image whose stamp never landed is reported rather than passed over.
21
23
 
22
24
  | Option | Behavior |
23
25
  | ------------------ | ------------------------------------------------- |
24
26
  | `--out <dir>` | Write every PNG here instead of beside its source |
25
- | `--selector <sel>` | Element to capture (default: `.window`) |
27
+ | `--selector <sel>` | Element to capture, required and never defaulted |
26
28
 
27
29
  ## What the command asserts
28
30
 
@@ -30,8 +32,12 @@ Each source renders at `deviceScaleFactor` 2 with a transparent background, and
30
32
 
31
33
  What is asserted is the font. The command reads the first family the captured element declares and fails when the browser did not resolve it, because a fallback face rewraps the block and silently changes the output. Sources therefore name a real font rather than relying on `monospace`. A source that cannot render reports its own line and exits 1 without dropping the rest of the batch.
32
34
 
33
- The browser binary installs separately from the package. A fresh clone runs `bunx playwright install chromium` once, and a run that cannot launch one reports the engine's own remediation inside the frame and exits 1 rather than escaping as a stack trace.
35
+ A run refuses before it reads anything else when `--selector` is absent, naming the flag and pointing at `--help`. Ordering it first is what keeps the message about the invocation: the source defaults to `assets`, so checking that first would answer `assets not found` from whatever directory the caller happened to be in and say nothing about the flag that was actually missing.
36
+
37
+ The browser binary installs separately from the package. A first run does `bunx playwright install chromium` once, and a run that cannot launch one reports the engine's own remediation inside the frame and exits 1 rather than escaping as a stack trace.
38
+
39
+ The command ships to targets, alongside `demo`, `inventory`, and `drive`. It was held back while its only caller was this repository regenerating its own committed images, which described the caller rather than the render. A project has generated pages of its own, and the command that would show them rendering and prove their fonts resolved was the one it could not run.
34
40
 
35
- The command is toolkit-only. Its render module holds every browser reference capture makes and `files` in `package.json` excludes it, so an installed `canon` carries the command, reports it as absent on one line, and exits 1. Every other command is unaffected, which is the reason the browser import sits behind a dynamic import rather than at a command's top level.
41
+ Shipping it also fixed what the exclusion was hiding. The render module imported the `@playwright/test` development dependency, which no published tarball carries. Every browser reference still sits behind a dynamic import, so a browser loads for this command rather than in front of every other one.
36
42
 
37
- `canon demo` is the other browser command and it ships, because the reason capture stays here is that it regenerates images committed to this repository and a target has nothing to regenerate. See `demo.md`.
43
+ `demo.md` and `driver.md` cover two of the other three browser commands. What separates this one is that a capture renders a single state from a file on disk, where the rest drive a running application.
@@ -24,7 +24,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
24
24
  | `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
25
  | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
26
26
  | `canon slides list` | List the available slide layouts (`--json` for the catalog) |
27
- | `canon feedback` | Write toolkit feedback from stdin to `.claude/review/feedback/`, or open a GitHub issue with `--github` |
27
+ | `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github` |
28
28
  | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
29
29
  | `canon tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
30
30
  | `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
@@ -42,6 +42,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
42
42
  | `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`) |
43
43
  | `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
44
44
  | `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`) |
45
+ | `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
45
46
  | `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
46
47
  | `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
47
48
  | `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
@@ -71,11 +72,11 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
71
72
  | `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
72
73
  | `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
73
74
  | `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
74
- | `canon capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
75
+ | `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
75
76
  | `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
76
77
  | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
77
78
 
78
- `canon serve` ships and drives no browser, which is what separates it from the two that do. A generated page loses its stylesheet and its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
79
+ `canon serve` drives no browser, which is what separates it from the four that do. All four ship now that `capture` does, so the line between them is the engine rather than the package. A generated page loses its stylesheet and its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
79
80
 
80
81
  A port already in use is the ordinary case rather than a refusal, so it walks forward to the next free one and reports which it took. That is why a caller reads `url` off the `--json` record instead of composing one from the port it asked for. Only contention is walked past. Any other bind failure refuses as `bind-failed` carrying the error's code, rather than being retried twenty times and reported as a range being full, which names a cause nothing checked.
81
82
 
@@ -95,7 +96,7 @@ routes = ["/", "/pricing", "/docs"]
95
96
  query = "button, a[href], input, select, textarea, [tabindex]"
96
97
  ```
97
98
 
98
- `canon drive` is the fourth and ships for the same reason the two before it do. What separates it from all three is the axis rather than the destination: `capture` and `inventory` each answer about a page as it loads, and every defect that exists only after a menu opens or the page scrolls is invisible to both. It takes a JSON run file naming the viewport, the probes, and the interaction sequence, since a route catalog is state a project holds and an interaction sequence is a script written for one question. Viewport heights are never defaulted, because the heights a defect hides at belong to the layout rather than to this command. It reports findings and never gates, since every probe it ships carries a class of false finding a throwaway version already produced. See `driver.md`.
99
+ `canon drive` is the fourth and ships for the same reason the three before it do. What separates it from all three is the axis rather than the destination: `capture` and `inventory` each answer about a page as it loads, and every defect that exists only after a menu opens or the page scrolls is invisible to both. It takes a JSON run file naming the viewport, the probes, and the interaction sequence, since a route catalog is state a project holds and an interaction sequence is a script written for one question. Viewport heights are never defaulted, because the heights a defect hides at belong to the layout rather than to this command. It reports findings and never gates, since every probe it ships carries a class of false finding a throwaway version already produced. See `driver.md`.
99
100
 
100
101
  ## Domain commands
101
102
 
@@ -122,7 +123,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
122
123
  | `secrets` | `scan` |
123
124
  | `deps` | `audit` |
124
125
  | `labels` | `audit` |
125
- | `migrate` | `rename` |
126
+ | `migrate` | `rename`, `records` |
126
127
  | `autoship` | `classify` |
127
128
  | `audits` | `run`, `list` |
128
129
  | `gate` | `run` |
@@ -136,6 +137,8 @@ Common patterns:
136
137
 
137
138
  `migrate rename` moves a project off the retired `aitk` name. It reports until `--write` is passed, and `--scope target` rewrites the toolkit-owned folders alone, reporting every other citation as one the project owns rather than editing prose somebody wrote. A project installing `canon` fresh never needs it.
138
139
 
140
+ `migrate records` moves a project's session records from `.claude/` to `.canon/` and rewrites every tracked file that cites one. It reports until `--write` is passed, and refuses outright when the project does not already ignore `.canon/`, since every folder it relocates is ignored where it stands and landing one under a tracked root commits the memory pen. Take the ignore entry with `canon tooling sync --write` first. A record folder already present at the destination is a refusal rather than a merge, and a line carrying `canon-keep-record-root`, or the line below it, keeps the old spelling for prose that dates a decision. Running it twice rewrites nothing, which is the check that the exclusions and the markers fired.
141
+
139
142
  ## Version skew
140
143
 
141
144
  `canon sync --check` and `canon claude skills drift` are the two moments a target
@@ -10,7 +10,7 @@ description: Compiling a screencast draft into a runnable plan, driving a served
10
10
  Two verbs, and they are separate because the artifact between them is edited.
11
11
 
12
12
  ```bash
13
- canon demo compile .claude/.tmp/screencast/inline-edit.md
13
+ canon demo compile .canon/tmp/screencast/inline-edit.md
14
14
  canon demo run demos/inline-edit.json
15
15
  canon demo run demos/inline-edit.json --cursor ~/cursors/theme --out assets
16
16
  ```
@@ -75,7 +75,7 @@ Every refusal exits 1 and names its reason in the `--json` record, so a skill br
75
75
 
76
76
  ## The browser reaches every target
77
77
 
78
- Unlike `canon capture`, this command ships. Capture is toolkit-only because it regenerates images committed to this repository, and that reason does not transfer to a command whose whole purpose is running in someone else's project.
78
+ This command was the first browser command to ship, and `canon capture` has since joined it. Capture was held back because its only caller was this repository regenerating its own committed images, which was a fact about that caller rather than about the render.
79
79
 
80
80
  The cost is stated rather than hidden: the browser binary installs separately, so a target runs `bunx playwright install chromium` once before a recording works at all. A run that cannot launch reports that command inside the frame and exits 1.
81
81
 
@@ -49,7 +49,7 @@ Every step names what it did, and that name is carried onto each finding it prod
49
49
 
50
50
  Probes run after a step and never on arrival, so a run reaches the load state by opening with a `wait` step of its own, as the example above does. Nothing probes before the first step runs, which makes that leading step the only way to measure the page as it first painted, and naming it is what puts the load state on its own findings rather than under whatever ran next.
51
51
 
52
- Write one in any project that does not have `canon capture`, which is toolkit-only and renders a single state from a committed source. Where capture runs, it already answers about arrival and a leading `wait` duplicates it. Where it does not, this command is the only thing measuring the page at all, and a run without that step reports every driven state and nothing about the one a visitor sees first.
52
+ Write one wherever no capture runs against the page. `canon capture` renders a single state from a source on disk, so where it runs it already answers about arrival and a leading `wait` duplicates it. Where it does not, this command is the only thing measuring the page at all, and a run without that step reports every driven state and nothing about the one a visitor sees first.
53
53
 
54
54
  Each height is driven in a context of its own from a fresh navigation, rather than by resizing the page the previous height already drove, since a sweep asks the same question of each height rather than a later question of an already-driven page.
55
55
 
@@ -98,6 +98,6 @@ It reports findings and never gates, and the exit code says only whether the dri
98
98
 
99
99
  An unreachable page refuses rather than returning an empty report, since nothing measured and nothing found read the same to anything counting findings.
100
100
 
101
- The browser binary installs separately from the package, once, with `bunx playwright install chromium`. The command ships to targets like `demo` and `inventory` rather than staying toolkit-only like `capture`, because its whole purpose is measuring a page inside someone else's project. It reaches nothing under `src/capture/`, which the package excludes, so the two surfaces move independently.
101
+ The browser binary installs separately from the package, once, with `bunx playwright install chromium`. The command ships to targets like `demo`, `inventory`, and `capture`, because its whole purpose is measuring a page inside someone else's project. It reaches nothing under `src/capture/`, so the two surfaces move independently.
102
102
 
103
103
  See `commands.md` for where this sits among the browser commands, and `capture.md` for the single-state render it adds an axis to.
@@ -9,7 +9,7 @@ category: Agent surface
9
9
  CLI catalog and invocation rules for agents, split by command domain. Start with overview.
10
10
 
11
11
  - [Audits](audits.md): Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
12
- - [Capture](capture.md): Rendering committed HTML sources to PNG, what the command asserts about fonts, and why it is toolkit-only
12
+ - [Capture](capture.md): Rendering HTML sources to PNG, what the command asserts about fonts, and why the selector has no default
13
13
  - [Census](census.md): Tracked-plus-untracked file count, a breakdown by extension, and a line total that skips whatever reads as binary
14
14
  - [Command catalog](commands.md): Every project-level command and every domain subcommand, plus the shape each domain exposes
15
15
  - [Comments](comments.md): Comment density by language and kind, the two structural exclusions, and how the degradation sweep finds its vocabulary
@@ -9,7 +9,7 @@ description: Flags, exit codes, and JSON shape for canon indexes regen, plus whe
9
9
 
10
10
  Duplicates dedupe. The whole-repo walk prunes `.git`, `node_modules`, and anything `.gitignore` covers via `git check-ignore`.
11
11
 
12
- A positional path is not filtered that way, because the walk-up resolves on the filesystem and never consults git. That is the only way to regenerate an index inside a gitignored folder, and it is how `.claude/tasks/` and `.claude/memory/` both stay current.
12
+ A positional path is not filtered that way, because the walk-up resolves on the filesystem and never consults git. That is the only way to regenerate an index inside a gitignored folder, and it is how `.canon/tasks/` and `.canon/memory/` both stay current.
13
13
 
14
14
  Each is driven by a `PostToolUse` hook matching `Write|Edit|MultiEdit`, so a file moved by a shell `mv` fires nothing. A caller that relocates an entry runs the command itself once the last move is done.
15
15
 
@@ -205,7 +205,7 @@ target never touched still reports `matching`. Every other seed copies byte for
205
205
  byte.
206
206
 
207
207
  `superseded` names a file a newer seed folder replaced, such as `.claude/TASKS.md`
208
- against the `.claude/tasks/` that now ships. The entry carries `replacedBy` and
208
+ against the `.canon/tasks/` that now ships. The entry carries `replacedBy` and
209
209
  nothing else, and the file is never deleted, since the content belongs to the
210
210
  project and only its author can decide where it moves. The list derives from the
211
211
  seed tree rather than from a fixed set of filenames, so a folder added later is
@@ -7,7 +7,7 @@ description: Reading intake folder counts and items, the three read states an it
7
7
 
8
8
  ## List
9
9
 
10
- `canon intake list` reports the intake folders under `.claude/intake/`, or the items one folder holds. It reads and never writes, because an answer belongs to the operator and a verb that filled one would decide what the folder exists to ask.
10
+ `canon intake list` reports the intake folders under `.canon/intake/`, or the items one folder holds. It reads and never writes, because an answer belongs to the operator and a verb that filled one would decide what the folder exists to ask.
11
11
 
12
12
  A folder carries a two-digit ordinal ahead of its slug, as in `21-toolkit-overview`, so a listing sorts by when each opened. A bare slug still resolves: passing `toolkit-overview` matches the one folder whose name is an ordinal ahead of it, and the folder's real name is what every command reports back.
13
13
 
@@ -10,7 +10,7 @@ description: Comparing the files a pull request body's Key Changes names against
10
10
  ```bash
11
11
  canon pr key-changes
12
12
  canon pr key-changes 1265 --json
13
- canon pr key-changes --body .claude/.tmp/pr/body.md --base origin/main
13
+ canon pr key-changes --body .canon/tmp/pr/body.md --base origin/main
14
14
  ```
15
15
 
16
16
  The positional is the pull request to read, defaulting to the one open on this branch.
@@ -7,11 +7,11 @@ description: The two roots a record folder resolves at, validating the session r
7
7
 
8
8
  ## Record roots
9
9
 
10
- Every verb here resolves a record folder at two roots rather than one. `.canon/<folder>` is read first, `.claude/<folder>` second, and a folder neither root carries resolves to the creation default, which is `.claude/`. The scratch folder is the one name that differs by root, spelled `.claude/.tmp` and `.canon/tmp`, since inside a dotted root the leading dot hides nothing already hidden.
10
+ Every verb here resolves a record folder at two roots rather than one. `.canon/<folder>` is read first, `.claude/<folder>` second, and a folder neither root carries resolves to the creation default, which is `.claude/`. The scratch folder is the one name that differs by root, spelled `.canon/tmp` and `.canon/tmp`, since inside a dotted root the leading dot hides nothing already hidden.
11
11
 
12
12
  The read order and the creation default disagree deliberately. The gitignored record folders are moving to a root of their own, and the CLI learns to read both roots in a release that ships ahead of the move, so the binary a session already holds knows where to look by the time a tree relocates. Creating under the new root before then would write records to a root whose ignore line may not have reached a project yet, and it would split one project's records across two roots with no verb able to reconcile them. The move flips the default and nothing else.
13
13
 
14
- A caller never spells a record root by hand for the same reason. A path written as `.claude/plans/...` resolves against one root and reports nothing when it is wrong, which is the quiet failure this ordering exists to prevent: a stale binary meeting a moved layout, writing to the old path, and reporting success. Read a folder through the verb that owns it, and where a skill needs the path itself, take it from that verb's record rather than composing one.
14
+ A caller never spells a record root by hand for the same reason. A path written as `.canon/plans/...` resolves against one root and reports nothing when it is wrong, which is the quiet failure this ordering exists to prevent: a stale binary meeting a moved layout, writing to the old path, and reporting success. Read a folder through the verb that owns it, and where a skill needs the path itself, take it from that verb's record rather than composing one.
15
15
 
16
16
  A refusal names every root it looked at, so a message reading `no-folder` says where a write would land as well as where the read failed.
17
17
 
@@ -126,7 +126,7 @@ The reading carries two windows rather than one, at 7 and 30 days. A single wind
126
126
 
127
127
  Nothing fails on a number here. A record folder has no correct size, so the reading is a number to notice rather than a threshold to gate, and the point of the verb is that the next reading is taken by a command instead of by someone remembering to count the folder. The memory pen went from 44 entries to 236 between two readings taken by hand two weeks apart, which is the measurement this replaces.
128
128
 
129
- The scratch folder is read here and skipped by a backup, because deletable without loss is not the same as empty. The routing handoffs and the memory archive both sit there and both accumulate. `.claude/.records.git` stays out because it is the backup history rather than a record, and `.claude/worktrees/` stays out because each entry is a checkout of the project with its own removal verb, and one of them outweighs every record folder combined.
129
+ The scratch folder is read here and skipped by a backup, because deletable without loss is not the same as empty. The routing handoffs and the memory archive both sit there and both accumulate. `.canon/.records.git` stays out because it is the backup history rather than a record, and `.claude/worktrees/` stays out because each entry is a checkout of the project with its own removal verb, and one of them outweighs every record folder combined.
130
130
 
131
131
  The window counts read `mtime`, so what they report is a file written inside the window rather than one created there. An entry edited long after it landed reads as recent, which overstates growth and never understates it, and these folders are append-mostly so the two readings agree on nearly every file. The one reading that is wrong rather than early is a machine restored by `canon records pull`, which resets the work tree hard and re-dates every file it writes, so a window taken there counts the restore. Nothing on the filesystem separates the two, since a restored file is new by every stamp it carries.
132
132
 
@@ -142,7 +142,7 @@ canon records push --json
142
142
  canon records pull
143
143
  ```
144
144
 
145
- The backed folders are `diagrams`, `groundwork`, `intake`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`, all under whichever record root the project carries. Eight of them are the Claude ignore group the claude manifest ships, minus three entries: `.claude/.tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.claude/.records.git/`, which is the history the rest are pushed into. `diagrams` is the one that group does not carry at all, since a target still tracks its own copies, which is why the list is spelled out rather than derived. Each name is a top-level record folder and every archive sits inside the one it archives, so the list stays at one entry per surface however many archives appear. It is a constant rather than configuration, and it deliberately does not match the six record kinds `validate` hardcodes.
145
+ The backed folders are `diagrams`, `groundwork`, `intake`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`, all under whichever record root the project carries. They are the record root's own entries less three: `tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.records.git/`, which is the history the rest are pushed into. Nothing bounds the list from outside any more, since the claude manifest ships one `.canon/` root entry and names no folder, so spelling the nine out is what keeps a record folder added later from silently entering the payload. Each name is a top-level record folder and every archive sits inside the one it archives, so the list stays at one entry per surface however many archives appear. It is a constant rather than configuration, and it deliberately does not match the six record kinds `validate` hardcodes.
146
146
 
147
147
  Records are gitignored by design, so the history lives in a second git directory at `.records.git` inside the record root, with that root as its work tree. Both resolve off the root together rather than folder by folder, since a history opened at one root beside a work tree at the other would stage the deletion of every folder a move relocated. Every path stays where it is, which is what a separate checkout could not do. The verbs stage the nine folders by explicit pathspec with `--force`, so nothing outside them can enter the index however the ignore rules read, and the project working tree and its index are never touched. Each pathspec is a bare folder name and git reads it against the current directory rather than against the work tree the same call names, so the invocation carries `-C` at the work tree beside the other two flags. That is what lets either verb run from a linked worktree under `.claude/worktrees/`, which sits inside the records work tree and would otherwise prefix every name with its own path.
148
148
 
@@ -151,12 +151,12 @@ Records are gitignored by design, so the history lives in a second git directory
151
151
  A person creates the records repository once per machine, and the verbs refuse with the commands when it is absent:
152
152
 
153
153
  ```bash
154
- git --git-dir=.claude/.records.git init
155
- git --git-dir=.claude/.records.git remote add origin <private-repo-url>
156
- printf '.claude/.records.git/\n' >> .gitignore
154
+ git --git-dir=.canon/.records.git init
155
+ git --git-dir=.canon/.records.git remote add origin <private-repo-url>
156
+ printf '.canon/\n' >> .gitignore
157
157
  ```
158
158
 
159
- The ignore line is repeated here rather than left to the install, because the person running these commands is the one who creates the directory and the rule is worth reading beside the command that needs it. The claude manifest ships `.claude/.records.git/` as the first entry of its group, so a project that ran `canon claude sync` already carries it and this line is a no-op there. What the group buys is the project that never sets records up: it holds a rule for a directory it will never create, which costs nothing, and the backed-folder derivation above subtracts that entry from the group rather than from a list it does not sit in.
159
+ The ignore line is repeated here rather than left to the install, because the person running these commands is the one who creates the directory and the rule is worth reading beside the command that needs it. The claude manifest ships `.canon/` too, so a project that ran `canon tooling sync` already carries it and this line is a no-op there. One root entry covers the history and every record beside it, which is what makes the rule worth stating once rather than per folder.
160
160
 
161
161
  Point it at a private repository, and at one that is not a remote of the project. Records carry the memory pen, the review reports, and the groundwork trails, so a public project publishes all of it to anyone who fetches all refs. `push` compares the configured origin against every remote of the project and refuses on a match. A read of that list which fails refuses as well, since an empty list clears the comparison for every origin and a gate that passes on its own failure is no gate.
162
162
 
@@ -164,7 +164,8 @@ Point it at a private repository, and at one that is not a remote of the project
164
164
 
165
165
  | Reason | What fired |
166
166
  | ------------------- | ------------------------------------------------------------------------------------ |
167
- | `no-repository` | No `.claude/.records.git`, answered with the two setup commands |
167
+ | `split-roots` | Record folders sit under both roots, so the resolved work tree is not the whole set |
168
+ | `no-repository` | No `.canon/.records.git`, answered with the two setup commands |
168
169
  | `no-remote` | The records history has no `origin` |
169
170
  | `remote-unreadable` | The project's own remotes could not be read, so the shared-origin gate could not run |
170
171
  | `remote-shared` | The records origin is also a remote of the project |
@@ -173,6 +174,8 @@ Point it at a private repository, and at one that is not a remote of the project
173
174
  | `local-ahead` | `pull` found local commits that never reached the origin |
174
175
  | `git-failed` | A git call failed, with its stderr in the message |
175
176
 
177
+ `split-roots` runs ahead of every gate below it and fires on a half-migrated tree, which is what a `canon migrate records` run that failed partway leaves. `recordRoot` answers for the whole tree on the first root that exists, so a folder left at the old root is absent from the work tree while the records index still names it, and an unguarded `add -A` would stage its deletion and drop it from the remote on the next push. Finish the move, or put the stranded folders back beside the others.
178
+
176
179
  The two `pull` refusals exist because the directions are not symmetric. A push only adds, while a pull onto a machine holding work that never left it would discard that work. Resolve either by running `push` first, or by moving the local folders aside. A machine holding none of the ten has nothing to lose, so a restore onto a fresh checkout runs straight through.
177
180
 
178
181
  ### When it runs
@@ -38,9 +38,9 @@ A rule stated in two skill bodies and never in `CLAUDE.md` or another rule is ou
38
38
 
39
39
  Matching is recall-first and keyed on shared distinctive tokens rather than on a phrase two surfaces spell the same way. The case this exists for was one rule written three different ways, so a near-exact matcher would ship a cheap report blind to the defect it was built for.
40
40
 
41
- A token is distinctive when it appears in at most 20 of the corpus's statements, which is under one percent of them. `.claude/plans/` sits at 14 and is the anchor the motivating case turns on, while `file` sits at 371 and would match most of the tree.
41
+ A token is distinctive when it appears in at most 20 of the corpus's statements, which is under one percent of them. `.canon/plans/` sits at 14 and is the anchor the motivating case turns on, while `file` sits at 371 and would match most of the tree.
42
42
 
43
- A backticked token counts double. An author marking a span as code named an identifier rather than describing one, so `.claude/plans/archive/` says more about what a statement governs than any two prose words do. Two statements are one rule when their shared anchors reach a weight of 3.
43
+ A backticked token counts double. An author marking a span as code named an identifier rather than describing one, so `.canon/plans/archive/` says more about what a statement governs than any two prose words do. Two statements are one rule when their shared anchors reach a weight of 3.
44
44
 
45
45
  Every record names the anchors its match rested on, so a reader can weigh a finding instead of taking it.
46
46
 
@@ -63,7 +63,7 @@ Two classes resolve to nothing and are right to. Both are reported by name rathe
63
63
 
64
64
  Only an exact declaration exempts, never a glob match against one. A glob declares a shape, so a body path sitting inside it is still a citation and a stale one is still a defect. A rule scoped at `docs/**` citing a `docs/agents/renamed.md` that moved is exactly the class this stage exists to catch, and matching the glob would excuse it.
65
65
 
66
- **Ignored.** A path git ignores is session scratch no clone is expected to hold. `governance/rules/claude/555-tasks.md` cites `.claude/tasks/index.md`, which is real in a live project and absent from a fresh clone and from every linked worktree. Resolving against the filesystem alone would make the verdict depend on which tree the stage ran in, so the unresolved paths go to one batched `git check-ignore` and an ignored one is excused. A read git cannot answer refuses rather than reporting those paths dead.
66
+ **Ignored.** A path git ignores is session scratch no clone is expected to hold. `governance/rules/claude/555-tasks.md` cites `.canon/tasks/index.md`, which is real in a live project and absent from a fresh clone and from every linked worktree. Resolving against the filesystem alone would make the verdict depend on which tree the stage ran in, so the unresolved paths go to one batched `git check-ignore` and an ignored one is excused. A read git cannot answer refuses rather than reporting those paths dead.
67
67
 
68
68
  ## Why the glob half reads one corpus
69
69
 
@@ -68,6 +68,6 @@ A skill pairs to a scenario by filename, `<category>-<command>` first and bare `
68
68
 
69
69
  `exempt` means no arm should be written and holds only with a reason, declared in `scripts/sandbox/exempt.toml` and limited to a harness limit the checker cannot reach past or a skill that writes no artifact. An armed arm outranks an exemption. An exemption naming no shipped skill, or naming one an arm now asserts, exits 1 without `--strict`. Each armed arm reports as `<category>:<command>/<arm>`, so two same-named arms under different scenarios stay distinct.
70
70
 
71
- `scripts/sandbox/run.sh` calls this after a headless run and merges the verdict into the envelope it prints. It also writes that merged record to `.claude/.tmp/sandbox-runs/<target>-<arm>-<timestamp>.json` with a `writes` array appended, and logs the path on stderr. Both fields are what a later re-score needs, since `--envelope` and `--writes` read files the run deletes on exit.
71
+ `scripts/sandbox/run.sh` calls this after a headless run and merges the verdict into the envelope it prints. It also writes that merged record to `.canon/tmp/sandbox-runs/<target>-<arm>-<timestamp>.json` with a `writes` array appended, and logs the path on stderr. Both fields are what a later re-score needs, since `--envelope` and `--writes` read files the run deletes on exit.
72
72
 
73
73
  Two more fields ride alongside the verdict rather than inside it. `escapes` lists what the run wrote under a watched toolkit root, which the verdict cannot assert over because those files sit outside the sandbox tree. `sessions` reports the nested-dispatch bound, carrying `watched` for whether the client's session registry was there to read, `new` for the records that appeared while the run was in flight, and `reap` for what the run found in the session's process group afterwards. Neither field fails a run on its own.
@@ -30,7 +30,7 @@ Three root files are read whether or not the field names them, being `package.js
30
30
 
31
31
  ### What the corpus leaves out
32
32
 
33
- The corpus answers what the package publishes, which is narrower than what the repository holds. Measured against this repository on 2026-08-21, the scan read 544 files and left 593 of the 1139 git lists unread. Those include everything under `.claude/`, `wiki/`, and `internal/`, the workflow definitions under `.github/`, and the trees the publish negations remove, being `scripts/sandbox/`, `scripts/eval/`, `src/capture/`, and every test file.
33
+ The corpus answers what the package publishes, which is narrower than what the repository holds. Measured against this repository on 2026-08-21, the scan read 544 files and left 593 of the 1139 git lists unread. Those include everything under `.claude/`, `wiki/`, and `internal/`, the workflow definitions under `.github/`, and the trees the publish negations remove, being `scripts/sandbox/`, `scripts/eval/`, and every test file. `src/capture/` was a fourth negation when that reading was taken and is not one now. The folder holds 5 tracked files, 2 of them tests the publish still excludes, so the corpus is 3 wider than the numbers above describe and the unread count is 3 smaller.
34
34
 
35
35
  A public repository makes that gap readable by anyone, so a clean run means no credential in the published tree rather than none in the repository. The run states the number on every pass, including a clean one, so the bound travels with the verdict. Widening the corpus to every tracked file is a separate decision, since the row this implements puts the shipped tree first on the record's rule that content leaving the repository gates harder than content that stays.
36
36