@erclx/canon 4.121.0 → 4.123.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/auto-ship/SKILL.md +6 -5
- package/claude/skills/auto-ship/references/tier-failures.md +7 -4
- package/claude/skills/backlog-triage/REQUIREMENT.md +47 -0
- package/claude/skills/backlog-triage/SKILL.md +139 -0
- package/claude/skills/canon-rollout/SKILL.md +6 -6
- package/claude/skills/context-fold/REQUIREMENT.md +75 -0
- package/claude/skills/context-fold/SKILL.md +162 -0
- package/claude/skills/{docs-fold → context-fold}/references/anchor-sweep.md +1 -1
- package/claude/skills/{docs-fold → context-fold}/references/classify.md +4 -2
- package/claude/skills/context-fold/references/context-refresh.md +48 -0
- package/claude/skills/context-fold/references/promoted-pages.md +29 -0
- package/claude/skills/context-fold/references/receipt-sweep.md +43 -0
- package/claude/skills/context-fold/references/unusable-baseline.md +25 -0
- package/claude/skills/{docs-fold → context-fold}/references/wireframe-sweep.md +1 -1
- package/claude/skills/docs-fold/REQUIREMENT.md +8 -58
- package/claude/skills/docs-fold/SKILL.md +3 -278
- package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
- package/claude/skills/docs-sync/SKILL.md +1 -1
- package/claude/skills/draft-context/REQUIREMENT.md +2 -2
- package/claude/skills/draft-context/SKILL.md +3 -3
- package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/SKILL.md +1 -1
- package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
- package/claude/skills/draft-wireframes/SKILL.md +2 -2
- package/claude/skills/git-pr/SKILL.md +8 -146
- package/claude/skills/git-pr/references/evidence.md +114 -0
- package/claude/skills/git-pr/references/labels.md +20 -0
- package/claude/skills/git-pr/references/task-number.md +26 -0
- package/claude/skills/git-ship/SKILL.md +5 -4
- package/claude/skills/memory-capture/REQUIREMENT.md +2 -2
- package/claude/skills/memory-capture/SKILL.md +19 -9
- package/claude/skills/memory-review/REQUIREMENT.md +5 -3
- package/claude/skills/memory-review/SKILL.md +22 -107
- package/claude/skills/memory-review/references/later-phases.md +111 -0
- package/claude/skills/memory-review/references/receipt-format.md +19 -3
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +1 -1
- package/claude/skills/review-branch/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +4 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
- package/claude/skills/session-resume/REQUIREMENT.md +1 -1
- package/claude/skills/session-resume/SKILL.md +4 -3
- package/claude/skills/session-worktree/SKILL.md +4 -10
- package/claude/skills/task-board/REQUIREMENT.md +1 -1
- package/claude/skills/task-board/SKILL.md +7 -81
- package/claude/skills/task-board/references/archive.md +54 -0
- package/claude/skills/task-board/references/decline.md +40 -0
- package/claude/skills/teach-workspace/SKILL.md +8 -105
- package/claude/skills/teach-workspace/references/lesson-craft.md +51 -1
- package/claude/skills/teach-workspace/references/promotion.md +41 -0
- package/docs/agents/architecture-record.md +26 -0
- package/docs/agents/commands.md +87 -154
- package/docs/agents/context-audit-checks.md +2 -20
- package/docs/agents/context-audit.md +1 -1
- package/docs/agents/context-classify.md +1 -1
- package/docs/agents/drift-surfaces.md +192 -0
- package/docs/agents/index.md +12 -4
- package/docs/agents/install-and-sync.md +2 -181
- package/docs/agents/markdown-audit-weight.md +73 -0
- package/docs/agents/markdown-audit.md +3 -90
- package/docs/agents/migrate.md +34 -0
- package/docs/agents/records-stale.md +62 -0
- package/docs/agents/records.md +5 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/tasks-archive.md +81 -0
- package/docs/agents/tasks-plans.md +149 -0
- package/docs/agents/tasks-validate.md +131 -0
- package/docs/agents/tasks.md +7 -334
- package/docs/target-projects.md +3 -3
- package/docs/workflow/ai-workflow.md +34 -29
- package/docs/workflow/operating-model.md +1 -1
- package/governance/rules/claude/555-tasks.md +2 -1
- package/governance/rules/core/045-memory.md +1 -1
- package/package.json +1 -1
- package/src/claude/cases/setup.ts +1 -1
- package/src/claude/cases/workflow.ts +22 -1
- package/src/commands/records.ts +146 -0
- package/src/commands/sandbox.ts +6 -4
- package/src/commands/tasks.ts +39 -29
- package/src/context/classify/extract.ts +1 -1
- package/src/context/classify/ollama.ts +1 -1
- package/src/context/classify/run.ts +1 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +1 -0
- package/src/migrate/skill-names.ts +15 -2
- package/src/records/stale.ts +243 -0
- package/src/sandbox/expect.ts +1 -1
- package/src/tasks/archive.ts +223 -92
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/record.ts +60 -17
- package/src/tasks/validate.ts +37 -4
- package/standards/board.md +136 -0
- package/standards/commit.md +5 -0
- package/standards/groundwork.md +25 -36
- package/standards/index.md +2 -1
- package/standards/memory.md +13 -1
- package/standards/plan.md +20 -34
- package/standards/readme.md +8 -77
- package/standards/requirements.md +2 -2
- package/standards/tasks.md +19 -126
- package/standards/versioning.md +1 -1
- package/tooling/astro/reference.md +1 -1
- package/tooling/base/configs/.husky/post-merge +1 -1
- package/tooling/base/configs/commitlint.config.js +27 -1
- package/tooling/base/reference.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/nextjs/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +1 -1
- package/tooling/vite-react/reference.md +1 -1
- package/tooling/web/reference.md +1 -1
|
@@ -57,72 +57,9 @@ A relative link's destination resolves against the filesystem, over the same cor
|
|
|
57
57
|
|
|
58
58
|
The placeholder exemption is the one live case the corpus needs. Every current instance is a genuine illustration rather than a literal path, and a marker-based override is available for the day a real one needs an exception instead.
|
|
59
59
|
|
|
60
|
-
###
|
|
60
|
+
### Weight, cadence, and length
|
|
61
61
|
|
|
62
|
-
Bullet
|
|
63
|
-
|
|
64
|
-
Every weight and depth measure counts the text a reader is shown. A link reduces to its anchor text and an autolink drops whole, since no reader is shown either destination. A backticked path stays counted, which is where these measures part from the ban scan above: that one blanks a code span so a standard quoting its own banned character does not report itself, and discounting the same span here would under-report a paragraph carrying several. One file holds both span sets and each answers its own question.
|
|
65
|
-
|
|
66
|
-
A code span is walked around rather than through, so a path quoting link or angle-bracket syntax keeps the width the page gives it. Masking inside one takes back the decision to count it, and the placeholders this toolkit writes are where that shows.
|
|
67
|
-
|
|
68
|
-
The paragraph check measures both halves of one rule. `markdown.md` caps a paragraph at four sentences, and a sentence cap on its own is satisfied by writing fewer and longer ones: 15 paragraphs in this corpus sit inside four sentences and past the weight checkpoint, and the heaviest of those runs 886 characters. The standard therefore states a weight beside the sentence cap, and the verb reads it as its own checkpoint.
|
|
69
|
-
|
|
70
|
-
A sentence boundary closes on terminal punctuation ahead of a capital or a code span. The capital is what keeps a version pin and a decimal from each reading as two sentences, and the code span is admitted beside it because a command name opening a sentence carries no capital to find.
|
|
71
|
-
|
|
72
|
-
The paragraph weight sits at 700 and the bullet weight at 400. Both shipped at 400, because the paragraph number was borrowed from the bullet rule when the two checks landed together, and each has since been read against a sample of its own. They are separate checkpoints in the standard and separate patterns in the parser, so a read that moves one leaves the other where it is.
|
|
73
|
-
|
|
74
|
-
#### The sample behind the paragraph number
|
|
75
|
-
|
|
76
|
-
The checkpoint shipped at 400 as a borrowed number and was decided against a read of the prose it reports. Thirty-six findings were sampled, six from each of six weight bands, drawn at even spacing through each band ordered by path and line, and each was classed as prose a reader wants split or prose the checkpoint should not have reported.
|
|
77
|
-
|
|
78
|
-
Every band below was measured before the scan stopped counting link syntax as prose, so a paragraph sitting in one of these bands is heavier than a paragraph reported at the same number today. The re-sample in the section below re-reads the same range against the corrected measure and reaches the opposite verdict on it, which is the measure moving rather than the reader.
|
|
79
|
-
|
|
80
|
-
| Band | Wants the split | Reads as written |
|
|
81
|
-
| --------- | --------------- | ---------------- |
|
|
82
|
-
| 400 - 425 | 1 | 5 |
|
|
83
|
-
| 425 - 450 | 2 | 4 |
|
|
84
|
-
| 450 - 500 | 2 | 4 |
|
|
85
|
-
| 500 - 600 | 2 | 4 |
|
|
86
|
-
| 600 - 750 | 6 | 0 |
|
|
87
|
-
| Past 750 | 6 | 0 |
|
|
88
|
-
|
|
89
|
-
Precision is what moved the number rather than the finding count. Below 600 the checkpoint was right about seven of twenty-four sampled paragraphs, and past 600 it was right about all twelve. A checkpoint is a prompt to look, and a prompt wrong three times in four teaches a reader to stop looking. The distribution offers no seam to place the number against, with a median of 487 and a seventy-fifth percentile of 563, so the read is the whole of the evidence.
|
|
90
|
-
|
|
91
|
-
Nothing inside the 500 to 600 band separated the two classes by length, which is the reason the number did not land there. The two paragraphs wanting a split ran 543 and 590 characters against four reading well at 515, 532, 555, and 569.
|
|
92
|
-
|
|
93
|
-
The sample is thirty-six paragraphs against a reported population in the hundreds, and one reader classed all of them. Treat a band's rate as the order of magnitude it is rather than as a measured precision, and re-sample before moving the number again.
|
|
94
|
-
|
|
95
|
-
#### The re-sample that moved the number to 700
|
|
96
|
-
|
|
97
|
-
That re-sample ran once the scan stopped counting link syntax as prose. Findings at 604, 633, and 677 characters each read as an ordinary four-sentence paragraph on one topic, density arrived around 760 and was plain by 860, and the move cut the weight half of the report roughly in half while leaving the sentence cap untouched.
|
|
98
|
-
|
|
99
|
-
A bullet, a heading, a table row, a blockquote, a blank line, and a fence each end a paragraph, so a heavy bullet is reported by the bullet check alone and never counted twice.
|
|
100
|
-
|
|
101
|
-
### Cadence
|
|
102
|
-
|
|
103
|
-
Uniform cadence is the failure a ban list cannot express. A ban set states negatives, and fragments, verbless clauses, and sentences that all run one length are each the absence of something, so no word added to a ban list reaches any of them. The shape layer already measured a bullet, a paragraph, and a run, and stopped one level above where that failure lives.
|
|
104
|
-
|
|
105
|
-
Cadence measures a paragraph on two numbers. The spread is the words between its longest and shortest sentence, and the opener count is the times one word opens a sentence in it. A spread of five words or under reads as one cadence, and a word opening more than two sentences is a pattern rather than a coincidence. Both come from `## Rhythm` in the `write-human` skill, which states them about prose a person reads, and this measures against that statement rather than setting a threshold of its own.
|
|
106
|
-
|
|
107
|
-
Words are counted off the text a reader is shown. A link contributes its anchor text, an autolink contributes nothing, and the sentence boundaries do not move under that masking, since the boundary pattern requires whitespace after the terminal punctuation and no destination carries any. An opening word is lowercased and stripped of punctuation, so a sentence opening on a backticked command name reports the command.
|
|
108
|
-
|
|
109
|
-
A paragraph carrying fewer than three sentences is skipped rather than scored. A two-sentence configuration note has no spread worth reading, and the opener rule is written about a third sentence turning a coincidence into a pattern, so neither number says anything before the floor. That is the cheap form of a wider exemption: a shape-aware one, exempting a short reference block by what it is rather than by how many sentences it holds, waits on a second case.
|
|
110
|
-
|
|
111
|
-
The unit is the paragraph and each file names its worst on each measure, which follows the depth check rather than setting a precedent. A file's flattest paragraph and its most repetitive one are named only when each crosses its checkpoint, so a file reading healthy names nothing rather than offering its least healthy paragraph as a finding.
|
|
112
|
-
|
|
113
|
-
Neither number gates and neither names a file wrong. This is a weaker claim than the one the weight checkpoints make, because a healthy range differs by surface: a catalog entry is several short sentences carrying one fact each, and a page arguing a decision is not, so one range applied across the corpus would report the surfaces that are correct. The run therefore states where the numbers came from beside them, and the counts are what a reader compares against.
|
|
114
|
-
|
|
115
|
-
A reading of that spread across the whole corpus is what backs the range above, taken once against this repository's own tracked markdown rather than against a target project's.
|
|
116
|
-
|
|
117
|
-
That reading travels with the command rather than staying here. `BASELINE` in `src/markdown/structure.ts` carries the overall share, the per-file range, and the ten-paragraph floor beneath which a file's own rate says nothing, and the run prints all four in the legend beside the rate it measured. A count with no range beside it reads as a finding, and naming that a healthy range differs by surface states that a range exists rather than what it looks like. This page is toolkit-internal, so a reader running the command in a project that installed no standards would otherwise have two counts and nothing to place them against.
|
|
118
|
-
|
|
119
|
-
Two of the rules `write-human` states are deliberately not implemented. A sentence's grammatical shape and whether it carries a finite verb each need a parse rather than a match, and an imperative would read as a defect under a pattern that approximated either. The verbless share is the measure closest to the reported symptom, which is exactly why shipping it wrong would discredit the two that hold.
|
|
120
|
-
|
|
121
|
-
The condition on that was something identifying a finite verb rather than guessing at one, and two parsers have now been run against it over 11,389 paragraph sentences. They disagree by a factor of four. `compromise` reports 2 percent and reads a fronted past participle as a finite verb, so `Measured at <sha> on <date>.` counts as carrying one. `wink-pos-tagger` reports 9 percent, fixes that class, and is still wrong on roughly three in four, because an imperative's verb tags as a proper noun and a noun-ambiguous predicate tags as a noun, which makes `Each maps to a skill.` read verbless. Separating those needs to know which token is the predicate, and that is syntax rather than a tag. The measure stays unimplemented, now against a mechanism rather than against the idea of one.
|
|
122
|
-
|
|
123
|
-
### Length
|
|
124
|
-
|
|
125
|
-
The Length step sums rendered lines over the whole source, frontmatter and fenced blocks included, and lists each document past the 300-line ceiling, longest first. A changelog and a document carrying a whole-line `<!-- canon-length-exempt: <reason> -->` outside a fence are counted in one line rather than listed. Each `--json` entry carries `renderedLines` and `exempt`, the stated reason or `null`, and `checkpoints` carries `ceiling`.
|
|
62
|
+
Bullet and paragraph weight, run depth, the two cadence numbers, and the document length ceiling are each a judgment a reader settles rather than a fact a scan settles. What each measures, the samples behind its checkpoint, and why none of them gates are in `markdown-audit-weight.md`.
|
|
126
63
|
|
|
127
64
|
## Exit codes
|
|
128
65
|
|
|
@@ -146,7 +83,7 @@ Rewrite the sentence rather than swapping the banned token for a near-synonym. T
|
|
|
146
83
|
|
|
147
84
|
A code span clears the report too, since the ban scan walks around one, and it is the answer only where the token is genuinely an identifier under discussion. `## Code and identifiers` in `markdown.md` reserves the span for commands, API names, file paths, and identifiers, so backticking a quoted utterance spends one rule to satisfy another and leaves the corpus no cleaner.
|
|
148
85
|
|
|
149
|
-
A hit the closed set could not separate from correct prose was the case with no third option while the set still carried words. The temporal `just` reported as the vague qualifier it happened to spell, and rewriting the sentence is what the toolkit settled on over building an exemption path,
|
|
86
|
+
A hit the closed set could not separate from correct prose was the case with no third option while the set still carried words. The temporal `just` reported as the vague qualifier it happened to spell, and rewriting the sentence is what the toolkit settled on over building an exemption path, since an exemption landing in the verb and not the edit hook leaves the line failing where an author actually meets it. That class is part of why the words left the gate.
|
|
150
87
|
|
|
151
88
|
### Where the rules are enforced
|
|
152
89
|
|
|
@@ -170,30 +107,6 @@ A machine with neither runner still blocks no edit, and it says so rather than e
|
|
|
170
107
|
|
|
171
108
|
The stage measures the whole corpus rather than the changed files. A `Do not use` bullet added to a standard bans a token retroactively, and no file in the push that adds the bullet was edited.
|
|
172
109
|
|
|
173
|
-
### Why a recorded count goes stale
|
|
174
|
-
|
|
175
|
-
A count written into prose goes stale against the corpus it describes, and nothing compares the two. The paragraph figure recorded when the masking fix shipped was already wrong by twelve one release later, which is why the standard states the rule and this page carries the numbers.
|
|
176
|
-
|
|
177
|
-
A standard sits inside the corpus this verb measures, so rewriting a rule can breach the rule beside it. A rewrite of the paragraph weight bullet landed at 539 characters against the bullet checkpoint stated two lines below it, in the authoring copy and the consumed one alike. Neither the drift stage nor the test suite reads that, so run the verb over a standard after editing one.
|
|
178
|
-
|
|
179
|
-
Masking took 7 of the weight-only paragraphs the checkpoint reported at 400 and 4 of the 44 files under their checkpoints, and no bullet at all. The first corpus triage put those at 31 paragraphs and 2 bullets, and neither reproduces: a code span is walked around, so a backticked path holding an angle-bracket placeholder keeps the width the page gives it, and both bullets the triage counted were that shape.
|
|
180
|
-
|
|
181
|
-
### How the ban count reached zero
|
|
182
|
-
|
|
183
|
-
The set still carried words and spellings when the gate turned on, so this section and the next are history rather than a description of the current scan.
|
|
184
|
-
|
|
185
|
-
Eight word hits stood between the baseline and a gate, and only three carried the sense the standard bans. `leverage` sat in the requirements worldview, `allows` in the claude stack reference, and one `just` was the vague qualifier in a skill body. Those three lost the qualifier rather than the word.
|
|
186
|
-
|
|
187
|
-
The other five were correct prose the closed set cannot separate from a violation. Four were the temporal `just`, meaning a moment ago, in phrases like the implementation `just` completed and the field the user `just` edited. The fifth quoted an anti-pattern a skill exists to forbid. `markdown.md` banned vague qualifiers and listed the tokens those qualifiers happen to spell, so the rule as written reached none of the five while the scan reached all of them.
|
|
188
|
-
|
|
189
|
-
### Why they were rewritten rather than exempted
|
|
190
|
-
|
|
191
|
-
Rewriting all five is what settled them, over building an exemption path. An exemption has three consumers, `src/markdown/scan.ts` for the patterns, `src/markdown/bans.ts` for the sets, and `.claude/hooks/standards-audit.sh`, which held its own copy of the word bans in awk at that point. A mechanism landing in the verb and not the hook leaves an exempted line still failing on edit, which is the surface an author actually meets. Five sentences lost a small amount of naturalness and the count now means what it says.
|
|
192
|
-
|
|
193
|
-
A code span was the first answer for the quoted anti-pattern and it was the wrong one. The ban scan walks around a code span, so backticking a quotation clears the report, and `## Code and identifiers` reserves the span for commands, API names, file paths, and identifiers, which a quoted utterance is none of. Spending one rule to satisfy another leaves the corpus no cleaner than dropping the qualifier does.
|
|
194
|
-
|
|
195
|
-
The collision is structural rather than a property of five legacy sentences. Writing the task and the plan behind this change each reproduced it, because a fresh file discussing the ban quotes the tokens it discusses. A later author writing about vague qualifiers meets the same thing, and the answer is to name the token in a code span where it is genuinely an identifier being discussed, and to rewrite the sentence where it is not.
|
|
196
|
-
|
|
197
110
|
## What it does not cover
|
|
198
111
|
|
|
199
112
|
The verb reads the two attribute standards and nothing else. The five standards declaring `appliesTo: ["*"]` also include `publish.md`, `slug.md`, and `versioning.md`, none of which this implements.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Migrate
|
|
3
|
+
description: What each canon migrate verb moves and rewrites, what it refuses, the markers that keep an old spelling, and the idempotence check a second run gives
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migrate
|
|
7
|
+
|
|
8
|
+
`canon migrate <verb>` moves a project off a retired name or layout. Each verb reports its plan and writes nothing until `--write` is passed, and each takes `--json`. Every verb but `rename` takes `--root`.
|
|
9
|
+
|
|
10
|
+
`migrate rename` moves a project off the retired `aitk` name. It reports until `--write` is passed, and `--scope target` rewrites the toolkit-owned folders alone, reporting every other citation as one the project owns rather than editing prose somebody wrote. A project installing `canon` fresh never needs it.
|
|
11
|
+
|
|
12
|
+
`migrate skill-names` moves the plugin skills that carried a `claude-` prefix onto their two-word names and rewrites every citation of one. It carries two plain renames as well, where a skill carried no prefix and changed its name because the old one described a job it no longer does, landing on `ui-checklist` and `context-fold`, and it leaves the pointer answering under the fold skill's old name in place. It shares its engine with `migrate rename` and differs in what it is scanning for: a skill name is a whole name rather than a word stem, so it matches only where the word ends, which is what keeps a longer word that merely opens with a skill name, such as `claude-worktrees`, out of it. It takes no `--scope`, since the skill folders it moves are authored in the toolkit and no target holds a copy of that catalog, which leaves a target's own citations of a renamed skill as the whole of what it rewrites there. The changelog and the eval transcripts are passed over on the argument the other sweep already carries, that each records what shipped or what a session ran under whatever name was current then.
|
|
13
|
+
|
|
14
|
+
`migrate records` moves a project's session records from `.claude/` to `.canon/` and rewrites every tracked file that cites one. It reports until `--write` is passed, and refuses outright when the project does not already ignore `.canon/`, since every folder it relocates is ignored where it stands and landing one under a tracked root commits the memory pen. Take the ignore entry with `canon tooling sync --write` first.
|
|
15
|
+
|
|
16
|
+
A record folder already present at the destination is a refusal rather than a merge, and a line carrying `canon-keep-record-root`, or the nearest non-blank line below it, keeps the old spelling for prose that dates a decision. The records themselves are never swept: everything under `.canon/` and every `.claude/` record folder is passed over and reported as a count on its own line, which is what keeps the run that follows the ignore collapse touching the same files as one before it. Running it twice rewrites nothing, which is the check that the exclusions, the markers, and that skip all fired.
|
|
17
|
+
|
|
18
|
+
`migrate surface-roots` moves the tracked surfaces from `.claude/` to `canon/`: `context/`, `decisions/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, `DESIGN.md`, and the install stamp folder, respelled `canon/config/` since a project under `canon/` reserves the bare `canon` name for the CLI's own install. Every path is tracked, so each file moves with `git mv` and its history follows it, and every tracked citation of one is rewritten in the same run. `rules/`, `skills/`, `hooks/`, and `settings.json` stay where Claude Code reads them. It reports until `--write` is passed, and a destination already present under `canon/` refuses the whole plan rather than splitting a surface across both roots.
|
|
19
|
+
|
|
20
|
+
A line carrying `canon-keep-surface-root`, or the nearest non-blank line above it, keeps the old spelling. A rule's frontmatter `paths:` glob, a citation shaped like a path into another repository, and one inside a dated paragraph are reported by file and line and left alone. Running it twice rewrites nothing, which is the idempotence check. A project that never runs it keeps resolving each surface under `.claude/`, since every reader takes whichever root holds the entry.
|
|
21
|
+
|
|
22
|
+
`migrate record-tree` is what reaches the records the sweep above passes over, and the two share no scope. That one enumerates through git, so it sees every tracked file and none of the records, which are gitignored by construction. This one walks `.canon/` itself and is scoped to the folders a session still follows a path into: `diagrams`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`, each minus its own `archive/` subtree. A closed groundwork or intake trail, the scratch folder, and the backup history are reported as counts and never rewritten, because a path inside a closed trail sits in a sentence about work that already ended. Run it after the move, since there is no new root to walk before one.
|
|
23
|
+
|
|
24
|
+
Every citation in scope is reported with its file, its line number, and the line text. The record tree is untracked, so a wrong rewrite has no git undo, and that report is what a reader judges before passing `--write`. Marking a line that has to keep the old spelling uses the same `canon-keep-record-root` comment, and a second run rewriting nothing is the idempotence check.
|
|
25
|
+
|
|
26
|
+
`migrate rule-layout` moves a target still on the flat installed-rule layout onto the `canon/`-wrapped one. It classifies each flat file against the target's own recorded stamp: an unchanged hash is clean, a changed one is edited, and a name neither the stamp nor the current rule catalog can vouch for is unclaimed and left in place. Clean and edited files both relocate with their content untouched, since moving a rule is a different decision from re-syncing it. The one renumbered rule reads its destination from a table rather than from its own current name, so it never lands as a second copy under the retired number.
|
|
27
|
+
|
|
28
|
+
`migrate scratch-evidence` moves a folder out of `.tmp/` for good rather than repointing a citation in place. A promoted folder is one a durable record, live or archived, cites as its evidence, and no source file under `claude/`, `scripts/`, `src/`, `governance/`, or `standards/` names by path, which is what a script still writing into it fails. It walks every `canon records push` folder rather than the live ones alone, since most of what it repoints sits in `tasks/archive/`, `plans/archive/`, and `groundwork/`. A destination already occupied is a refusal, and a second run rewriting nothing is the same idempotence check `migrate record-tree` uses.
|
|
29
|
+
|
|
30
|
+
A destination that already holds the same bytes marks the flat file a duplicate, which `--write` deletes rather than moves. One holding different bytes is a real collision: neither file is touched, it is reported by name, and every other planned move still applies independently. Run this before `canon gov install` or `canon gov sync` against a target still on the flat layout, since neither bootstrap verb detects or clears it on its own.
|
|
31
|
+
|
|
32
|
+
`migrate record-layout` folds two record folders under the memory pen: review receipts move from `.canon/review/memory/` to `.canon/memory/review/`, and retired entries move from `.canon/tmp/memory-archive/` to `.canon/memory/archive/`, backed for the first time since `memory/` is a folder `canon records push` carries and `tmp/` is not. Each move is refused independently when its destination already exists, and the citation repoint sweeps every folder `canon records push` carries, archives included, since an archived receipt still cites the row it retired. The same `canon-keep-record-root` marker and second-run idempotence check apply. A receipt sitting at the flat `review/` root, from before `.canon/review/memory/` existed, matches neither mapped move and is reported by name rather than moved.
|
|
33
|
+
|
|
34
|
+
The same verb leaves `.canon/review/` holding only reviews. Feedback moves to `.canon/feedback/`, and the design, board, slides, and diagram renders move to `.canon/tmp/render/<kind>/`, off the records remote since the commands regenerate them. Reference images move to `.canon/picks/references/`. A branch report flattens from `review/branch/review-<slug>.md` to `review/branch-<slug>.md`, and a flat `review/ui-checklist-<slug>.md` moves into `.canon/tmp/handoff/ui-checklist/`. Each folder under `review/evidence/` is decided from what it holds: one directly carrying an `arm-<id>` capture or a `design-handoff.md` is a pick and moves to `.canon/picks/<slug>/`, and any other is evidence and moves to `.canon/evidence/<nn>-<slug>/`. Evidence folders are numbered by the oldest file each holds, continuing past any ordinal `evidence/` already carries. The dry run labels each derived destination `(pick)` or `(evidence)`, so a misfiled folder shows before `--write`, and one filed wrong moves back by hand. A bare mention of an emptied folder, such as `review/evidence/` with no slug or `review/branch/` with no report name, matches no row and stays as written.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Records stale
|
|
3
|
+
description: Reading the memory pen as a review queue, what makes an entry due, how a cited path is read and resolved, the order entries come back in, and the exit codes and refusals
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Records stale
|
|
7
|
+
|
|
8
|
+
`canon records stale memory` reports each memory entry's review state and every backticked path it cites that the project no longer holds. It answers what a review of the pen should read next, so a skill takes the queue from the verb rather than re-deriving it by grep.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
canon records stale memory
|
|
12
|
+
canon records stale memory --json
|
|
13
|
+
canon records stale memory --days 60 --json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Option | Behavior |
|
|
17
|
+
| --------------- | ------------------------------------------------------------- |
|
|
18
|
+
| `--json` | Add a machine-readable record on stdout |
|
|
19
|
+
| `--days <n>` | Days after a review before an entry is due again (default 30) |
|
|
20
|
+
| `--root <path>` | Project root, defaulting to the main worktree |
|
|
21
|
+
|
|
22
|
+
`memory` is the only kind it reads, since a memory entry is the one record carrying a review date. Any other argument refuses as `unknown-kind`.
|
|
23
|
+
|
|
24
|
+
It reports and never writes. It moves, archives, and rewrites no entry, matching `validate` and `size`.
|
|
25
|
+
|
|
26
|
+
## What makes an entry due
|
|
27
|
+
|
|
28
|
+
An entry is due when its frontmatter carries no `reviewed` date, or one older than `--days`. The field is optional, and a review that keeps an entry is what writes it, per `standards/memory.md`. A `reviewed` value that is not a `YYYY-MM-DD` calendar date reads as never reviewed, and the record carries the raw value as `invalidReviewed` so a review can see it rather than the verb throwing over one hand edit.
|
|
29
|
+
|
|
30
|
+
The field is read off the raw frontmatter block rather than a parsed value, since a YAML parser resolves a bare date to a date object on one schema and a string on another.
|
|
31
|
+
|
|
32
|
+
## Cited paths
|
|
33
|
+
|
|
34
|
+
A cited path is a backticked token holding a `/` and ending in a file extension. A trailing `:12`, `:12-20`, or `#heading` anchor is stripped before the test, since otherwise every line citation reads as missing. The path resolves against the project root and never against the entry's own folder, because an entry cites the tree it was written about.
|
|
35
|
+
|
|
36
|
+
Tokens that look like paths and cite nothing in the tree are never reported:
|
|
37
|
+
|
|
38
|
+
- Anything under `.canon/`, the scratch folder, or `.claude/worktrees/`, which no tree commits
|
|
39
|
+
- A placeholder or glob holding `<`, `*`, or `{`, a variable holding `$`, and a path elided with `...`
|
|
40
|
+
- A URL, an absolute path, a home path, and a path opening with `../`
|
|
41
|
+
|
|
42
|
+
A missing path is a proxy for an entry the tree has moved under, not a verdict. It misses a rule the tree now contradicts in prose, and in a target project it flags a toolkit path the target never held. The review judges each one rather than retiring on the flag alone.
|
|
43
|
+
|
|
44
|
+
## Order
|
|
45
|
+
|
|
46
|
+
The pen is read at its top level alone. `review/` holds receipts, `archive/` holds retirements, and `index.md` is the generated catalog, so none is an entry a review would queue.
|
|
47
|
+
|
|
48
|
+
Entries come back due first. Among the due entries, one citing a missing path leads, then the longest unreviewed, with a never-reviewed entry ahead of any dated one, then by name so two runs over one pen agree. A caller takes the first N as a batch:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
canon records stale memory --json | jq -r '[.entries[] | select(.due)][0:20][] | .name'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The record carries `total`, `due`, `days`, the `folder` read relative to the root, and one `entries` row per entry with `name`, `category`, `reviewed`, `due`, and `unresolved`.
|
|
55
|
+
|
|
56
|
+
## Exit codes and refusals
|
|
57
|
+
|
|
58
|
+
Exit codes: `0` the reading completed, whatever it found, and `1` refused. A due entry is a queue position rather than a failure, so nothing here gates.
|
|
59
|
+
|
|
60
|
+
A `reason` field carries which gate fired: `no-folder` when the project holds no memory folder at either record root, `unknown-kind` for any kind but `memory`, and `bad-days` when `--days` is not a positive whole number. An empty pen is not a refusal. It reports an empty list and exits `0`.
|
|
61
|
+
|
|
62
|
+
The pen is shared scratch at the main worktree root, so `--root` defaults there, the same default `validate memory` takes. A pen at the legacy `.claude/memory/` root resolves the way every record verb resolves it, per `records.md`. <!-- canon-keep-record-root -->
|
package/docs/agents/records.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Records
|
|
3
|
-
description: The two roots a record folder resolves at, migrating a record a frontmatter change orphaned, claiming the ordinal intake and groundwork share, reading each folder's size and growth, pruning stale scratch, and where validation and the private-remote backup are described
|
|
3
|
+
description: The two roots a record folder resolves at, migrating a record a frontmatter change orphaned, claiming the ordinal intake and groundwork share, reading each folder's size and growth, pruning stale scratch, and where validation, the memory review queue, and the private-remote backup are described
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Records
|
|
@@ -116,6 +116,10 @@ It writes nothing until `--write` is passed, matching `canon records migrate`: a
|
|
|
116
116
|
|
|
117
117
|
Exit codes: `0` nothing to prune, or `--write` deleted every candidate. `1` refused, sharing `no-folder` with `size`, or a delete failed. `2` candidates exist and `--write` was not passed.
|
|
118
118
|
|
|
119
|
+
## Stale
|
|
120
|
+
|
|
121
|
+
`canon records stale memory` reports which memory entries are due for review and which cite a path the tree no longer holds, ordered so a review takes the first N as a batch. What makes an entry due, how a cited path resolves, and the refusals are in `records-stale.md`.
|
|
122
|
+
|
|
119
123
|
## Push and pull
|
|
120
124
|
|
|
121
125
|
`canon records push` backs the record folders to a private remote and `canon records pull` restores them. The payload, the setup, and the refusals are in `records-push.md`.
|
package/docs/agents/sandbox.md
CHANGED
|
@@ -22,7 +22,7 @@ Scenario categories: `infra:*` (domain flows), `git:*`, `scaffold:*`. `create` s
|
|
|
22
22
|
`canon sandbox check <category>:<command> [arm]` scores a provisioned sandbox against the arm's `expect.toml`, printing a verdict on stderr and, with `--json`, the same verdict as a record on stdout.
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
canon sandbox check claude:
|
|
25
|
+
canon sandbox check claude:context-fold drift --json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
| Flag | Effect |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tasks archive
|
|
3
|
+
description: Archiving a shipped task by stem or pull request, declining one decided against, the refusal reasons each carries, and how the plan and ready folder move with the task
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tasks archive
|
|
7
|
+
|
|
8
|
+
## Archive
|
|
9
|
+
|
|
10
|
+
`canon tasks archive` moves a shipped task from `.canon/tasks/` into `.canon/tasks/archive/`, drops its row from `priority.md`, and regenerates the board index. The three run as one unit, so the attended and unattended callers cannot archive differently.
|
|
11
|
+
|
|
12
|
+
Name the task by its filename stem, or by the pull request it carries:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
canon tasks archive v28.1-trigger-escalation # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
16
|
+
canon tasks archive --pull-request 673 --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Option | Behavior |
|
|
20
|
+
| -------------------- | ------------------------------------------------------------ |
|
|
21
|
+
| `--pull-request <n>` | Select the task whose `Pull request:` line names this number |
|
|
22
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
23
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
24
|
+
|
|
25
|
+
Exit codes: `0` archived, `1` refused. Every gate is a refusal rather than a warning, because `.husky/post-merge` calls this with nobody watching. The `reason` field carries which gate fired: `no-board`, `no-match`, `ambiguous`, `no-outcomes`, `open-outcomes`, `earlier-slice`, or `bad-input`. `earlier-slice` fires when `--pull-request` names a number that is not the last on the task's `Pull request:` line, since outcomes are ticked at ship time and only the last slice's merge closes the task.
|
|
26
|
+
|
|
27
|
+
An outcome whose body is struck reads as cut rather than open or closed, whatever its checkbox holds: `- ~~<outcome>~~ <why>`. A task carrying only cut outcomes archives, since the gate refuses `no-outcomes` only when both the closed and the cut counts are zero. The success record carries `closed` and `cut` as counts, so a reader tells a shipped task from an abandoned one without opening the file.
|
|
28
|
+
|
|
29
|
+
The task carries its plan with it. When the closing task is the last live one whose `Plan:` line resolves onto that file, the plan moves to `.canon/plans/archive/` under its own name and the archived task's link is rewritten as `[feature-<slug>](../../plans/archive/feature-<slug>.md)`, a folder deeper than the live task wrote it. A line linking several plans moves each one no other live task cites and rewrites only those links. The `plans` array on the success record carries a `from` and `to` per moved plan in line order, and is empty when nothing moved.
|
|
30
|
+
|
|
31
|
+
Every other relative link in the task is re-resolved for the deeper folder too, whether its target moved or not: `Groundwork:`, `Intake:`, a `Plan:` or `Ready:` line whose target stayed put, and a link in the body. Fenced blocks, inline code spans, absolute URLs, `#` anchors, and rooted paths are left alone. `canon tasks decline` applies the same rebase, since `declined/` sits at the same depth. The plan retarget runs before the rebase and writes each moved link relative to the live board, so the rebase carries it into the deeper folder like every other link, while the ready retarget runs after it.
|
|
32
|
+
|
|
33
|
+
The ready folder the task's `Ready:` line names moves with the plan to `.canon/ready/archive/`, falling back to a `.canon/ready/<nn>-<slug>/` path in the plan when the line is absent. The `Ready:` line and the plan's literal path to the folder are retargeted, and the `ready` field on the success record carries `from` and `to`, or `null` when nothing moved. A folder that resolves to nothing, sits already archived, or meets a taken destination moves nothing and refuses nothing.
|
|
34
|
+
|
|
35
|
+
A plan several tasks share stays where it is, and its ready folder with it, and the task archives anyway. Moving it on the first task to close strands every sibling's pointer at a path that has gone, and `.canon/plans/` is gitignored so no history recovers the target. A `Plan:` line resolving to no file leaves the plan alone too, since a pointer somebody typed wrong is not a plan to move and holding the whole archive over it would park the board behind a repair the merge cannot make.
|
|
36
|
+
|
|
37
|
+
The merge is what settles a plan. `.husky/post-merge` reads the pull request number off the squash subject and calls this verb, so both halves close in one act with nobody naming a file. That is why the move sits inside this verb rather than in a second call the hook would make after it, which could leave the task archived and the plan live.
|
|
38
|
+
|
|
39
|
+
`bad-input` covers a malformed command line, which all three task verbs answer the same way. It is separate from `ambiguous` and `no-match` because those describe the board, and a caller that passed two selectors would otherwise be sent to repair a task citation that is fine.
|
|
40
|
+
|
|
41
|
+
The row is matched by the link in its first cell rather than by a pattern against the whole line. A row names the task it is about in the first cell, so a link anywhere after it is a reference, such as a blocker naming what it waits on, and matching the line would drop the referring task's row too.
|
|
42
|
+
|
|
43
|
+
The row removal reaches `priority.md` alone. A task gets to a merge by being planned and handed out, and both steps move it onto the board first, so one archived straight off `backlog.md` leaves its bullet standing and `canon tasks validate` reports that bullet as naming a file that is gone.
|
|
44
|
+
|
|
45
|
+
The board is shared scratch at the main worktree root, so `--root` defaults to the first entry of `git worktree list` rather than the working directory. A linked worktree archives against the same board every other session reads.
|
|
46
|
+
|
|
47
|
+
Skills branch on the reason rather than on the exit code:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
canon tasks archive --pull-request 673 --json | jq -r 'if .ok then .task else .reason end'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Decline
|
|
54
|
+
|
|
55
|
+
`canon tasks decline <task>` moves a task decided against from `.canon/tasks/` into `.canon/tasks/declined/`, clears whichever of `priority.md` or `backlog.md` holds its row, and regenerates the board index. Unlike `canon tasks archive`, it carries no outcome-state gate: a task can be decided against at any outcome state.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
canon tasks decline v28.1-trigger-escalation --reason "superseded by v30.2" # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
59
|
+
canon tasks decline v28.1-trigger-escalation --reason "no longer needed" --by Alex --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Option | Behavior |
|
|
63
|
+
| ----------------- | ------------------------------------------------- |
|
|
64
|
+
| `--reason <text>` | Why the task was decided against, required |
|
|
65
|
+
| `--by <name>` | Who decided, defaulting to `git config user.name` |
|
|
66
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
67
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
68
|
+
|
|
69
|
+
Exit codes: `0` declined, `1` refused. The `reason` field carries which gate fired: `no-board`, `no-match`, `ambiguous`, or `bad-input`. `bad-input` covers a missing `--reason` and a `--by` that resolves to nobody, git config included.
|
|
70
|
+
|
|
71
|
+
`DECLINE_REFUSALS` is kept apart from archive's own refusal set on purpose. Archive and decline answer different questions, shipped versus decided-against, and a shared gate would let one archive a task that cannot yet ship or decline one that already has.
|
|
72
|
+
|
|
73
|
+
The decision is written onto the task as a `Declined: <reason>, <who> on <YYYY-MM-DD>` line, anchored the same way `Pull request:` is, after the last origin line the task carries.
|
|
74
|
+
|
|
75
|
+
The task carries its plans with it the same way archive does, each one the declining task is the last live citation of, and the success record carries the same `plans` array. A declined task's plan lands in `.canon/plans/archive/`, indistinguishable from a shipped one by folder alone. The task file under `.canon/tasks/declined/` is what records which it was.
|
|
76
|
+
|
|
77
|
+
Skills branch on the reason rather than on the exit code, the same rule `canon tasks archive` states:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
canon tasks decline v28.1-trigger-escalation --reason "superseded by v30.2" --json | jq -r 'if .ok then .task else .reason end' # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
81
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tasks plans
|
|
3
|
+
description: The five verbs that read or write a task's plan, being where it sits and who cites it, whether it waits on the operator, the branch it derives, what a branch reached against what it declared, and writing its Plan line
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tasks plans
|
|
7
|
+
|
|
8
|
+
## Plan citations
|
|
9
|
+
|
|
10
|
+
`canon tasks plan-citations <stem>` answers where a task's plan sits and which other live tasks hold it. It reports and never writes.
|
|
11
|
+
|
|
12
|
+
| Option | Effect |
|
|
13
|
+
| --------------- | ------------------------------------------- |
|
|
14
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
15
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
16
|
+
|
|
17
|
+
The record carries `location`, one of `unstated`, `several`, `live`, `archived`, or `outside`, `targets`, every target the line carries in line order, and `citedBy`, the other live tasks whose `Plan:` line lands on the same file through any of its links. `several` is a line linking more than one plan, which answers no single location, so `target` is absent there and `targets` names each link. Exit codes: `0` read, `1` refused with `no-board` or `no-match`.
|
|
18
|
+
|
|
19
|
+
The target resolves against the board folder and against the project root both, so `../plans/x.md` and `.canon/plans/x.md` land on the same file and one plan two tasks spelled differently counts once. Containment is tested at both record roots rather than at the one this tree resolves at, since a line somebody wrote against a root the tree has since left is still a path into the plans folder, and reading it as outside would report a shipped plan as still live. `docs/agents/records.md` states the read order.
|
|
20
|
+
|
|
21
|
+
`canon tasks archive` decides its plan move on this same answer, so a caller wanting the count reads it here rather than scanning the board.
|
|
22
|
+
|
|
23
|
+
Branch on `reason` rather than on the exit code, which is the rule `tasks-archive.md` already states for archive and which this verb needs for a second reason. An operator's shell profile may wrap `canon` in a function that runs the binary and then another command and takes the second status, which masks every non-zero exit rather than only an absent verb. The binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike, so the record is the only signal that survives the wrapper.
|
|
24
|
+
|
|
25
|
+
A `live` location with an empty `citedBy` is the sweep to run. One whose `citedBy` names a sibling is a plan several tasks share, which the sweep leaves alone and the archive gate lets through.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
canon tasks plan-citations v28.1-trigger-escalation --json | jq -r '.location' # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Plan answers
|
|
32
|
+
|
|
33
|
+
`canon tasks plan-answers <plan>` answers whether a plan is launchable, which is whether it still waits on the operator for a call only they can make. It reports and never writes.
|
|
34
|
+
|
|
35
|
+
Name the plan by its path or by its slug, which resolve to the same file:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
canon tasks plan-answers dispatch-answer-gate
|
|
39
|
+
canon tasks plan-answers .canon/plans/feature-dispatch-answer-gate.md
|
|
40
|
+
canon tasks plan-answers ../plans/feature-dispatch-answer-gate.md
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A relative path resolves against the project root first and against `.canon/tasks/` second. The third form above is what a board row writes, since its link is relative to the board, and a dispatcher copying the reference out of the row it is dispatching has that spelling to hand rather than either of the other two. A refusal names every base it looked under.
|
|
44
|
+
|
|
45
|
+
`canon tasks plan-citations` reads a task's `Plan:` line against those same two bases in the opposite order, and tests that the target lands under the live plans folder, which this verb does not. Both answer the same file for every spelling a board writes. Liveness is a separate refusal here: a plan resolving inside `.canon/plans/archive/` returns `archived` rather than a launchable reading, since it answers every question and describes work that already shipped.
|
|
46
|
+
|
|
47
|
+
A plan still staging its batches with a `**Batch N**` sub-heading inside one file's `**Files to touch:**` reads `launchable: false` the same way, alongside the operator-call case. It reports as an entry in `open` labeled `Batch staging`, stating that the plan must split into one file per batch before it can dispatch, since the row waits on a split rather than on the operator.
|
|
48
|
+
|
|
49
|
+
| Option | Effect |
|
|
50
|
+
| --------------- | ------------------------------------------- |
|
|
51
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
52
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
53
|
+
|
|
54
|
+
The record carries `launchable` and `open`, the questions still waiting, each with the `label` that names it and the `why` its suggestion gave for needing a person. Exit codes: `0` launchable, `1` refused with `no-plan`, `archived`, or `bad-input`, `2` waiting on the operator.
|
|
55
|
+
|
|
56
|
+
A blank `- Answer:` is not a waiting question. The plan standard fixes an empty slot as accepting the `- Suggested:` line above it, so the shapes this reads are `- Suggested: needs your call, <why>` and the two demonstrated paraphrases, `needs operator's call` and `needs the operator's call`, over an empty slot, which is what that standard writes where the answer turns on preference rather than on a technical default. A verb reading every blank slot as open would report every plan in the folder.
|
|
57
|
+
|
|
58
|
+
The question block is read through the same parser `canon tasks validate` runs, so the gate and the conformance check cannot drift into disagreeing about what a question is. A question carrying no suggestion at all is that check's finding rather than this one's, and it goes unread here.
|
|
59
|
+
|
|
60
|
+
Branch on `launchable` rather than on the exit code, for the reason the section above states: a shell profile wrapping `canon` in a function can take a later command's status and mask every non-zero exit, which reads a waiting plan as a launchable one.
|
|
61
|
+
|
|
62
|
+
The orchestrator dispatch runbook calls this before it checks the branch or the file sets, so a row whose plan still needs a person is handed back rather than launched into a worker that halts on the same question.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
canon tasks plan-answers dispatch-answer-gate --json | jq -r '.launchable'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Plan branch
|
|
69
|
+
|
|
70
|
+
`canon tasks plan-branch <plan>` derives the branch name from a plan file. It reports and never writes, and it names the plan the same two ways `canon tasks plan-answers` does, by path or by slug, against the same two bases.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
canon tasks plan-branch dispatch-answer-gate
|
|
74
|
+
canon tasks plan-branch .canon/plans/feature-dispatch-answer-gate.md --json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Option | Effect |
|
|
78
|
+
| --------------- | ------------------------------------------- |
|
|
79
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
80
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
81
|
+
|
|
82
|
+
The record carries `type`, `slug`, `branch`, `words`, and `conforms`. Exit codes: `0` derived and conforming, `1` refused with `no-plan`, `archived`, or `bad-input`, `2` derived with `conforms` false. Branch on `conforms` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
|
|
83
|
+
|
|
84
|
+
`slug` is the plan filename with its `feature-` prefix and its extension taken off, and `type` is the constant `feat`. Reading a type off the plan's prose was the alternative, and it is the half of the derivation that has already disagreed with itself: one dispatch checked `fix/path-form-hook` against a worker that took `feat/path-form-hook`, both sides reading one plan. What makes the constant safe is that a branch type is cosmetic. `git-stage` reads a commit's type off the staged diff and `git-pr` reads a title off the diff, so the semantics a release reads never pass through the branch name. What it costs is a worktree listing where every plan-derived branch reads `feat/`, and nothing renames it later.
|
|
85
|
+
|
|
86
|
+
`conforms` reads both caps `standards/branch.md` states, being 4 words on the description and 50 characters on the whole branch. A false reading is a row for a person rather than a name to shorten here, since a rename parts the branch slug from the plan slug that `session-worktree` tier 1 and `git-pr`'s plan lookup both read back.
|
|
87
|
+
|
|
88
|
+
Both sides of a dispatch call it. The orchestrator's collision check derives its candidate here, and `auto-ship` Step 0 derives the worktree it enters from the same plan, so the branch a gate clears and the branch a session takes are one string by construction. They were two readings of one paragraph until 2026-09-06, when four dispatches on one plan produced three different strings.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
canon tasks plan-branch dispatch-answer-gate --json | jq -r '.branch'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Plan reach
|
|
95
|
+
|
|
96
|
+
`canon tasks plan-reach <plan>` reads a branch back against what was written down about it. It reports and never writes, and it names the plan the same two ways `canon tasks plan-branch` does.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
canon tasks plan-reach dispatch-answer-gate
|
|
100
|
+
canon tasks plan-reach dispatch-answer-gate --base origin/main --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Option | Effect |
|
|
104
|
+
| --------------- | ---------------------------------------------- |
|
|
105
|
+
| `--base <ref>` | Far side of the range, defaulting to the trunk |
|
|
106
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
107
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
108
|
+
|
|
109
|
+
The record carries `claimed`, `undeclared`, `declared`, `base`, `changed`, `plans`, `rows`, and `board`. Exit codes: `0` read with nothing claimed, `1` refused with `no-plan`, `archived`, `bad-input`, `no-base`, or `no-diff`, `2` read with a claim standing. Branch on the record rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
|
|
110
|
+
|
|
111
|
+
`claimed` leads because it is the short list and the one worth acting on. It carries one entry per path, each holding a `holders` list, so a track carrying both a live plan and a `## Run now` row reads as one holder rather than two. A holder names itself, its `source` of `plan` or `row`, and the `declaration` it matched on, so a folder claim reports which folder rather than leaving the reader to find it. A plan holder also carries `rowed`, whether that plan has a row in `## Run now` at all: a plan with no row is the shape a plan nobody archived takes, and equally the shape of one whose task has yet to be dispatched, so it narrows the reader's search rather than answering it. `undeclared` is every changed path this plan never named, which runs long on an ordinary branch: over the wave this verb was filed against, it ran 22 of 26 paths on one pull request and 18 of 25 on another. Those are the ship chain's own writes rather than scope creep, since the sync skills reach a context entry and the public docs, and the check stages regenerate what they assert.
|
|
112
|
+
|
|
113
|
+
A declaration is a backticked span standing as an entry's subject, ahead of the colon opening its reason. Reading every span was the alternative and it reports pairs that were never going to collide, since a reason routinely cites a file the entry is not about: measured over the same wave, the subject rule reports 6 crossing pairs against 14 for every span. Both sides of a rename declare, since both sit ahead of the colon.
|
|
114
|
+
|
|
115
|
+
The range is read at the current directory and the plans and board at the board root, so a linked worktree reads its own branch against the shared records. Reading both at one root was the alternative and it measures a main checkout sitting on the trunk, where the range closes on itself and every branch reports a reach of nothing.
|
|
116
|
+
|
|
117
|
+
It reads only what is written down, so it inherits the dispatch runbook's blindness: a hand-launched track carries no row and a track with no plan carries no declaration. The `plans`, `rows`, and `board` fields say how much there was to compare against, so a clear reading over an empty corpus does not read as a proof. A missing board reports `board: false` and zero rows rather than refusing, since a project with plans and no board still has a branch worth reading.
|
|
118
|
+
|
|
119
|
+
A claim is only as current as the folder it was read from, and the live folder holds a plan whose work already shipped until something archives it. `canon tasks archive` moves a plan on merge, so a plan stranded by a run that never reached the archive keeps claiming its files against every branch afterwards. Check whether the holder is actually in flight before treating a claim as a collision: the first run of this verb on its own branch reported five paths held by a plan whose verb had already merged, and the whole reading came of a file nobody archived. `canon tasks validate` is what reports the stranded plan itself.
|
|
120
|
+
|
|
121
|
+
`canon:git-ship` runs it at step 5, after `git add -A` and before the commit grouping. That is the first point the branch is whole and the last before a pull request exists to carry the answer.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
canon tasks plan-reach dispatch-answer-gate --json | jq -r '.claimed[] | "\(.path) held by \([.holders[].name] | join(", "))"'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Plan link
|
|
128
|
+
|
|
129
|
+
`canon tasks plan-link <task> <plan>` writes or corrects a task's `Plan:` line, as `Plan: [<label>](<target>)` right after the H1. `plan-feature` calls it right after a plan file lands, when Step 1 resolved an existing task for the feature, so the line is a mechanical write rather than hand-edited markdown.
|
|
130
|
+
|
|
131
|
+
Name the task by its filename stem, and the plan by its path or its slug, the same two forms `canon tasks plan-answers` accepts:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
canon tasks plan-link v28.1-trigger-escalation dispatch-answer-gate # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
135
|
+
canon tasks plan-link v28.1-trigger-escalation .canon/plans/feature-dispatch-answer-gate.md --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Option | Behavior |
|
|
139
|
+
| --------------- | ------------------------------------------- |
|
|
140
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
141
|
+
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
142
|
+
|
|
143
|
+
The plan resolves the same way `canon tasks plan-answers` resolves one, against the project root first and `.canon/tasks/` second, so a bare slug and a board-relative path both work. A reference resolving to no file refuses as `no-plan`, naming every base it looked under.
|
|
144
|
+
|
|
145
|
+
The write adds the line when it is absent and corrects the target in place when it exists, anchored on the H1 rather than on the last origin line, since `Plan:` is the first origin line a task carries rather than the last. A `Plan:` line inside a fenced sample is skipped. The `action` field reports `added`, `corrected`, or `unchanged`, which makes a rerun against the same plan safe, and `replaced` names the target the line carried before when it differed.
|
|
146
|
+
|
|
147
|
+
A line already linking several plans is refused as `several-plans` and left unchanged, with `detail` listing each link, even when one of them is the plan named. The verb cannot tell which link to drop, and reporting `unchanged` would hide a line that breaks the one-plan rule.
|
|
148
|
+
|
|
149
|
+
Exit codes: `0` recorded, `1` refused. The `reason` field carries `no-board`, `no-match`, `no-plan`, `several-plans`, or `bad-input`.
|