@erclx/canon 4.120.0 → 4.122.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 +29 -115
- package/claude/skills/auto-ship/references/draft-mark.md +30 -0
- package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
- package/claude/skills/auto-ship/references/tier-failures.md +26 -0
- package/claude/skills/auto-ship/references/verb-absent.md +37 -0
- package/claude/skills/auto-ship/references/worktree-name.md +22 -0
- package/claude/skills/backlog-triage/REQUIREMENT.md +47 -0
- package/claude/skills/backlog-triage/SKILL.md +139 -0
- package/claude/skills/bash-script/SKILL.md +1 -1
- package/claude/skills/bash-script/references/patterns.md +0 -99
- package/claude/skills/bash-script/references/prompts.md +100 -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/create-skill/SKILL.md +1 -1
- package/claude/skills/create-snippet/SKILL.md +1 -1
- package/claude/skills/create-standard/SKILL.md +1 -1
- package/claude/skills/design-extract/SKILL.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +2 -2
- 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 +2 -2
- package/claude/skills/draft-context/REQUIREMENT.md +2 -2
- package/claude/skills/draft-context/SKILL.md +4 -4
- package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
- package/claude/skills/draft-docs/SKILL.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +1 -1
- package/claude/skills/draft-readme/SKILL.md +1 -1
- package/claude/skills/draft-wiki/SKILL.md +1 -1
- package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
- package/claude/skills/draft-wireframes/SKILL.md +3 -3
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +1 -1
- package/claude/skills/git-pr/SKILL.md +9 -147
- 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 +1 -1
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/memory-capture/REQUIREMENT.md +2 -2
- package/claude/skills/memory-capture/SKILL.md +6 -6
- package/claude/skills/memory-review/REQUIREMENT.md +1 -1
- package/claude/skills/memory-review/SKILL.md +5 -101
- package/claude/skills/memory-review/references/later-phases.md +108 -0
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
- package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +5 -4
- package/claude/skills/review-branch/REQUIREMENT.md +1 -0
- package/claude/skills/review-branch/SKILL.md +6 -7
- package/claude/skills/review-craft/REQUIREMENT.md +43 -0
- package/claude/skills/review-craft/SKILL.md +50 -0
- package/claude/skills/review-craft/references/security.md +21 -0
- package/claude/skills/review-craft/references/ui.md +72 -0
- package/claude/skills/review-pr/REQUIREMENT.md +1 -0
- package/claude/skills/review-pr/SKILL.md +1 -1
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -0
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/session-resume/REQUIREMENT.md +1 -1
- package/claude/skills/session-worktree/SKILL.md +4 -10
- package/claude/skills/standards-audit/SKILL.md +1 -1
- package/claude/skills/task-board/REQUIREMENT.md +1 -1
- package/claude/skills/task-board/SKILL.md +6 -80
- 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/claude/skills/write-human/REQUIREMENT.md +7 -4
- package/claude/skills/write-human/SKILL.md +4 -2
- package/claude/skills/write-human/references/machine-tells.md +1 -1
- package/claude/skills/write-human/references/source-material.md +5 -2
- package/docs/agents/architecture-record.md +26 -0
- package/docs/agents/audits.md +1 -1
- 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 +11 -104
- package/docs/agents/migrate.md +34 -0
- package/docs/agents/pr-local.md +6 -3
- 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 +147 -0
- package/docs/agents/tasks-validate.md +131 -0
- package/docs/agents/tasks.md +6 -333
- package/docs/target-projects.md +4 -4
- package/docs/workflow/ai-workflow.md +33 -27
- package/governance/rules/claude/500-prose.md +1 -1
- package/governance/rules/claude/501-markdown.md +3 -3
- package/governance/rules/core/045-memory.md +1 -1
- package/package.json +1 -1
- package/src/autoship/paths.ts +3 -2
- package/src/claude/cases/setup.ts +1 -1
- package/src/claude/cases/workflow.ts +27 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/markdown.ts +7 -9
- package/src/commands/pr.ts +11 -4
- package/src/commands/records.ts +146 -0
- package/src/commands/sandbox.ts +6 -4
- 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 +5 -5
- package/src/gate/stages.ts +0 -1
- package/src/markdown/bans.ts +11 -68
- package/src/markdown/gate.ts +1 -1
- package/src/markdown/scan.ts +7 -88
- 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 +28 -4
- package/src/tasks/record.ts +1 -1
- package/src/tasks/validate.ts +36 -3
- package/standards/docs.md +1 -1
- package/standards/index.md +1 -1
- package/standards/markdown.md +5 -7
- package/standards/memory.md +13 -1
- package/standards/pr.md +1 -1
- package/standards/publish.md +1 -1
- package/standards/readme.md +2 -2
- package/standards/tasks.md +1 -1
- package/tooling/astro/reference.md +1 -1
- package/tooling/base/configs/.husky/post-merge +1 -1
- package/tooling/base/configs/scripts/verify.sh +2 -2
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
- 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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tasks validate
|
|
3
|
+
description: Validating the board against the tree, the seven checks, the untested, claims, and unplaced arrays, the exit codes, and the refusal reasons
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tasks validate
|
|
7
|
+
|
|
8
|
+
`canon tasks validate` reports what each row of `priority.md` claims against what the tree holds. It reads and never writes, because a row is a session's claim about readiness and a validator that repaired one would be asserting the claim it exists to test.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
canon tasks validate
|
|
12
|
+
canon tasks validate --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Seven checks run. Collisions reaches one half of the `## Run now` test the board standard states, and Plan reaches every group, reading the Plan column under `## Run now` and the task file's own line under the other two. Mapping and Grouping test the folder contract and hold for every group, and Shape holds for every group too, ahead of the four. Ordering reaches only the `## Needs a plan` rows, and Blockers reaches every row outside `## Run now`:
|
|
16
|
+
|
|
17
|
+
| Check | What it reports |
|
|
18
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
|
|
20
|
+
| Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line links several plans |
|
|
21
|
+
| Mapping | A row or backlog line naming no task file |
|
|
22
|
+
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
23
|
+
| Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
|
|
24
|
+
| Collisions | Two `## Run now` rows whose Touches columns name a path in common |
|
|
25
|
+
| Blockers | A parked row whose blocker has stopped holding, whose cited task resolves nowhere, or whose cited task was declined |
|
|
26
|
+
|
|
27
|
+
Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
|
|
28
|
+
|
|
29
|
+
The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair naming two different plans is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
|
|
30
|
+
|
|
31
|
+
The other two groups have no Plan column, so the same check reads the task file's own `Plan:` line against what the group name claims. A `## Needs a plan` row whose task cites a plan that sits live on disk is `plan-parked`, since the group says no such plan exists and the row is either misplaced or its plan is stale and belongs in the archive. An `## Up next` row whose task cites no live plan is `plan-absent`, since that group says one is written. A plan already archived, or a cited path with no file behind it, reads as not live on both sides. A row whose task file is gone is left to `task-unresolved`. An `## Up next` row with nothing left to build, waiting on an act only a person can take, reports `plan-absent` once its plan archives while the task stays live. That report is expected, since the check reads the task file and not the `Waiting on` prose, so a row in that state moves to `## Needs a plan` or closes.
|
|
32
|
+
|
|
33
|
+
A task whose `Plan:` line links more than one plan is `plan-several` in every group, and it replaces the `plan-uncited` or `plan-absent` report the line would otherwise draw. The line is there, so saying it is missing would send the reader looking for the wrong fault. The line breaks the one-plan-per-task rule the tasks standard states instead, and `plan-mismatched` does not fit either, since that kind means the row and the task disagree while here the task alone is at fault. The message lists every plan the line links, and the fix is to split the task or drop the extra links.
|
|
34
|
+
|
|
35
|
+
Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A row or a backlog line naming no task file is `task-unresolved`, and a file both surfaces name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. A task file neither surface names is `unplaced` rather than a finding, since that is the normal state between a session filing it and a live orchestrator placing it on the board. One check across both is what lets a task move between them without the move reading as a dropped file.
|
|
36
|
+
|
|
37
|
+
A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name still lands in the `unplaced` array below rather than the findings. A project carrying no `backlog.md` reads as an empty backlog rather than a refusal.
|
|
38
|
+
|
|
39
|
+
Ordering reads a `## Needs a plan` row's `Waiting on` cell for the position it claims, and reports two failures off one walk. A row stating an ordinal is checked against where it actually sits, which is `row-misordered`. A row claiming no position in either form it may take is `row-unranked`, since its cell argues the task matters and ranks it against nothing, which leaves the order recording when each row was filed.
|
|
40
|
+
|
|
41
|
+
The ordinal phrase is prose rather than data, searched for anywhere in the cell rather than at its start, since every live row states its position at the end of a sentence rather than at the front. The vocabulary stops at `first` through `twentieth` plus `last`, since a parser strict enough to catch a gap would otherwise flag a row phrased correctly and differently, and bounding it to those words is what keeps a cell reading `Untestable from here` from matching on `from`. One comparison against the real position catches a gap, a duplicate, and a sequence starting somewhere other than first alike.
|
|
42
|
+
|
|
43
|
+
The comparative phrase is bounded the same way and for the same reason. A closed verb list of `leads`, `heads`, `opens`, `closes`, `trails`, `precedes`, `follows`, `outranks`, and `sits under`, `above`, or `below` has to sit in one clause with a positional object, which is a `vNN.N` phase label, the word `group`, or `row` or `rows`. Both halves are needed, since a cell reading `it closes a gap the reference gate leaves open` carries the verb and claims no position, and the clause bound is what stops a verb in one half of the cell pairing with an object in the other. An ordinal exempts the row, being a comparative claim already. An unusual comparative phrasing reports as unranked, the safe direction for a check over prose, and a vocabulary verb reaching a positional object non-positionally reports as ranked, which only grading prose could separate.
|
|
44
|
+
|
|
45
|
+
The collision check is the one a person cannot run by eye. Paths come from the backticked spans in the Touches column, a span naming no file is dropped, and a directory collides with any file beneath it. A `## Run now` row whose column parses to nothing is reported rather than skipped, since a row stating no file set makes a claim nothing can check.
|
|
46
|
+
|
|
47
|
+
Where a directory holds the other row's file, the finding names the row that claimed it, reading `both touch src/tasks, which v2.0-second claims as a folder.` The shared strings alone leave an over-broad cell and a genuine overlap identical. <!-- canon-allow-reference: illustrates the finding's own sentence shape, not a citation of a real task -->
|
|
48
|
+
|
|
49
|
+
The blocker check re-takes a measurement the board records once and never repeats. Two of the five blocker kinds put a fact on disk: a dependency is settled by the cited task being archived or by its work reaching the trunk, and a collision is settled by nothing under `## Run now` still holding the file the cell cites. A cited task resolving under `.canon/tasks/declined/` instead reports separately as `blocker-declined`, since a decided-against task is neither the shipped work `blocker-settled` reports nor the dangling pointer `blocker-unresolved` reports.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"findings": [
|
|
54
|
+
{
|
|
55
|
+
"kind": "blocker-declined",
|
|
56
|
+
"group": "Up next",
|
|
57
|
+
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
58
|
+
"message": "waits on v9.0-superseded, which was declined." // canon-allow-reference: illustrates the finding's own sentence shape, not a citation of a real task
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A closed outcome is not the fact the dependency half needs. The ship chain marks outcomes as its first step and opens the pull request several steps later, so a check reading the checkbox reports the row settled while the branch is still in review. A live task therefore settles the row only once it closed every outcome and carries a `Pull request:` line the trunk holds. One that names no pull request, and one whose number no trunk ref could answer for, land in the untested array below rather than being settled or left silent.
|
|
65
|
+
|
|
66
|
+
The trunk is read as the clone already holds it, `origin/main` first and local `main` behind it, and no run fetches. A validate runs several times a sweep and a fetch per run is a cost this command does not carry, so a clone behind its remote under-reports rather than claiming work landed.
|
|
67
|
+
|
|
68
|
+
Both halves gate on a citation inside the `Waiting on` cell, never on the columns beside it. The board format gives a collision cell the file held by the running task, so a row whose cell names no file was parked by something else, and testing its Touches column instead reports a cleared collision on a row no collision ever parked while counting that row as re-tested. A cited task is a bare sibling link, the way the Task column spells one, so a pointer into another folder names a plan rather than a task and settles nothing. A cited task carrying no outcome box settles nothing either, since a file the check could not parse is not evidence of a finished one.
|
|
69
|
+
|
|
70
|
+
A citation resolving in none of the board, the archive, or the declined folder is `blocker-unresolved` rather than a settled row. Reading an absent file as archived or declined states a specific fact about a file nobody ever wrote, which is what a renamed task or a typo produces, and only a task that genuinely closed or was genuinely declined releases the row waiting on it.
|
|
71
|
+
|
|
72
|
+
The other three kinds rest on a person's judgment, so a row neither half reached lands in a second array rather than in the findings:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"untested": [
|
|
77
|
+
{
|
|
78
|
+
"group": "Needs a plan",
|
|
79
|
+
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
80
|
+
"message": "..."
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
An untested row is not a finding and moves no exit code. Reading a clean findings list as a clean board is the failure the array exists to prevent, and `orchestrator-parked.md` is the pass that takes those rows by hand.
|
|
87
|
+
|
|
88
|
+
A `## Run now` row whose Touches column names a bare folder lands in a third array on the same reasoning:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"claims": [
|
|
93
|
+
{
|
|
94
|
+
"group": "Run now",
|
|
95
|
+
"subject": "v1.0-first", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
96
|
+
"message": "claims the whole src/tasks folder, so it collides with every row written under it."
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That claim collides with every row a later session writes under the folder, and it is legitimate whenever the row does rewrite the directory, so the array states the reach and moves no exit code. A measure failing on a cell that is right teaches a reader to skip it. Folder against file is decided by asking the tree for a path that resolves, and by the extension only for a path the row has yet to create, since the name alone reads a file carrying no extension as a folder.
|
|
103
|
+
|
|
104
|
+
The scan reaches `## Run now` and stops, where the collision check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
|
|
105
|
+
|
|
106
|
+
A task file neither surface names lands in a fourth array, on the same reasoning:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"unplaced": [
|
|
111
|
+
{
|
|
112
|
+
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
113
|
+
"message": "is a task file with no row on the board and no line on the backlog."
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Under the roster-checked hand-off `task-board` and `plan-groundwork` state, filing a task and placing its row are two acts a different session each may take, so a task caught between the two is ordinary rather than a finding and this array moves no exit code. It still reports, since it is the only local detector for a row a hand-edit dropped, a handoff message that never arrived, or an orchestrator that ended before placing it.
|
|
120
|
+
|
|
121
|
+
Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
|
|
122
|
+
|
|
123
|
+
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `backlog` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
|
|
124
|
+
|
|
125
|
+
Skills branch on the findings rather than on the exit code:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
canon tasks validate --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For the board format, the `Pull request:` line, and the archive rules, see `standards/tasks.md`.
|
package/docs/agents/tasks.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Tasks
|
|
3
|
-
description:
|
|
3
|
+
description: Claiming the next phase label, recording a pull request number and closing an outcome, listing each task's readiness, and where archiving, the plan verbs, and board validation are described
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Tasks
|
|
@@ -33,221 +33,13 @@ A claim is an exclusive `mkdir` at `.canon/ordinal-locks/<label>`, and the scan
|
|
|
33
33
|
canon tasks next-label --json | jq -r '.label'
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
## Archive
|
|
36
|
+
## Archive and decline
|
|
37
37
|
|
|
38
|
-
`canon tasks archive` moves a shipped task
|
|
38
|
+
`canon tasks archive` moves a shipped task into `.canon/tasks/archive/`, selected by its stem or by the pull request it carries, and `canon tasks decline` moves one decided against into `.canon/tasks/declined/`. Both carry the plan and the ready folder along when the task is their last citation. The selectors, the gates, and the refusal reasons are in `tasks-archive.md`.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
## Plan verbs
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
canon tasks archive v28.1-trigger-escalation # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
44
|
-
canon tasks archive --pull-request 673 --json
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
| Option | Behavior |
|
|
48
|
-
| -------------------- | ------------------------------------------------------------ |
|
|
49
|
-
| `--pull-request <n>` | Select the task whose `Pull request:` line names this number |
|
|
50
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
51
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
52
|
-
|
|
53
|
-
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.
|
|
54
|
-
|
|
55
|
-
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.
|
|
56
|
-
|
|
57
|
-
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 line is rewritten as `Plan: [feature-<slug>](../../plans/archive/feature-<slug>.md)`, a folder deeper than the live task wrote it. The `plan` field on the success record carries that `from` and `to`, and is `null` when nothing moved.
|
|
58
|
-
|
|
59
|
-
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 rebase runs before the plan and ready retargets, so a moved plan keeps its own line.
|
|
60
|
-
|
|
61
|
-
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.
|
|
62
|
-
|
|
63
|
-
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.
|
|
64
|
-
|
|
65
|
-
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.
|
|
66
|
-
|
|
67
|
-
`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.
|
|
68
|
-
|
|
69
|
-
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.
|
|
70
|
-
|
|
71
|
-
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.
|
|
72
|
-
|
|
73
|
-
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.
|
|
74
|
-
|
|
75
|
-
Skills branch on the reason rather than on the exit code:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
canon tasks archive --pull-request 673 --json | jq -r 'if .ok then .task else .reason end'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Decline
|
|
82
|
-
|
|
83
|
-
`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.
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
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
|
|
87
|
-
canon tasks decline v28.1-trigger-escalation --reason "no longer needed" --by Alex --json
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
| Option | Behavior |
|
|
91
|
-
| ----------------- | ------------------------------------------------- |
|
|
92
|
-
| `--reason <text>` | Why the task was decided against, required |
|
|
93
|
-
| `--by <name>` | Who decided, defaulting to `git config user.name` |
|
|
94
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
95
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
96
|
-
|
|
97
|
-
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.
|
|
98
|
-
|
|
99
|
-
`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.
|
|
100
|
-
|
|
101
|
-
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.
|
|
102
|
-
|
|
103
|
-
The task carries its plan with it the same way archive does, when the declining task is the last live citation. 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.
|
|
104
|
-
|
|
105
|
-
Skills branch on the reason rather than on the exit code, the same rule `canon tasks archive` states:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
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
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Plan citations
|
|
112
|
-
|
|
113
|
-
`canon tasks plan-citations <stem>` answers where a task's plan sits and which other live tasks hold it. It reports and never writes.
|
|
114
|
-
|
|
115
|
-
| Option | Effect |
|
|
116
|
-
| --------------- | ------------------------------------------- |
|
|
117
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
118
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
119
|
-
|
|
120
|
-
The record carries `location`, one of `unstated`, `live`, `archived`, or `outside`, and `citedBy`, the other live tasks whose `Plan:` line lands on the same file. Exit codes: `0` read, `1` refused with `no-board` or `no-match`.
|
|
121
|
-
|
|
122
|
-
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.
|
|
123
|
-
|
|
124
|
-
`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.
|
|
125
|
-
|
|
126
|
-
Branch on `reason` rather than on the exit code, which is the rule the archive section above already states 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.
|
|
127
|
-
|
|
128
|
-
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.
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
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
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Plan answers
|
|
135
|
-
|
|
136
|
-
`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.
|
|
137
|
-
|
|
138
|
-
Name the plan by its path or by its slug, which resolve to the same file:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
canon tasks plan-answers dispatch-answer-gate
|
|
142
|
-
canon tasks plan-answers .canon/plans/feature-dispatch-answer-gate.md
|
|
143
|
-
canon tasks plan-answers ../plans/feature-dispatch-answer-gate.md
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
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.
|
|
147
|
-
|
|
148
|
-
`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.
|
|
149
|
-
|
|
150
|
-
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.
|
|
151
|
-
|
|
152
|
-
| Option | Effect |
|
|
153
|
-
| --------------- | ------------------------------------------- |
|
|
154
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
155
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
156
|
-
|
|
157
|
-
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.
|
|
158
|
-
|
|
159
|
-
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.
|
|
160
|
-
|
|
161
|
-
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.
|
|
162
|
-
|
|
163
|
-
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.
|
|
164
|
-
|
|
165
|
-
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.
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
canon tasks plan-answers dispatch-answer-gate --json | jq -r '.launchable'
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## Plan branch
|
|
172
|
-
|
|
173
|
-
`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.
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
canon tasks plan-branch dispatch-answer-gate
|
|
177
|
-
canon tasks plan-branch .canon/plans/feature-dispatch-answer-gate.md --json
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
| Option | Effect |
|
|
181
|
-
| --------------- | ------------------------------------------- |
|
|
182
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
183
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
184
|
-
|
|
185
|
-
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.
|
|
186
|
-
|
|
187
|
-
`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.
|
|
188
|
-
|
|
189
|
-
`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.
|
|
190
|
-
|
|
191
|
-
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.
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
canon tasks plan-branch dispatch-answer-gate --json | jq -r '.branch'
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Plan reach
|
|
198
|
-
|
|
199
|
-
`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.
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
canon tasks plan-reach dispatch-answer-gate
|
|
203
|
-
canon tasks plan-reach dispatch-answer-gate --base origin/main --json
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
| Option | Effect |
|
|
207
|
-
| --------------- | ---------------------------------------------- |
|
|
208
|
-
| `--base <ref>` | Far side of the range, defaulting to the trunk |
|
|
209
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
210
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
211
|
-
|
|
212
|
-
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.
|
|
213
|
-
|
|
214
|
-
`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.
|
|
215
|
-
|
|
216
|
-
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.
|
|
217
|
-
|
|
218
|
-
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.
|
|
219
|
-
|
|
220
|
-
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.
|
|
221
|
-
|
|
222
|
-
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.
|
|
223
|
-
|
|
224
|
-
`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.
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
canon tasks plan-reach dispatch-answer-gate --json | jq -r '.claimed[] | "\(.path) held by \([.holders[].name] | join(", "))"'
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Plan link
|
|
231
|
-
|
|
232
|
-
`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.
|
|
233
|
-
|
|
234
|
-
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:
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
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
|
|
238
|
-
canon tasks plan-link v28.1-trigger-escalation .canon/plans/feature-dispatch-answer-gate.md --json
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
| Option | Behavior |
|
|
242
|
-
| --------------- | ------------------------------------------- |
|
|
243
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
244
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
245
|
-
|
|
246
|
-
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.
|
|
247
|
-
|
|
248
|
-
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. The `action` field reports `added`, `corrected`, or `unchanged`, which makes a rerun against the same plan safe.
|
|
249
|
-
|
|
250
|
-
Exit codes: `0` recorded, `1` refused. The `reason` field carries `no-board`, `no-match`, `no-plan`, or `bad-input`.
|
|
42
|
+
Five verbs read or write the plan a task cites. `plan-citations` reports where it sits and who else cites it, `plan-answers` whether it still waits on the operator, `plan-branch` the branch it derives, `plan-reach` what a branch reached against what the plan declared, and `plan-link` writes the task's `Plan:` line. Each verb's record, exit codes, and refusals are in `tasks-plans.md`.
|
|
251
43
|
|
|
252
44
|
## Pull request
|
|
253
45
|
|
|
@@ -322,125 +114,6 @@ canon tasks list --json | jq -r '.tasks[] | select(.readiness == "backlog") | .s
|
|
|
322
114
|
|
|
323
115
|
## Validate
|
|
324
116
|
|
|
325
|
-
`canon tasks validate` reports what each row of `priority.md` claims against what the tree holds.
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
canon tasks validate
|
|
329
|
-
canon tasks validate --json
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Seven checks run. Collisions reaches one half of the `## Run now` test the board standard states, and Plan reaches every group, reading the Plan column under `## Run now` and the task file's own line under the other two. Mapping and Grouping test the folder contract and hold for every group, and Shape holds for every group too, ahead of the four. Ordering reaches only the `## Needs a plan` rows, and Blockers reaches every row outside `## Run now`:
|
|
333
|
-
|
|
334
|
-
| Check | What it reports |
|
|
335
|
-
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
336
|
-
| Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
|
|
337
|
-
| Plan | A `## Run now` row whose Plan column carries no link, resolves to no file, or disagrees with the task's own line, a `## Needs a plan` row whose task cites a live plan, or an `## Up next` row whose task cites none |
|
|
338
|
-
| Mapping | A row or backlog line naming no task file |
|
|
339
|
-
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
340
|
-
| Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
|
|
341
|
-
| Collisions | Two `## Run now` rows whose Touches columns name a path in common |
|
|
342
|
-
| Blockers | A parked row whose blocker has stopped holding, whose cited task resolves nowhere, or whose cited task was declined |
|
|
343
|
-
|
|
344
|
-
Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
|
|
345
|
-
|
|
346
|
-
The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair naming two different plans is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
|
|
347
|
-
|
|
348
|
-
The other two groups have no Plan column, so the same check reads the task file's own `Plan:` line against what the group name claims. A `## Needs a plan` row whose task cites a plan that sits live on disk is `plan-parked`, since the group says no such plan exists and the row is either misplaced or its plan is stale and belongs in the archive. An `## Up next` row whose task cites no live plan is `plan-absent`, since that group says one is written. A plan already archived, or a cited path with no file behind it, reads as not live on both sides. A row whose task file is gone is left to `task-unresolved`. An `## Up next` row with nothing left to build, waiting on an act only a person can take, reports `plan-absent` once its plan archives while the task stays live. That report is expected, since the check reads the task file and not the `Waiting on` prose, so a row in that state moves to `## Needs a plan` or closes.
|
|
349
|
-
|
|
350
|
-
Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A row or a backlog line naming no task file is `task-unresolved`, and a file both surfaces name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. A task file neither surface names is `unplaced` rather than a finding, since that is the normal state between a session filing it and a live orchestrator placing it on the board. One check across both is what lets a task move between them without the move reading as a dropped file.
|
|
351
|
-
|
|
352
|
-
A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name still lands in the `unplaced` array below rather than the findings. A project carrying no `backlog.md` reads as an empty backlog rather than a refusal, which leaves the one-to-one mapping this check ran before the second surface existed.
|
|
353
|
-
|
|
354
|
-
Ordering reads a `## Needs a plan` row's `Waiting on` cell for the position it claims, and reports two failures off one walk. A row stating an ordinal is checked against where it actually sits, which is `row-misordered`. A row claiming no position in either form it may take is `row-unranked`, since its cell argues the task matters and ranks it against nothing, which leaves the order recording when each row was filed.
|
|
355
|
-
|
|
356
|
-
The ordinal phrase is prose rather than data, searched for anywhere in the cell rather than at its start, since every live row states its position at the end of a sentence rather than at the front. The vocabulary stops at `first` through `twentieth` plus `last`, since a parser strict enough to catch a gap would otherwise flag a row phrased correctly and differently, and bounding it to those words is what keeps a cell reading `Untestable from here` from matching on `from`. One comparison against the real position catches a gap, a duplicate, and a sequence starting somewhere other than first alike.
|
|
357
|
-
|
|
358
|
-
The comparative phrase is bounded the same way and for the same reason. A closed verb list of `leads`, `heads`, `opens`, `closes`, `trails`, `precedes`, `follows`, `outranks`, and `sits under`, `above`, or `below` has to sit in one clause with a positional object, which is a `vNN.N` phase label, the word `group`, or `row` or `rows`. Both halves are needed, since a cell reading `it closes a gap the reference gate leaves open` carries the verb and claims no position, and the clause bound is what stops a verb in one half of the cell pairing with an object in the other. An ordinal exempts the row, being a comparative claim already. The residue runs both ways: a cell phrased comparatively and unusually reports as unranked, which is the false negative and the safe direction for a check over prose, and a vocabulary verb reaching a positional object non-positionally reports as ranked, which is the false positive. Separating the second from a real position claim means grading prose, so the bound stays where it is.
|
|
359
|
-
|
|
360
|
-
The collision check is the one a person cannot run by eye. Paths come from the backticked spans in the Touches column, a span naming no file is dropped, and a directory collides with any file beneath it. A `## Run now` row whose column parses to nothing is reported rather than skipped, since a row stating no file set makes a claim nothing can check.
|
|
361
|
-
|
|
362
|
-
Where a directory holds the other row's file, the finding names the row that claimed it, reading `both touch src/tasks, which v2.0-second claims as a folder.` The shared strings alone leave an over-broad cell and a genuine overlap identical, which is how a correct report was once read as the verb comparing folders rather than files. <!-- canon-allow-reference: illustrates the finding's own sentence shape, not a citation of a real task -->
|
|
363
|
-
|
|
364
|
-
The blocker check re-takes a measurement the board records once and never repeats. Two of the five blocker kinds put a fact on disk: a dependency is settled by the cited task being archived or by its work reaching the trunk, and a collision is settled by nothing under `## Run now` still holding the file the cell cites. A cited task resolving under `.canon/tasks/declined/` instead reports separately as `blocker-declined`, since a decided-against task is neither the shipped work `blocker-settled` reports nor the dangling pointer `blocker-unresolved` reports.
|
|
365
|
-
|
|
366
|
-
```json
|
|
367
|
-
{
|
|
368
|
-
"findings": [
|
|
369
|
-
{
|
|
370
|
-
"kind": "blocker-declined",
|
|
371
|
-
"group": "Up next",
|
|
372
|
-
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
373
|
-
"message": "waits on v9.0-superseded, which was declined." // canon-allow-reference: illustrates the finding's own sentence shape, not a citation of a real task
|
|
374
|
-
}
|
|
375
|
-
]
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
A closed outcome is not the fact the dependency half needs. The ship chain marks outcomes as its first step and opens the pull request several steps later, so a check reading the checkbox reports the row settled while the branch is still in review. A live task therefore settles the row only once it closed every outcome and carries a `Pull request:` line the trunk holds. One that names no pull request, and one whose number no trunk ref could answer for, land in the untested array below rather than being settled or left silent.
|
|
380
|
-
|
|
381
|
-
The trunk is read as the clone already holds it, `origin/main` first and local `main` behind it, and no run fetches. A validate runs several times a sweep and a fetch per run is a cost this command does not carry, so a clone behind its remote under-reports rather than claiming work landed.
|
|
382
|
-
|
|
383
|
-
Both halves gate on a citation inside the `Waiting on` cell, never on the columns beside it. The board format gives a collision cell the file held by the running task, so a row whose cell names no file was parked by something else, and testing its Touches column instead reports a cleared collision on a row no collision ever parked while counting that row as re-tested. A cited task is a bare sibling link, the way the Task column spells one, so a pointer into another folder names a plan rather than a task and settles nothing. A cited task carrying no outcome box settles nothing either, since a file the check could not parse is not evidence of a finished one.
|
|
384
|
-
|
|
385
|
-
A citation resolving in none of the board, the archive, or the declined folder is `blocker-unresolved` rather than a settled row. Reading an absent file as archived or declined states a specific fact about a file nobody ever wrote, which is what a renamed task or a typo produces, and only a task that genuinely closed or was genuinely declined releases the row waiting on it.
|
|
386
|
-
|
|
387
|
-
The other three kinds rest on a person's judgment, so a row neither half reached lands in a second array rather than in the findings:
|
|
388
|
-
|
|
389
|
-
```json
|
|
390
|
-
{
|
|
391
|
-
"untested": [
|
|
392
|
-
{
|
|
393
|
-
"group": "Needs a plan",
|
|
394
|
-
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
395
|
-
"message": "..."
|
|
396
|
-
}
|
|
397
|
-
]
|
|
398
|
-
}
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
An untested row is not a finding and moves no exit code. Reading a clean findings list as a clean board is the failure the array exists to prevent, and `orchestrator-parked.md` is the pass that takes those rows by hand.
|
|
402
|
-
|
|
403
|
-
A `## Run now` row whose Touches column names a bare folder lands in a third array on the same reasoning:
|
|
404
|
-
|
|
405
|
-
```json
|
|
406
|
-
{
|
|
407
|
-
"claims": [
|
|
408
|
-
{
|
|
409
|
-
"group": "Run now",
|
|
410
|
-
"subject": "v1.0-first", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
411
|
-
"message": "claims the whole src/tasks folder, so it collides with every row written under it."
|
|
412
|
-
}
|
|
413
|
-
]
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
That claim collides with every row a later session writes under the folder, and it is legitimate whenever the row does rewrite the directory, so the array states the reach and moves no exit code. A measure failing on a cell that is right teaches a reader to skip it. Folder against file is decided by asking the tree for a path that resolves, and by the extension only for a path the row has yet to create, since the name alone reads a file carrying no extension as a folder.
|
|
418
|
-
|
|
419
|
-
The scan reaches `## Run now` and stops, where the collision check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
|
|
420
|
-
|
|
421
|
-
A task file neither surface names lands in a fourth array, on the same reasoning:
|
|
422
|
-
|
|
423
|
-
```json
|
|
424
|
-
{
|
|
425
|
-
"unplaced": [
|
|
426
|
-
{
|
|
427
|
-
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
428
|
-
"message": "is a task file with no row on the board and no line on the backlog."
|
|
429
|
-
}
|
|
430
|
-
]
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
Under the roster-checked hand-off `task-board` and `plan-groundwork` state, filing a task and placing its row are two acts a different session each may take, so a task caught between the two is ordinary rather than a finding and this array moves no exit code. It still reports, since it is the only local detector for a row a hand-edit dropped, a handoff message that never arrived, or an orchestrator that ended before placing it.
|
|
435
|
-
|
|
436
|
-
Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
|
|
437
|
-
|
|
438
|
-
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `backlog` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
|
|
439
|
-
|
|
440
|
-
Skills branch on the findings rather than on the exit code:
|
|
441
|
-
|
|
442
|
-
```bash
|
|
443
|
-
canon tasks validate --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
|
|
444
|
-
```
|
|
117
|
+
`canon tasks validate` reports what each row of `priority.md` and each line of `backlog.md` claims against what the tree holds. Its seven checks, the three arrays that move no exit code, and the refusals are in `tasks-validate.md`.
|
|
445
118
|
|
|
446
119
|
For the board format, the `Pull request:` line, and the archive rules, see `standards/tasks.md`.
|
package/docs/target-projects.md
CHANGED
|
@@ -57,7 +57,7 @@ The chain is:
|
|
|
57
57
|
|
|
58
58
|
- `canon init` installs base tooling, Claude seeds, and governance rules into `.claude/rules/` in the same pass
|
|
59
59
|
- `canon tooling sync <stack> --write` adds stack-specific deps, scripts, and gitignore entries
|
|
60
|
-
- The agent reads `canon tooling reference <stack>` (plus parents) as its audit context, follows it to generate eslint, vitest, playwright configs and the stack's setup script, and extends `canon/context/ci.md` and `canon/context/development.md` per the reference's extend sections <!-- audit-ignore-citations: canon/context/development.md -->
|
|
60
|
+
- The agent reads `canon tooling reference <stack>` (plus parents) as its audit context, follows it to generate eslint, vitest, playwright configs and the stack's setup script, and extends `canon/context/ci.md` and `canon/context/development.md` per the reference's extend sections <!-- audit-ignore-citations: canon/context/ci.md, canon/context/development.md -->
|
|
61
61
|
- The `verify` phase runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
|
|
62
62
|
- The `indexes` phase bootstraps the `index.md` system over the project's own documentation folders, confirming candidate folders with the operator rather than running unattended
|
|
63
63
|
|
|
@@ -80,7 +80,7 @@ A machine without a renderer still gets the diagrams and is told which check was
|
|
|
80
80
|
|
|
81
81
|
Each diagram entry records the commit and date it was last verified against, and nothing maintains that record for you. The folder is redrawn on demand rather than swept on every ship, so `verified` carries the whole signal: an entry whose date sits far behind your branch is due a read, and no pass will name which one. Run `canon:draft-diagram` again when the code a kind is drawn from moves.
|
|
82
82
|
|
|
83
|
-
`canon/ARCHITECTURE.md` carries the same mechanism on the same ship. `canon:
|
|
83
|
+
`canon/ARCHITECTURE.md` carries the same mechanism on the same ship. `canon:context-fold` anchors a decision it amends to the paths that decision cites, and reports an anchored decision whose cited path the branch touched.
|
|
84
84
|
|
|
85
85
|
### Stack decision
|
|
86
86
|
|
|
@@ -102,7 +102,7 @@ Run `canon tooling list --json` and `canon gov list --json` to see the current c
|
|
|
102
102
|
|
|
103
103
|
`canon init` installs base tooling, Claude workflow, and governance, and scaffolds `.claude/wiki/`. Governance defaults to the `base` stack, so a bare init carries the rules that route a project. Pass `--stack <name>` to install a framework stack instead.
|
|
104
104
|
|
|
105
|
-
No standard is written into the project. Each governance rule's authority line names `canon standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `canon markdown audit` needs no standard at all, its ban
|
|
105
|
+
No standard is written into the project. Each governance rule's authority line names `canon standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `canon markdown audit` needs no standard at all, its ban set and checkpoints shipping with the package as data.
|
|
106
106
|
|
|
107
107
|
No snippet is written into the project either. `claude/snippets` in the plugin cache symlinks to the toolkit's own `snippets/`, so a session reaches one at its `@` reference with no install step, the same live resolution `claude/standards` gives a rule's authority line.
|
|
108
108
|
|
|
@@ -181,7 +181,7 @@ The first line reports the plan and the second applies it, relocating each rule
|
|
|
181
181
|
|
|
182
182
|
### Rename the skill citations, once
|
|
183
183
|
|
|
184
|
-
Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:docs
|
|
184
|
+
Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. <!-- canon-keep-retired -->
|
|
185
185
|
|
|
186
186
|
Two of those files run rather than sit there. `.husky/post-merge` prints a command for a person to type, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
|
|
187
187
|
|