@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.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-feedback-file/SKILL.md +3 -3
- package/claude/skills/canon-feedback-triage/SKILL.md +1 -1
- package/claude/skills/canon-rollout/SKILL.md +1 -1
- package/claude/skills/canon-screencast/SKILL.md +5 -5
- package/claude/skills/canon-slides-draft/SKILL.md +1 -1
- package/claude/skills/claude-address-review/SKILL.md +4 -4
- package/claude/skills/claude-autoship/SKILL.md +17 -17
- package/claude/skills/claude-design-extract/SKILL.md +2 -2
- package/claude/skills/claude-diagram/SKILL.md +13 -13
- package/claude/skills/claude-docs/SKILL.md +23 -23
- package/claude/skills/claude-feature/SKILL.md +6 -6
- package/claude/skills/claude-groundwork/SKILL.md +16 -16
- package/claude/skills/claude-intake/SKILL.md +8 -8
- package/claude/skills/claude-intake-answer/SKILL.md +1 -1
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +2 -2
- package/claude/skills/claude-markdown-propose/SKILL.md +8 -8
- package/claude/skills/claude-markdown-propose/references/format.md +3 -3
- package/claude/skills/claude-memory-capture/SKILL.md +13 -13
- package/claude/skills/claude-memory-review/SKILL.md +28 -28
- package/claude/skills/claude-memory-review/references/receipt-format.md +1 -1
- package/claude/skills/claude-orchestrate/SKILL.md +9 -9
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +1 -1
- package/claude/skills/claude-orchestrate/scripts/poll.sh +1 -1
- package/claude/skills/claude-planner/REQUIREMENT.md +1 -1
- package/claude/skills/claude-planner/SKILL.md +6 -6
- package/claude/skills/claude-pr-review/SKILL.md +5 -5
- package/claude/skills/claude-review/SKILL.md +4 -4
- package/claude/skills/claude-seed-sync/SKILL.md +2 -2
- package/claude/skills/claude-tasks/SKILL.md +16 -16
- package/claude/skills/claude-teach/SKILL.md +9 -9
- package/claude/skills/claude-ui-test/SKILL.md +3 -3
- package/claude/skills/claude-ux-audit/SKILL.md +5 -5
- package/claude/skills/claude-ux-measure/SKILL.md +3 -3
- package/claude/skills/claude-worker/SKILL.md +2 -2
- package/claude/skills/claude-worktree/SKILL.md +3 -3
- package/claude/skills/git-issue/SKILL.md +4 -4
- package/claude/skills/git-pr/SKILL.md +6 -6
- package/claude/skills/git-ship/SKILL.md +3 -3
- package/claude/skills/git-split/REQUIREMENT.md +1 -1
- package/claude/skills/git-split/SKILL.md +20 -20
- package/claude/skills/migration-superseded/SKILL.md +4 -4
- package/claude/skills/session-map/SKILL.md +2 -2
- package/claude/skills/session-resume/SKILL.md +8 -8
- package/docs/agents/capture.md +17 -11
- package/docs/agents/commands.md +8 -5
- package/docs/agents/demo.md +2 -2
- package/docs/agents/driver.md +2 -2
- package/docs/agents/index.md +1 -1
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +1 -1
- package/docs/agents/intake.md +1 -1
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/records.md +12 -9
- package/docs/agents/restated.md +2 -2
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/tasks.md +6 -6
- package/docs/agents/teach.md +4 -4
- package/docs/ai-workflow.md +26 -19
- package/docs/operating-model.md +3 -3
- package/docs/target-projects.md +22 -2
- package/docs/visual-design-workflow.md +1 -1
- package/governance/rules/claude/555-tasks.md +3 -3
- package/governance/rules/claude/556-groundwork.md +2 -2
- package/governance/rules/claude/557-intake.md +2 -2
- package/governance/rules/claude/558-plan.md +3 -3
- package/governance/rules/claude/559-memory.md +2 -2
- package/governance/rules/claude/560-diagrams.md +3 -3
- package/governance/rules/claude/561-teach.md +2 -2
- package/governance/rules/claude/562-session.md +2 -2
- package/governance/rules/core/035-tasks.md +2 -2
- package/governance/rules/core/045-memory.md +3 -3
- package/governance/rules/core/055-scratch.md +2 -1
- package/governance/rules/core/085-worktrees.md +1 -1
- package/package.json +1 -2
- package/scripts/core/check-ignore-parity.sh +24 -51
- package/scripts/core/install-check.sh +4 -4
- package/scripts/core/regen-hero.sh +6 -3
- package/scripts/core/snapshot.sh +1 -1
- package/scripts/manage-sandbox.sh +1 -1
- package/scripts/tooling/verify.sh +2 -2
- package/snippets/claude/decision-memo.md +1 -1
- package/snippets/claude/feature-recap.md +1 -1
- package/src/audits/catalog.ts +1 -1
- package/src/capture/render.ts +166 -0
- package/src/capture/sources.ts +42 -0
- package/src/capture/stamp.ts +52 -0
- package/src/claude/cases/all.ts +1 -1
- package/src/claude/seeds.ts +13 -2
- package/src/claude/skills-rank.ts +1 -1
- package/src/cli.ts +5 -5
- package/src/commands/capture.ts +52 -41
- package/src/commands/demo.ts +1 -1
- package/src/commands/design.ts +1 -1
- package/src/commands/feedback.ts +5 -5
- package/src/commands/gov.ts +17 -6
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +173 -2
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +1 -1
- package/src/commands/serve.ts +2 -2
- package/src/commands/slides.ts +1 -1
- package/src/commands/tasks.ts +5 -5
- package/src/commands/teach.ts +1 -1
- package/src/context/audit.ts +1 -1
- package/src/demo/compile.ts +1 -1
- package/src/demo/drive.ts +5 -8
- package/src/driver/drive.ts +7 -8
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/restated.ts +3 -3
- package/src/inventory/walk.ts +3 -3
- package/src/migrate/apply.ts +67 -1
- package/src/migrate/records.ts +276 -0
- package/src/paths.ts +1 -1
- package/src/pr/paths.ts +1 -1
- package/src/record-root.ts +77 -27
- package/src/records/backup.ts +71 -17
- package/src/records/validate.ts +1 -1
- package/src/sync/layout.ts +11 -2
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +20 -3
- package/standards/diagrams.md +4 -4
- package/standards/glossary.md +1 -1
- package/standards/groundwork.md +3 -3
- package/standards/index.md +2 -2
- package/standards/intake.md +3 -3
- package/standards/memory.md +2 -2
- package/standards/plan.md +3 -3
- package/standards/session.md +2 -2
- package/standards/standard.md +1 -1
- package/standards/tasks.md +9 -9
- package/standards/teach.md +2 -2
- package/standards/versioning.md +1 -1
- package/tooling/base/configs/.husky/post-merge +1 -1
- package/tooling/base/reference.md +1 -1
- package/tooling/claude/manifest.toml +13 -3
- package/tooling/claude/reference.md +28 -15
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +8 -8
package/src/records/backup.ts
CHANGED
|
@@ -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
|
|
10
|
-
* whichever
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
package/src/records/validate.ts
CHANGED
|
@@ -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 `.
|
|
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
|
package/src/sync/layout.ts
CHANGED
|
@@ -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
|
-
* `.
|
|
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
|
-
|
|
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
|
package/src/tasks/answers.ts
CHANGED
|
@@ -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 `.
|
|
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
|
*
|
package/src/tasks/archive.ts
CHANGED
|
@@ -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 `.
|
|
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
|
package/standards/diagrams.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Diagram reference
|
|
3
|
-
description: Shape and content rules for .
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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
|
|
package/standards/glossary.md
CHANGED
|
@@ -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 `.
|
|
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
|
|
package/standards/groundwork.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
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 `.
|
|
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
|
|
package/standards/index.md
CHANGED
|
@@ -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 .
|
|
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 .
|
|
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
|
package/standards/intake.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
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 `.
|
|
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
|
|
package/standards/memory.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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
|
package/standards/session.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
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
|
|
package/standards/standard.md
CHANGED
|
@@ -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 `.
|
|
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.
|
package/standards/tasks.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Tasks reference
|
|
3
|
-
description: Folder layout, filename convention, readiness groups, and content rules for .
|
|
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 `.
|
|
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 `.
|
|
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
|
-
.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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
|
|
package/standards/teach.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
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
|
|
package/standards/versioning.md
CHANGED
|
@@ -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
|
-
| `.
|
|
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/.
|
|
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 `.
|
|
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
|
|
12
|
-
#
|
|
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
|
|
24
|
+
"# Claude" = [".canon/", ".claude/worktrees/"]
|