@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
@@ -2,28 +2,29 @@ import { existsSync } from 'node:fs'
2
2
  import { join, relative, resolve } from 'node:path'
3
3
  import { $ } from 'bun'
4
4
  import { gitEnv } from '@/git-env'
5
- import { recordRoot } from '@/record-root'
5
+ import { RECORD_ROOTS, recordRoot } from '@/record-root'
6
6
 
7
7
  /**
8
8
  * The folders a backup carries, relative to the record root `workTree` resolves
9
- * rather than to `.claude/` specifically, since the same nine names sit under
10
- * whichever root a tree holds. Most of them are the `# Claude` group the claude
11
- * manifest ships, minus three: the scratch folder, which is defined as deletable
12
- * without loss, `worktrees/`, whose contents belong to the enclosing repository
13
- * already, and `.records.git/`, which is the history the rest are pushed into.
14
- * The list is spelled out rather than read off that group so adding an ignore
15
- * entry cannot silently enlarge the payload.
9
+ * rather than to either root specifically, since the same nine names sit under
10
+ * whichever one a tree holds.
16
11
  *
17
- * `diagrams` is the one name the manifest group does not carry, so a target
18
- * tracks it where this repository ignores it. That is the second reason to
19
- * spell the list out: the manifest bounds it rather than producing it, and no
20
- * count taken off that group matches this one. `scripts/core/check-ignore-parity.sh`
21
- * compares the two lists and holds the reason `diagrams` stays apart, so the
22
- * one difference is enforced rather than assumed.
12
+ * Nothing bounds this list any more, and the move is what took the bound away.
13
+ * The claude manifest used to ship a folder apiece, so the `# Claude` group
14
+ * named a superset this could be read against; it ships one `.canon/` root
15
+ * entry now and names no folder at all. Spelling the list out is therefore the
16
+ * whole of the protection rather than half of it, since a record folder added
17
+ * under `.canon/` is ignored the moment it exists and enters no payload until
18
+ * a name is written here.
23
19
  *
24
- * The manifest group is the one this reads rather than the enclosing
25
- * repository's own `.gitignore`, which spreads the same entries across two
26
- * headers and carries `.claude/README.md` that no target receives.
20
+ * Three counts describe this surface and each is right about a different
21
+ * question, so they are stated apart rather than reconciled. Nine is what a
22
+ * disk loss would take, which is this list. Eleven is what sat under `.claude/`
23
+ * as an ignored folder before the move, which adds the scratch folder that is
24
+ * deletable without loss and `worktrees/`, whose contents belong to the
25
+ * enclosing repository already. Twelve is what the move relocated, which counts
26
+ * ignore entries rather than folders: the eleven less `worktrees/`, which stayed,
27
+ * plus `.records.git/` and the `README.md` a records pull writes back.
27
28
  *
28
29
  * Each entry is a top-level record folder and every archive sits inside the one
29
30
  * it archives, so the three former archive entries are covered by their parents
@@ -103,6 +104,7 @@ const COMMIT_IDENTITY = [
103
104
  ]
104
105
 
105
106
  export const BACKUP_REFUSALS = [
107
+ 'split-roots',
106
108
  'no-repository',
107
109
  'no-remote',
108
110
  'remote-unreadable',
@@ -297,6 +299,52 @@ async function resolveRemote(root: string): Promise<string | BackupRefused> {
297
299
  return url
298
300
  }
299
301
 
302
+ /**
303
+ * Backed folders sitting at a record root other than the one `workTree` chose.
304
+ *
305
+ * A half-migrated tree is the case this reads. `recordRoot` answers for the
306
+ * whole tree on the first root that exists, so one folder left behind by a move
307
+ * that failed partway is absent from the work tree while the records index
308
+ * still names it. `scopedFolders` puts it in the pathspec on the index side,
309
+ * `add -A` stages its deletion, and the push drops it from the remote, which is
310
+ * the harm `RETIRED_FOLDERS` documents reached by a different route.
311
+ *
312
+ * Both verbs refuse on it rather than only `push`. A `pull` resets the resolved
313
+ * work tree hard and leaves the stranded copy beside it, which is not a loss but
314
+ * is a tree where two roots disagree and neither is wrong.
315
+ *
316
+ * Only `BACKED_FOLDERS` is read. The scratch folder is deletable by definition
317
+ * and `worktrees/` belongs to the enclosing repository, so neither stranded
318
+ * anywhere costs a record.
319
+ */
320
+ function strandedFolders(root: string): string[] {
321
+ const resolved = resolve(workTree(root))
322
+
323
+ return RECORD_ROOTS.flatMap((candidate) => {
324
+ const dir = join(root, candidate)
325
+ if (resolve(dir) === resolved) return []
326
+
327
+ return BACKED_FOLDERS.filter((folder) => existsSync(join(dir, folder))).map(
328
+ (folder) => join(candidate, folder),
329
+ )
330
+ })
331
+ }
332
+
333
+ /** Refuses a tree whose records sit under both roots, naming what to move. */
334
+ function refuseSplitRoots(root: string): BackupRefused | undefined {
335
+ const stranded = strandedFolders(root)
336
+ if (stranded.length === 0) return undefined
337
+
338
+ return refuse(
339
+ 'split-roots',
340
+ [
341
+ `Records sit under both roots, so ${relative(root, workTree(root))} is not the whole set and a push would stage the rest as deleted:`,
342
+ ...stranded.map((path) => ` ${path}`),
343
+ 'Finish the move with canon migrate records --write, or put these back beside the others by hand.',
344
+ ].join('\n'),
345
+ )
346
+ }
347
+
300
348
  /**
301
349
  * The subset of the backed and retired names a pathspec can name: on disk, or
302
350
  * already in the records index.
@@ -345,6 +393,9 @@ function countLines(text: string): number {
345
393
  * that commit on one disk, which is the state the whole verb exists to end.
346
394
  */
347
395
  export async function pushRecords(root: string): Promise<PushOutcome> {
396
+ const split = refuseSplitRoots(root)
397
+ if (split) return split
398
+
348
399
  const remote = await resolveRemote(root)
349
400
  if (typeof remote !== 'string') return remote
350
401
 
@@ -406,6 +457,9 @@ export async function pushRecords(root: string): Promise<PushOutcome> {
406
457
  * resolves by pushing first or by moving the local folders aside.
407
458
  */
408
459
  export async function pullRecords(root: string): Promise<PullOutcome> {
460
+ const split = refuseSplitRoots(root)
461
+ if (split) return split
462
+
409
463
  const remote = await resolveRemote(root)
410
464
  if (typeof remote !== 'string') return remote
411
465
 
@@ -977,7 +977,7 @@ export function governedPaths(statement: string): string[] {
977
977
  /**
978
978
  * The words a governed path offers a filename. Each segment gives its own word
979
979
  * and, where it carries a prefix or a placeholder, the parts either side of a
980
- * hyphen, so `.claude/tasks/session-<slug>.md` offers `tasks` and `session`.
980
+ * hyphen, so `.canon/tasks/session-<slug>.md` offers `tasks` and `session`.
981
981
  *
982
982
  * A dotted segment gives nothing. It names the folder holding the artifact
983
983
  * rather than the artifact, and a standard named for it would pass this check
@@ -1,6 +1,7 @@
1
1
  import { existsSync, statSync } from 'node:fs'
2
2
  import { basename, join } from 'node:path'
3
3
  import { SUBDIRS } from '@/claude/seeds'
4
+ import { creationRel, isRecordEntry } from '@/record-root'
4
5
  import type { StampDomain } from '@/sync/stamp'
5
6
 
6
7
  const CLAUDE_DIR = '.claude'
@@ -52,7 +53,7 @@ export interface UnmigratedDomain {
52
53
  /**
53
54
  * Pairs each seed subdirectory against an uppercase-stem sibling in the target,
54
55
  * so a project still holding `.claude/TASKS.md` is reported against the
55
- * `.claude/tasks/` folder that replaced it.
56
+ * `.canon/tasks/` folder that replaced it.
56
57
  *
57
58
  * Deriving from the seed tree rather than from a fixed list means a folder
58
59
  * added later is covered without editing this file. The cost is that only an
@@ -66,7 +67,15 @@ export function collectSuperseded(target: string): SupersededEntry[] {
66
67
  const rel = join(CLAUDE_DIR, `${subdir.toUpperCase()}.md`)
67
68
  if (!isFile(join(target, rel))) continue
68
69
 
69
- entries.push({ rel, replacedBy: join(CLAUDE_DIR, subdir) })
70
+ // A record subdir now lives under the record root, so the replacement this
71
+ // names is resolved against the target rather than fixed at `.claude/`.
72
+ // Naming a folder the target does not have sends a person to migrate their
73
+ // legacy file into a path nothing reads.
74
+ const replacedBy = isRecordEntry(subdir)
75
+ ? creationRel(target, subdir)
76
+ : join(CLAUDE_DIR, subdir)
77
+
78
+ entries.push({ rel, replacedBy })
70
79
  }
71
80
 
72
81
  return entries
@@ -56,7 +56,7 @@ export type AnswersOutcome = PlanAnswers | AnswersRefused
56
56
  * project root and against the board directory both.
57
57
  *
58
58
  * The second base is the one a dispatcher actually has to hand. A board row
59
- * writes its `Plan:` link relative to `.claude/tasks/`, so the href reads
59
+ * writes its `Plan:` link relative to `.canon/tasks/`, so the href reads
60
60
  * `../plans/feature-<slug>.md`, and resolving that against the root alone lands
61
61
  * a directory above the repository and refuses a plan that exists.
62
62
  *
@@ -184,7 +184,7 @@ function isRowFor(line: string, target: string): boolean {
184
184
  /**
185
185
  * Resolves the `Plan:` target against the board and against the project root
186
186
  * both, which is how `claude-docs` reads the same line. It accepts `../plans/x.md`
187
- * and `.claude/plans/x.md` as one file, so a gate reading only the first form
187
+ * and `.canon/plans/x.md` as one file, so a gate reading only the first form
188
188
  * would pass the second and strand the plan this exists to protect.
189
189
  *
190
190
  * The resolved path is returned rather than a boolean, because the citation
@@ -213,11 +213,28 @@ export function resolveLivePlan(
213
213
  const fromBoard = resolve(dir, target)
214
214
  const fromRoot = resolve(root, target)
215
215
 
216
- if (live(fromBoard)) return fromBoard
217
- if (live(fromRoot)) return fromRoot
216
+ if (live(fromBoard)) return atOneRoot(fromBoard, plans, root)
217
+ if (live(fromRoot)) return atOneRoot(fromRoot, plans, root)
218
218
  return undefined
219
219
  }
220
220
 
221
+ /**
222
+ * Restates a live plan path at the root this tree actually resolves, so two
223
+ * tasks naming one plan through different roots compare equal.
224
+ *
225
+ * Callers compare the return of `resolveLivePlan` by string. Both roots are
226
+ * accepted above, so without this a task citing `.canon/plans/x.md` and one
227
+ * citing `.canon/plans/x.md` name one file and read as two, which counts a
228
+ * sibling's citation as absent and refuses the archive it should allow.
229
+ */
230
+ function atOneRoot(path: string, plans: string[], root: string): string {
231
+ const at = plans.find((dir) => isUnder(path, dir))
232
+
233
+ return at === undefined
234
+ ? path
235
+ : join(recordDir(root, PLANS), relative(at, path))
236
+ }
237
+
221
238
  /**
222
239
  * Names the other live tasks whose `Plan:` line lands on the same file. This is
223
240
  * the rule `claude-docs` applies before it archives a plan, held here so one
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  title: Diagram reference
3
- description: Shape and content rules for .claude/diagrams/<kind>.md files
3
+ description: Shape and content rules for .canon/diagrams/<kind>.md files
4
4
  ---
5
5
 
6
6
  # Diagram reference
7
7
 
8
- Applies to per-kind entries under `.claude/diagrams/`. Skip for `index.md`, which is regenerated by `canon indexes regen`.
8
+ Applies to per-kind entries under `.canon/diagrams/`. Skip for `index.md`, which is regenerated by `canon indexes regen`.
9
9
 
10
10
  A diagram entry answers one question about the system with one or more Mermaid diagrams and the prose that makes them readable. It is not a rendering of the file tree. The check for any single line: does it tell a reader something the code layout would not have told them? If not, it belongs in `.claude/context/`.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs per-kind diagram entries under `.claude/diagrams/`: which question each answers, the Mermaid source, the accessibility fields, and the explanation prose beneath. It states the voice for that prose, which is the yield the `write-human` skill grants a surface whose own standard sets one.
14
+ Governs per-kind diagram entries under `.canon/diagrams/`: which question each answers, the Mermaid source, the accessibility fields, and the explanation prose beneath. It states the voice for that prose, which is the yield the `write-human` skill grants a surface whose own standard sets one.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -39,7 +39,7 @@ An entry that fails these is non-conforming regardless of whether it satisfies e
39
39
  - `verified` (required): the short commit SHA an entry was last checked against and the ISO date of that check, separated by a space (`73e9a3f8 2026-08-02`). A stub nobody has drawn yet carries the literal `TODO: never verified` instead, which is the one other accepted value.
40
40
  - `stale` (optional): one line naming what changed under the entry since that check. Nothing writes it on its own, so it sits on an entry because a reader put it there and is absent everywhere else.
41
41
 
42
- The first three feed `.claude/diagrams/index.md` when regenerated. The catalog sorts categories alphabetically rather than in narrative order, so an entry cannot rely on its position to introduce another. Each entry names its own starting point, and the catalog's subtitle routes a first-time reader to the system context entry.
42
+ The first three feed `.canon/diagrams/index.md` when regenerated. The catalog sorts categories alphabetically rather than in narrative order, so an entry cannot rely on its position to introduce another. Each entry names its own starting point, and the catalog's subtitle routes a first-time reader to the system context entry.
43
43
 
44
44
  The marker fields reach the catalog through neither route. `canon indexes regen` reads `title`, `description`, and `category` and ignores every other key, so a marker changes no generated file. A reader picks it up by opening the entry, which is where it sits above the diagram and where anyone deciding whether to trust the picture is already standing.
45
45
 
@@ -9,7 +9,7 @@ Applies to a glossary, the file holding one entry per term a body of material de
9
9
 
10
10
  ## Scope
11
11
 
12
- Governs a glossary at `.claude/teach/<nn>-<topic>/GLOSSARY.md` and at whatever path a surface fixes for one it holds: its frontmatter, entry shape, ordering, grouping, and the rules deciding which terms it carries.
12
+ Governs a glossary at `.canon/teach/<nn>-<topic>/GLOSSARY.md` and at whatever path a surface fixes for one it holds: its frontmatter, entry shape, ordering, grouping, and the rules deciding which terms it carries.
13
13
 
14
14
  Does not govern:
15
15
 
@@ -5,13 +5,13 @@ description: Folder layout, ordinal naming, reserved numbering, frontmatter and
5
5
 
6
6
  # Groundwork reference
7
7
 
8
- Applies to a groundwork track at `.claude/groundwork/<nn>-<slug>/`. A track measures one question that has to be settled before anyone can plan against it. The numbering inside the folder is the table of contents, so a reader opens the folder and knows where to start and what follows without an index maintained inside each file.
8
+ Applies to a groundwork track at `.canon/groundwork/<nn>-<slug>/`. A track measures one question that has to be settled before anyone can plan against it. The numbering inside the folder is the table of contents, so a reader opens the folder and knows where to start and what follows without an index maintained inside each file.
9
9
 
10
10
  The folder is gitignored and unbacked. No check reaches its contents and no history recovers a deleted one, so every rule here holds only while a session reads it, and the handoff file has to be self-contained.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs a groundwork track under `.claude/groundwork/<nn>-<slug>/`: folder layout, ordinal naming, reserved numbering, frontmatter and dating, what each required file holds, and the conventions a track keeps.
14
+ Governs a groundwork track under `.canon/groundwork/<nn>-<slug>/`: folder layout, ordinal naming, reserved numbering, frontmatter and dating, what each required file holds, and the conventions a track keeps.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -25,7 +25,7 @@ Does not govern:
25
25
  ## Folder name
26
26
 
27
27
  - Name the folder `<nn>-<slug>`, a two-digit zero-padded ordinal followed by a kebab-case slug.
28
- - Take the ordinal from the highest one already present across both `.claude/groundwork/` and `.claude/intake/`, incremented. A listing then sorts by when each folder opened rather than alphabetically, and the count includes both kinds because the two share one creation-order line.
28
+ - Take the ordinal from the highest one already present across both `.canon/groundwork/` and `.canon/intake/`, incremented. A listing then sorts by when each folder opened rather than alphabetically, and the count includes both kinds because the two share one creation-order line.
29
29
  - With neither folder holding an entry, the first one opened takes `01`. Do not read this off the numbering inside a track, which starts at `00` on a large one and disagrees with intake's own first file.
30
30
  - Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
31
31
 
@@ -12,7 +12,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
12
12
  - [Commit reference](commit.md): Commit message format and type conventions
13
13
  - [Context entry reference](context.md): Shape and content rules for .claude/context/<domain>.md entries
14
14
  - [Design reference](design.md): Shape and content rules for .claude/DESIGN.md
15
- - [Diagram reference](diagrams.md): Shape and content rules for .claude/diagrams/<kind>.md files
15
+ - [Diagram reference](diagrams.md): Shape and content rules for .canon/diagrams/<kind>.md files
16
16
  - [Glossary reference](glossary.md): Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
17
17
  - [Groundwork reference](groundwork.md): Folder layout, ordinal naming, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
18
18
  - [Intake reference](intake.md): Folder layout, ordinal naming, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
@@ -30,7 +30,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
30
30
  - [Slug reference](slug.md): Transform from a git branch name to a slug, and the three responses to an empty result
31
31
  - [Snippet reference](snippets.md): Snippet reference and authoring conventions
32
32
  - [Standard reference](standard.md): Shape and content rules for authoring a standard
33
- - [Tasks reference](tasks.md): Folder layout, filename convention, readiness groups, and content rules for .claude/tasks/
33
+ - [Tasks reference](tasks.md): Folder layout, filename convention, readiness groups, and content rules for .canon/tasks/
34
34
  - [Teach reference](teach.md): Workspace layout, ordinal naming, frontmatter, and the mission and learning-record formats for a learning workspace
35
35
  - [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
36
36
  - [Wireframe reference](wireframes.md): Shape and content rules for .claude/wireframes/<surface>.md files
@@ -5,13 +5,13 @@ description: Folder layout, ordinal naming, reserved index number, frontmatter a
5
5
 
6
6
  # Intake reference
7
7
 
8
- Applies to an intake folder at `.claude/intake/<nn>-<slug>/`. One folder holds one dump, filed by domain, and every finding in it is an item carrying a measured problem, one proposed fix, and a verdict.
8
+ Applies to an intake folder at `.canon/intake/<nn>-<slug>/`. One folder holds one dump, filed by domain, and every finding in it is an item carrying a measured problem, one proposed fix, and a verdict.
9
9
 
10
10
  The folder is gitignored and unbacked. No check reaches its contents, so the shape below survives only by being read.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs an intake folder under `.claude/intake/<nn>-<slug>/`: folder layout, ordinal naming, the reserved index number, frontmatter and dating, the item format, the answer contract, and retrieval.
14
+ Governs an intake folder under `.canon/intake/<nn>-<slug>/`: folder layout, ordinal naming, the reserved index number, frontmatter and dating, the item format, the answer contract, and retrieval.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -25,7 +25,7 @@ Does not govern:
25
25
  ## Folder name
26
26
 
27
27
  - Name the folder `<nn>-<slug>`, a two-digit zero-padded ordinal followed by a kebab-case slug. This is the folder's own ordinal, distinct from the `NN-<domain>.md` numbering a cluster file carries inside it.
28
- - Take the ordinal from the highest one already present across both `.claude/intake/` and `.claude/groundwork/`, incremented. A listing then sorts by when each folder opened rather than alphabetically, and the count includes both kinds because the two share one creation-order line.
28
+ - Take the ordinal from the highest one already present across both `.canon/intake/` and `.canon/groundwork/`, incremented. A listing then sorts by when each folder opened rather than alphabetically, and the count includes both kinds because the two share one creation-order line.
29
29
  - With neither folder holding an entry, the first one opened takes `01`. Do not read this off the numbering inside a dump, which starts at `00` and disagrees with groundwork's own first required file.
30
30
  - Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
31
31
 
@@ -5,13 +5,13 @@ description: Filename and type prefix, frontmatter, the body shape per type, lin
5
5
 
6
6
  # Memory reference
7
7
 
8
- Applies to a memory entry at `.claude/memory/<type>-<slug>.md`. One file holds one rule or one fact, written at the end of the session that produced it and read by a session that holds none of it. Which surface owns a given fact is settled before an entry is written at all, and that routing is project policy rather than a shape rule.
8
+ Applies to a memory entry at `.canon/memory/<type>-<slug>.md`. One file holds one rule or one fact, written at the end of the session that produced it and read by a session that holds none of it. Which surface owns a given fact is settled before an entry is written at all, and that routing is project policy rather than a shape rule.
9
9
 
10
10
  The folder is gitignored and unbacked. Nothing recovers a deleted entry, which is why the retire step below is a move rather than a cleanup.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs a memory entry under `.claude/memory/<type>-<slug>.md`: the filename and its type prefix, the frontmatter, the body shape each type carries, links between entries, and the lifecycle from the first write to retirement.
14
+ Governs a memory entry under `.canon/memory/<type>-<slug>.md`: the filename and its type prefix, the frontmatter, the body shape each type carries, links between entries, and the lifecycle from the first write to retirement.
15
15
 
16
16
  Does not govern:
17
17
 
package/standards/plan.md CHANGED
@@ -5,13 +5,13 @@ description: Filename and slug, required sections, the suggested-and-answer cont
5
5
 
6
6
  # Plan reference
7
7
 
8
- Applies to a feature plan at `.claude/plans/feature-<slug>.md`. One file holds one concern, written before implementation starts and read by whatever executes it, so it has to carry the scope without the conversation that produced it.
8
+ Applies to a feature plan at `.canon/plans/feature-<slug>.md`. One file holds one concern, written before implementation starts and read by whatever executes it, so it has to carry the scope without the conversation that produced it.
9
9
 
10
10
  The folder is gitignored and unbacked. Nothing recovers a deleted plan, which is why the archive step below is a move rather than a cleanup.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs a feature plan under `.claude/plans/feature-<slug>.md`: the filename, the required sections, what each holds, the suggested-and-answer contract, and the lifecycle from the live folder to the archive.
14
+ Governs a feature plan under `.canon/plans/feature-<slug>.md`: the filename, the required sections, what each holds, the suggested-and-answer contract, and the lifecycle from the live folder to the archive.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -124,7 +124,7 @@ This contract inverts the one an intake folder keeps, where an empty slot means
124
124
  - Write the plan before implementation starts, and treat it as the scope of the run that executes it.
125
125
  - Keep every plan at one root. A plan copied into each parallel working tree forks, and the copies answer the same question differently.
126
126
  - Amend the plan in place when a decision changes mid-flight. Do not append a second passage narrating the change, which leaves a reader to work out which of two answers is current. An execution-time deviation from a suggestion is one such amendment, and the contract above fixes which line takes it.
127
- - Move the plan to `.claude/plans/archive/` when the work it describes ships. Never delete it, because the plan is where the rejected alternative is written down and nothing else records it. The archive nests inside `.claude/plans/` rather than sitting beside it as `.claude/plans-archive/`, so an older layout is a flat sibling by that name and a current one is not.
127
+ - Move the plan to `.canon/plans/archive/` when the work it describes ships. Never delete it, because the plan is where the rejected alternative is written down and nothing else records it. The archive nests inside `.canon/plans/` rather than sitting beside it as `.claude/plans-archive/`, so an older layout is a flat sibling by that name and a current one is not.
128
128
  - Write the plan in the same session that opens the task it serves. The session executing it later inherits reasoning it would otherwise re-derive.
129
129
 
130
130
  ## Anti-patterns
@@ -5,13 +5,13 @@ description: Filename and location, the sections a handoff carries, the write an
5
5
 
6
6
  # Session map reference
7
7
 
8
- Applies to `.claude/tasks/session-<slug>.md`. A session writes one before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them. It is rewritten whenever the session that owns it learns something the next session would otherwise re-derive, and it is optional: a project whose sessions never approach a compaction carries none.
8
+ Applies to `.canon/tasks/session-<slug>.md`. A session writes one before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them. It is rewritten whenever the session that owns it learns something the next session would otherwise re-derive, and it is optional: a project whose sessions never approach a compaction carries none.
9
9
 
10
10
  Any session writes one. The role a session holds decides which sections it adds on top of the core, never whether it may write at all.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs the pre-compaction handoff at `.claude/tasks/session-<slug>.md`: its filename, its sections, what a writer puts in each, and how a reader picks one up.
14
+ Governs the pre-compaction handoff at `.canon/tasks/session-<slug>.md`: its filename, its sections, what a writer puts in each, and how a reader picks one up.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -48,7 +48,7 @@ A standard failing these questions is non-conforming even when it satisfies ever
48
48
 
49
49
  ### Naming the file
50
50
 
51
- - Name the file for the artifact the scope statement governs, taking the word from the deepest path segment that names the artifact rather than the folder holding it. `plan.md` over `.claude/plans/feature-<slug>.md` and `session.md` over `.claude/tasks/session-<slug>.md` both follow it.
51
+ - Name the file for the artifact the scope statement governs, taking the word from the deepest path segment that names the artifact rather than the folder holding it. `plan.md` over `.canon/plans/feature-<slug>.md` and `session.md` over `.canon/tasks/session-<slug>.md` both follow it.
52
52
  - Take the singular or the plural of that word, whichever reads as the artifact. A standard over one document takes the singular, and one over a set may take either.
53
53
  - Name an attribute standard for the attribute itself, since it governs no path to derive a word from.
54
54
  - Write the derived word alone. A standard installs as a sibling and is cited by bare filename, so a qualifier makes an outlier that every later citation has to carry.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: Tasks reference
3
- description: Folder layout, filename convention, readiness groups, and content rules for .claude/tasks/
3
+ description: Folder layout, filename convention, readiness groups, and content rules for .canon/tasks/
4
4
  ---
5
5
 
6
6
  # Tasks reference
7
7
 
8
- Applies to `.claude/tasks/`. Tracks what is being built and why, at the level of features and outcomes. One file per task.
8
+ Applies to `.canon/tasks/`. Tracks what is being built and why, at the level of features and outcomes. One file per task.
9
9
 
10
10
  Update when a task starts, completes, or changes scope. When to open a task at all is project policy, not a shape rule, and lives in `CLAUDE.md`.
11
11
 
@@ -13,7 +13,7 @@ The folder is gitignored. Board state changes when work ships rather than when a
13
13
 
14
14
  ## Scope
15
15
 
16
- Governs the task board under `.claude/tasks/`: folder layout, filenames, frontmatter, file format, origin lines, execution ordering, the backlog beside it, and archiving.
16
+ Governs the task board under `.canon/tasks/`: folder layout, filenames, frontmatter, file format, origin lines, execution ordering, the backlog beside it, and archiving.
17
17
 
18
18
  Does not govern:
19
19
 
@@ -26,7 +26,7 @@ Does not govern:
26
26
  ## Layout
27
27
 
28
28
  ```plaintext
29
- .claude/tasks/
29
+ .canon/tasks/
30
30
  ├── index.md ← generated, never hand-edited
31
31
  ├── priority.md ← hand-maintained execution order
32
32
  ├── backlog.md ← unordered, what is not being scheduled
@@ -205,13 +205,13 @@ A task with no origin is either lost context or work nobody decided to do. The i
205
205
 
206
206
  An intake folder answers that direction at folder scope rather than item scope, since one dump dispositions many items and most close without ever becoming a task. What names a folder is every item answered and no task citing it, on the board or in the archive. That is a dump nobody acted on. Counting the archive beside the board is what separates it from one already promoted and shipped, and a check reading the board alone calls every finished folder abandoned.
207
207
 
208
- `Plan:`, `Groundwork:`, and `Intake:` name their target as a markdown link whose text is the file or folder stem, so the line resolves on a ctrl-click the way `priority.md` rows already do. Write the path relative to `.claude/tasks/`, which makes it `../plans/`, `../groundwork/`, and `../intake/`. A path written from the project root renders as a link and resolves to nothing in an editor rooted at the project. `Issue:` stays a bare `#NNN`, since an issue number is not a path and a full URL would write the remote into a gitignored file.
208
+ `Plan:`, `Groundwork:`, and `Intake:` name their target as a markdown link whose text is the file or folder stem, so the line resolves on a ctrl-click the way `priority.md` rows already do. Write the path relative to `.canon/tasks/`, which makes it `../plans/`, `../groundwork/`, and `../intake/`. A path written from the project root renders as a link and resolves to nothing in an editor rooted at the project. `Issue:` stays a bare `#NNN`, since an issue number is not a path and a full URL would write the remote into a gitignored file.
209
209
 
210
210
  Phase-label format and where labels may appear are governed by `standards/versioning.md`.
211
211
 
212
212
  `Plan:` points at `../plans/feature-<slug>.md` while the task is open. Once the task ships and the plan is archived, it points at `../plans/archive/feature-<slug>.md`, and at `../../plans/archive/feature-<slug>.md` once the task itself is archived a folder deeper. Retarget both halves of the link rather than dropping it, so a completed task still leads to the reasoning behind it.
213
213
 
214
- A project that archived plans before the folder nested under `.claude/plans/` holds closed tasks pointing at `../plans-archive/`, or at `../.tmp/plans-archive/` from before the durable records left the scratch tree. Each form resolves against the files it names, so leave those pointers where they are. A named route now moves the folder and retargets its pointers together, but no automation runs it, so an unmigrated project keeps holding the old spelling until someone does, and a task retargeted without its plan moving leads nowhere.
214
+ A project that archived plans before the folder nested under `.canon/plans/` holds closed tasks pointing at `../plans-archive/`, or at `../.tmp/plans-archive/` from before the durable records left the scratch tree. Each form resolves against the files it names, so leave those pointers where they are. A named route now moves the folder and retargets its pointers together, but no automation runs it, so an unmigrated project keeps holding the old spelling until someone does, and a task retargeted without its plan moving leads nowhere.
215
215
 
216
216
  One plan per task. A plan cited by two tasks is a misfile rather than a shape to design for, which is why the sweep counts citations before archiving: the count is a guard against the misfile stranding a pointer, not support for the shape.
217
217
 
@@ -241,11 +241,11 @@ The line is what lets a merge close its own task. Every merge on `main` is a squ
241
241
 
242
242
  ## Archiving
243
243
 
244
- Never delete a task file. A shipped task moves to `.claude/tasks/archive/` under its own name, and the live index regenerates without it. `canon tasks archive` owns that move, and what it does and what it refuses on are at `docs/agents/tasks.md`.
244
+ Never delete a task file. A shipped task moves to `.canon/tasks/archive/` under its own name, and the live index regenerates without it. `canon tasks archive` owns that move, and what it does and what it refuses on are at `docs/agents/tasks.md`.
245
245
 
246
- The archive nests inside `.claude/tasks/` rather than sitting beside it as a flat `.claude/task-archive/`. Nesting is what lets a reader tell the two shapes apart on sight: the flat sibling is what a binary predating this convention still writes, so meeting one names an older checkout rather than a second archive to reconcile against this one.
246
+ The archive nests inside `.canon/tasks/` rather than sitting beside it as a flat `.claude/task-archive/`. Nesting is what lets a reader tell the two shapes apart on sight: the flat sibling is what a binary predating this convention still writes, so meeting one names an older checkout rather than a second archive to reconcile against this one.
247
247
 
248
- One destination rather than a per-project choice is what lets the move happen without asking. It mirrors the plans archive at `.claude/plans/archive/`, sitting inside the folder it archives the same way, and it inherits the board's own ignore entry rather than needing one of its own. The cost is that an archived task does not appear in diffs, which is the cost the live board already carries.
248
+ One destination rather than a per-project choice is what lets the move happen without asking. It mirrors the plans archive at `.canon/plans/archive/`, sitting inside the folder it archives the same way, and it inherits the board's own ignore entry rather than needing one of its own. The cost is that an archived task does not appear in diffs, which is the cost the live board already carries.
249
249
 
250
250
  Archiving a task does not archive its plan. `claude-docs` owns the plans sweep and moves a plan only when the closing task is its last live citation, so the sweep runs before the archive rather than after it. The sweep finds its work by scanning the live folder, and a task archived first is beyond its reach for good, leaving the plan with no live task citing it and an archived task pointing at a path nothing will retarget.
251
251
 
@@ -5,13 +5,13 @@ description: Workspace layout, ordinal naming, frontmatter, and the mission and
5
5
 
6
6
  # Teach reference
7
7
 
8
- Applies to a learning workspace at `.claude/teach/<nn>-<topic>/`. One workspace holds one subject studied across sessions, and it carries both halves of what studying produces: the durable reference material a reader consults later, and the disposable lessons a learner works through once.
8
+ Applies to a learning workspace at `.canon/teach/<nn>-<topic>/`. One workspace holds one subject studied across sessions, and it carries both halves of what studying produces: the durable reference material a reader consults later, and the disposable lessons a learner works through once.
9
9
 
10
10
  The folder is gitignored. Its markdown half is written in a format the authoring gates read, so a page promoted out of it later needs no conversion.
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs a learning workspace under `.claude/teach/<nn>-<topic>/`: folder layout, ordinal naming, frontmatter, and the mission and learning-record formats.
14
+ Governs a learning workspace under `.canon/teach/<nn>-<topic>/`: folder layout, ordinal naming, frontmatter, and the mission and learning-record formats.
15
15
 
16
16
  Does not govern:
17
17
 
@@ -40,7 +40,7 @@ External release identity used in git tags and release notes. Independent of pha
40
40
 
41
41
  | Surface | Phase labels | Semver tags |
42
42
  | ------------------------- | ------------ | ----------------------------------- |
43
- | `.claude/tasks/` | yes | no |
43
+ | `.canon/tasks/` | yes | no |
44
44
  | Chat with the operator | yes | no |
45
45
  | PR titles | no | only when the PR cuts a release |
46
46
  | PR bodies | no | only when the PR cuts a release |
@@ -27,7 +27,7 @@ root=$(git worktree list --porcelain 2>/dev/null | grep -m 1 '^worktree ' | cut
27
27
  # The board is shared scratch at the main worktree root, and `git worktree
28
28
  # list` puts that root first. A pull inside a linked worktree fires this same
29
29
  # hook, so trusting the working directory would read a board that is not there.
30
- board="$root/.claude/tasks"
30
+ board="$root/.canon/tasks"
31
31
  [ -d "$board" ] || exit 0
32
32
 
33
33
  closed=""
@@ -50,7 +50,7 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
50
50
  - `pre-commit` runs `bunx lint-staged`.
51
51
  - `commit-msg` runs `bunx commitlint --edit "$1"`.
52
52
  - `pre-push` runs `bun run check`.
53
- - `post-merge` names `.claude/tasks/` archive candidates, staying silent otherwise and when the board is absent.
53
+ - `post-merge` names `.canon/tasks/` archive candidates, staying silent otherwise and when the board is absent.
54
54
  - `post-rewrite` delegates to `post-merge` on `rebase`, so a `pull.rebase=true` machine still gets the check.
55
55
 
56
56
  ## lint-staged
@@ -8,7 +8,17 @@ runtime = ""
8
8
  scaffold = ""
9
9
 
10
10
  # Checked against this repository's own `.gitignore` by
11
- # scripts/core/check-ignore-parity.sh, which also holds the two `.claude/` paths
12
- # this array deliberately omits and the reason each stays out.
11
+ # scripts/core/check-ignore-parity.sh, which carries no sanctioned divergence
12
+ # now that both sides ship the same two entries.
13
+ #
14
+ # One root entry rather than a folder apiece, so a target that gains a record
15
+ # folder later receives no manifest edit and no sync. `.claude/worktrees/` is
16
+ # the carve-out, since the harness requires a worktree to sit there.
17
+ #
18
+ # The cost is recorded rather than mitigated, and the operator took it on
19
+ # 2026-09-01 against the two alternatives. A target that syncs and never runs
20
+ # `canon migrate records` still holds its records under `.claude/`, and this
21
+ # array no longer tells it to ignore them, so the first sign is a memory file or
22
+ # a task board appearing in someone's commit.
13
23
  [gitignore]
14
- "# Claude" = [".canon/", ".claude/.records.git/", ".claude/.tmp/", ".claude/groundwork/", ".claude/intake/", ".claude/memory/", ".claude/plans/", ".claude/proposals/", ".claude/review/", ".claude/worktrees/", ".claude/tasks/", ".claude/teach/"]
24
+ "# Claude" = [".canon/", ".claude/worktrees/"]