@erclx/canon 5.39.0 → 5.40.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/README.md +5 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-ui/SKILL.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +29 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +160 -43
- package/src/commands/records.ts +8 -0
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +4 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +5 -17
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/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
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
package/docs/agents/slides.md
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Slides
|
|
3
|
-
description: Where a deck folder lives and what it holds, what it declares for the whole deck in deck.json, the faces it embeds, what each slide declares on its own body, transitions and entrances, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Slides
|
|
7
|
-
|
|
8
|
-
`canon slides render <deck>` turns a folder of `.html` slides into an editable PowerPoint deck. Each deck lives in its own folder under `.canon/slides/`, which `canon records push` backs and git ignores, and `canon slides list --json` lists them. Each slide describes its own shapes. What belongs to the whole deck, being the bands, the numbers, and the mark, sits in one file beside them, and the master that carries it takes the project's own colors and face.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
canon slides list --json
|
|
12
|
-
canon slides render q3-review
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## The deck folder
|
|
16
|
-
|
|
17
|
-
| Path | Holds |
|
|
18
|
-
| -------------------------------- | ---------------------------------------------------- |
|
|
19
|
-
| `.canon/slides/<deck>/*.html` | the slides, rendered in filename order |
|
|
20
|
-
| `.canon/slides/<deck>/deck.json` | what belongs to the whole deck, described below |
|
|
21
|
-
| `.canon/slides/<deck>/assets/` | images the slides reference by a relative path |
|
|
22
|
-
| `.canon/slides/layouts/` | the project's shared layouts, never listed as a deck |
|
|
23
|
-
|
|
24
|
-
A folder holding no `.html` file is not a deck. `render` with no name takes the only deck there is, and refuses with the list when there are several. It also takes a folder path outside `.canon/slides/`.
|
|
25
|
-
|
|
26
|
-
## The deck file
|
|
27
|
-
|
|
28
|
-
`deck.json` beside the slides is optional. Without it the deck gets its defaults.
|
|
29
|
-
|
|
30
|
-
| Field | Default | What it sets |
|
|
31
|
-
| -------------- | ------------------------------ | ------------------------------------------------- |
|
|
32
|
-
| `title` | the folder name | the file's title and the default footer text |
|
|
33
|
-
| `header` | off | a band at the top, with `left`, `center`, `right` |
|
|
34
|
-
| `footer` | on, with the title on the left | a band at the bottom, with the same three slots |
|
|
35
|
-
| `slideNumbers` | `true` | the number at the right of the footer |
|
|
36
|
-
| `mark` | none | an image path, relative to the folder, top right |
|
|
37
|
-
| `fonts` | none | faces to embed in the file, described below |
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"title": "Q3 review",
|
|
42
|
-
"header": { "left": "Acme" },
|
|
43
|
-
"footer": { "center": "Internal" },
|
|
44
|
-
"mark": "assets/mark.png",
|
|
45
|
-
"fonts": [
|
|
46
|
-
{ "family": "Inter", "path": "fonts/Inter-Regular.ttf" },
|
|
47
|
-
{ "family": "Inter", "weight": 700, "path": "fonts/Inter-Bold.ttf" }
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
A band takes `show` and the three slots. A declared band merges over its default, so `"footer": { "center": "Internal" }` keeps the title on the left. Declaring any slot turns a band on unless it also says `"show": false`. Set a slot to `""` to empty it.
|
|
53
|
-
|
|
54
|
-
Slide numbers belong to the footer and hold its right slot. They show only where the footer does, and `footer.right` needs `slideNumbers` set to `false`.
|
|
55
|
-
|
|
56
|
-
Prefer a raster mark. An SVG mark lands as a vector, and its fallback for a viewer that cannot draw SVG comes out broken.
|
|
57
|
-
|
|
58
|
-
### Embedded faces
|
|
59
|
-
|
|
60
|
-
Each entry in `fonts` names one TrueType or OpenType file inside the deck folder, and the deck carries it so it renders in its own face on a machine without the font installed.
|
|
61
|
-
|
|
62
|
-
- `family` is required and should match the family the slides set, since that is the name PowerPoint looks the face up by.
|
|
63
|
-
- `weight` defaults to `400`. PowerPoint holds four faces a family, so `600` and above fills the bold slot and anything lighter fills regular.
|
|
64
|
-
- `style` is `normal` or `italic`, defaulting to `normal`.
|
|
65
|
-
- `path` is relative to the folder and has to stay inside it.
|
|
66
|
-
|
|
67
|
-
A face is refused, and the deck still written without it, when its license forbids embedding or allows a bitmap only, when it is a variable font, when it is a `woff` or `woff2` file, or when a second face lands in a slot one already fills. Supply static instances, one per weight, in place of a variable face. For `woff2`, supply the TrueType or OpenType file it was compressed from, which most font downloads ship beside it.
|
|
68
|
-
|
|
69
|
-
## What each slide declares
|
|
70
|
-
|
|
71
|
-
A slide sets its own place in the deck through attributes on `<body>`.
|
|
72
|
-
|
|
73
|
-
| Attribute | Effect |
|
|
74
|
-
| --------------------------------------- | --------------------------------------------- |
|
|
75
|
-
| `data-section="Results"` | opens a section the slides after it stay in |
|
|
76
|
-
| `data-hidden` | hides the slide in the show |
|
|
77
|
-
| `data-header="off"` | drops the header on this slide |
|
|
78
|
-
| `data-footer="off"` | drops the footer and its number on this slide |
|
|
79
|
-
| `data-footer-left`, `-center`, `-right` | replaces that footer slot on this slide alone |
|
|
80
|
-
| `data-header-left`, `-center`, `-right` | replaces that header slot on this slide alone |
|
|
81
|
-
|
|
82
|
-
Once any slide opens a section, slides ahead of the first one sit in a section named after the deck, since PowerPoint wants every slide in one. A title the deck returns to after another section opens a new section numbered `Intro (2)`, so each section stays one run of slides.
|
|
83
|
-
|
|
84
|
-
A slide dropping one band keeps the other from the master. A slide replacing a slot draws its band as its own text, so editing that band once in PowerPoint's master view reaches every slide except the ones that replaced it.
|
|
85
|
-
|
|
86
|
-
## Motion
|
|
87
|
-
|
|
88
|
-
A slide declares how it arrives on `<body>`, and an element declares how it comes in on its own tag.
|
|
89
|
-
|
|
90
|
-
| Attribute | Takes | Effect |
|
|
91
|
-
| -------------------------- | ------------------------------- | -------------------------------------- |
|
|
92
|
-
| `data-transition` | `fade`, `push`, `wipe`, `cover` | the transition into this slide |
|
|
93
|
-
| `data-transition-duration` | `500`, `500ms`, or `0.5s` | rounded to 0.5, 0.75, or 1 second |
|
|
94
|
-
| `data-enter` | `fade`, `fly`, `wipe`, `zoom` | an entrance played on a click |
|
|
95
|
-
| `data-enter-order` | a number | the click it plays on, lowest first |
|
|
96
|
-
| `data-enter-duration` | `500`, `500ms`, or `0.5s` | the entrance length, 500 ms by default |
|
|
97
|
-
|
|
98
|
-
```html
|
|
99
|
-
<body data-transition="fade">
|
|
100
|
-
<h1>Results</h1>
|
|
101
|
-
<div class="card" data-enter="fly" data-enter-order="1">…</div>
|
|
102
|
-
<div class="card" data-enter="fly" data-enter-order="2">…</div>
|
|
103
|
-
</body>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Each entrance is its own click. Entrances without an order play after the ordered ones, in page order. An entrance brings in every shape drawn from its element and the elements inside it together, so a card's box and its text arrive on one click, and an element drawn as a picture brings in that picture. An element inside it with its own `data-enter` waits for its own click. Fly comes in from the bottom and wipe from the left.
|
|
107
|
-
|
|
108
|
-
A transition length rounds to the nearest of the three speeds PowerPoint's file format names, since a length in milliseconds needs an extension the deck does not write.
|
|
109
|
-
|
|
110
|
-
## The master
|
|
111
|
-
|
|
112
|
-
The master takes its colors from the `<html>` element of the first slide as Chromium laid it out with the project's token stylesheet injected, so a theme switched on one slide's `<body>`, such as a dark cover, stays on that slide:
|
|
113
|
-
|
|
114
|
-
- `--color-background`: the slide ground
|
|
115
|
-
- `--color-text`: the header text
|
|
116
|
-
- `--color-muted`: the footer text
|
|
117
|
-
- `--color-accent`: the slide number
|
|
118
|
-
- the `<body>` font family, first name with a ` Variable` suffix dropped: the theme face
|
|
119
|
-
|
|
120
|
-
A role the project declares no token for falls back to the slide body's own color and prints one `✗` line naming it. It never falls back to the toolkit's palette.
|
|
121
|
-
|
|
122
|
-
A slide whose `<body>` background differs from the master's keeps its own. A slide whose `<body>` computes a `--color-text` apart from the master's prints one `✗` line naming it, since its bands keep the master's colors and may not read against its ground.
|
|
123
|
-
|
|
124
|
-
## Speaker notes
|
|
125
|
-
|
|
126
|
-
`<aside class="notes">` becomes the slide's notes and is never drawn. Each paragraph or list item becomes its own line, and markup is dropped.
|
|
127
|
-
|
|
128
|
-
## Charts
|
|
129
|
-
|
|
130
|
-
A `<figure data-chart="...">` holding a `<table>` becomes a native, editable chart placed where the figure was laid out. The table stays the page's accessible source and is not drawn as well.
|
|
131
|
-
|
|
132
|
-
```html
|
|
133
|
-
<figure data-chart="bar" data-labels>
|
|
134
|
-
<figcaption>Revenue by region</figcaption>
|
|
135
|
-
<table>
|
|
136
|
-
<tr>
|
|
137
|
-
<th>Region</th>
|
|
138
|
-
<th>Q1</th>
|
|
139
|
-
<th>Q2</th>
|
|
140
|
-
</tr>
|
|
141
|
-
<tr>
|
|
142
|
-
<td>North</td>
|
|
143
|
-
<td>12</td>
|
|
144
|
-
<td>16</td>
|
|
145
|
-
</tr>
|
|
146
|
-
<tr>
|
|
147
|
-
<td>South</td>
|
|
148
|
-
<td></td>
|
|
149
|
-
<td>11</td>
|
|
150
|
-
</tr>
|
|
151
|
-
</table>
|
|
152
|
-
</figure>
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
- `data-chart` takes `bar`, `line`, `area`, `pie`, or `doughnut`.
|
|
156
|
-
- The header row names the series and the first column names the categories.
|
|
157
|
-
- An empty cell is a gap, not a zero. A comma is a thousands separator only where it groups in threes, as in `12,345.5`, so a comma-decimal `3,5` refuses rather than reading as 35.
|
|
158
|
-
- `data-labels` shows each value. A `<figcaption>` becomes the chart title.
|
|
159
|
-
- Series take the accent first, then every other declared `--color-*` role in sheet order except the background and surface, repeating past the last.
|
|
160
|
-
|
|
161
|
-
## Refusals
|
|
162
|
-
|
|
163
|
-
| Case | Report | Exit |
|
|
164
|
-
| -------------------------------------- | ---------------------------------------------- | ---- |
|
|
165
|
-
| `deck.json` field malformed or unknown | `✗` naming the file and the field | 1 |
|
|
166
|
-
| `mark` path missing | `✗` naming the resolved path | 1 |
|
|
167
|
-
| `fonts` path missing or outside folder | `✗` naming the field and the path | 1 |
|
|
168
|
-
| face not embeddable | `✗` naming the path and the reason | 0 |
|
|
169
|
-
| unknown transition or entrance name | `✗` naming the slide, the element, the effect | 0 |
|
|
170
|
-
| entrance on an element that draws none | `✗` naming the slide and the element | 0 |
|
|
171
|
-
| chart cell not a number | `✗` naming the slide, the figure, and the cell | 0 |
|
|
172
|
-
| `data-chart` outside the five types | `✗` naming the value | 0 |
|
|
173
|
-
| token role not declared | `✗` naming the role | 0 |
|
|
174
|
-
| slide body sets its own `--color-text` | `✗` naming the slides | 0 |
|
|
175
|
-
|
|
176
|
-
A refused chart, effect, or face is left out and the rest of the deck is written.
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: State-scoped risk
|
|
3
|
-
description: Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# State-scoped risk
|
|
7
|
-
|
|
8
|
-
Every review surface a session can reach is scoped to a change. `review-pr` reads a pull request, and `code-review` takes a diff, a branch, or a path. A risk that arrived before the range under review is invisible to both by construction, which is what these two commands answer.
|
|
9
|
-
|
|
10
|
-
| Question | Command |
|
|
11
|
-
| -------------------------------------------------------- | -------------------- |
|
|
12
|
-
| Does a credential sit in the tree this repository ships? | `canon secrets scan` |
|
|
13
|
-
| Does a resolved dependency carry a published advisory? | `canon deps audit` |
|
|
14
|
-
|
|
15
|
-
Neither reads a range. That boundary is deliberate: change-scoped correctness review already exists, and a state-scoped bug scan would be a different product competing with it. Both register in `canon audits run`, so a reader who runs the aggregate gets them without knowing they exist.
|
|
16
|
-
|
|
17
|
-
## Secrets in the shipped tree
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
canon secrets scan
|
|
21
|
-
canon secrets scan --json
|
|
22
|
-
canon secrets scan ../my-app
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The corpus is the package's own `files` field rather than a list the check keeps. That field is the single statement of which trees leave this repository, so a second list beside it would answer the same question and drift. It also carries the negations the publish already makes, which is what puts the eval tree and every test file out of scope by the rule that keeps them out of the tarball rather than by an exclusion this check invented. The sandbox tree sits under no `files` entry at all, so the same field leaves it out without a negation.
|
|
26
|
-
|
|
27
|
-
The plugin reaches a target by a different route, loading live from `claude/` rather than from a tarball. That folder is a `files` entry too, so both routes land inside the same corpus.
|
|
28
|
-
|
|
29
|
-
Three root files are read whether or not the field names them, being `package.json`, the readme, and the license, since npm packs those on every publish. A negation still removes one, because a field that excludes a file outranks the default that included it.
|
|
30
|
-
|
|
31
|
-
### What the corpus leaves out
|
|
32
|
-
|
|
33
|
-
The corpus answers what the package publishes, which is narrower than what the repository holds. Measured against this repository on 2026-08-21, the scan read 544 files and left 593 of the 1139 git lists unread. Those include everything under `.claude/`, `wiki/`, and `internal/`, the workflow definitions under `.github/`, and the trees the publish negations remove, being `scripts/eval/` and every test file. The scenario tree was a third negation at that reading and now sits at `sandbox/` under no `files` entry, so it stays unread. `src/capture/` was a fourth negation when that reading was taken and is not one now. The folder holds 5 tracked files, 2 of them tests the publish still excludes, so the corpus is 3 wider than the numbers above describe and the unread count is 3 smaller.
|
|
34
|
-
|
|
35
|
-
A public repository makes that gap readable by anyone, so a clean run means no credential in the published tree rather than none in the repository. The run states the number on every pass, including a clean one, so the bound travels with the verdict. Widening the corpus to every tracked file is a separate decision, since the row this implements puts the shipped tree first on the toolkit's rule that content leaving the repository gates harder than content that stays.
|
|
36
|
-
|
|
37
|
-
### What it keys on
|
|
38
|
-
|
|
39
|
-
Every pattern matches an issued value and none of them matches a word. A scan keyed on `password`, `secret`, or `token` fires on the environment reads, the workflow inputs, and the prose that name those things, and this repository ships all three. Keying on values instead is what makes the exclusion set empty: measured across the shipped tree on 2026-08-21, 544 files produced zero findings with nothing exempted.
|
|
40
|
-
|
|
41
|
-
The set covers issuer-stamped shapes, being AWS access key ids, GitHub tokens in both forms, Google API keys, Slack tokens and webhooks, Stripe live keys, Anthropic and OpenAI keys, npm tokens, and private key block headers. What it does not reach is a credential no issuer stamps recognizably, which no exclusion policy would have helped with either.
|
|
42
|
-
|
|
43
|
-
A reported value is redacted to its two ends. Those are what a reader needs to find it in the file and to tell one match from another, and the middle is the part no report should carry.
|
|
44
|
-
|
|
45
|
-
### Exempting a deliberate value
|
|
46
|
-
|
|
47
|
-
A line carrying a credential-shaped value on purpose takes an inline marker, either on the line itself or on the line directly above it:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# canon-allow-secret: documented sample from the vendor's own reference
|
|
51
|
-
AWS_KEY="<the sample value>"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Only a marker naming a reason counts. A bare token reads as a line that meant to say something and did not, which is the rule `stub: true` already applies to a seed field set to anything but `true`, and honoring it would let a typo mute a finding.
|
|
55
|
-
|
|
56
|
-
The exemption travels with the line rather than sitting in a path list away from it, so a reader meeting a muted match finds the reason on the spot. A path allow-list was weighed and declined: the noise it would target is word-keyed and spread past the fixture trees, so it would hide part of the noise and none of the risk.
|
|
57
|
-
|
|
58
|
-
### Exit codes and gating
|
|
59
|
-
|
|
60
|
-
Exit codes are `0` when the shipped tree carries no credential-shaped value, `1` for a refusal, and `2` for at least one value found.
|
|
61
|
-
|
|
62
|
-
This is the one entry in `canon audits run` that gates without a `canon gate run` stage behind it. A credential in the published tree is a fact rather than a judgment, which is the test the catalog asks any gating addition to pass, and content leaving the repository ranks above content that stays.
|
|
63
|
-
|
|
64
|
-
A refusal is never a clean tree. Five reasons produce one, and each exits `1`, because zero findings over zero files reads in the report exactly like zero findings over the whole shipped tree.
|
|
65
|
-
|
|
66
|
-
| Reason | What it means | In the aggregate |
|
|
67
|
-
| ------------------ | ------------------------------------------------------------ | ---------------- |
|
|
68
|
-
| `no-manifest` | No `package.json`, so nothing is published from this tree | absent |
|
|
69
|
-
| `no-publish` | The manifest declares `private`, so it publishes nothing | absent |
|
|
70
|
-
| `no-shipped-files` | The `files` field matched nothing git lists | absent |
|
|
71
|
-
| `no-files-field` | A publish would pack the whole tree, and none of it was read | unmeasured |
|
|
72
|
-
| `no-git` | git could not list the tree, so the corpus is unknown | unmeasured |
|
|
73
|
-
|
|
74
|
-
The split turns on whether a corpus exists. The first three mean this project publishes nothing, which is where most targets installing this CLI sit, so reporting them as unmeasured would pin the aggregate at `incomplete` there forever.
|
|
75
|
-
|
|
76
|
-
The last two mean a corpus exists and went unread, so neither is softened. `private` is what separates them, since a manifest with no `files` field publishes everything rather than nothing and the field alone cannot tell those apart. Declaring `private: true` is the way a project that never publishes says so.
|
|
77
|
-
|
|
78
|
-
## Advisories against the resolved dependencies
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
canon deps audit
|
|
82
|
-
canon deps audit --json
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
The check shells the runtime's own advisory command rather than carrying an index. A vendored advisory database is a second corpus to keep current, and what this is worth is the report rather than the data.
|
|
86
|
-
|
|
87
|
-
It refuses for three reasons, and each is an absence rather than a break: no `package.json`, no lockfile beside it, and no record back from the lookup. The lockfile is checked here rather than left to the underlying command, because a project whose dependencies were never resolved needs an install and not a retry, and one message naming the network would send half the readers at the wrong cause.
|
|
88
|
-
|
|
89
|
-
That buys the one failure mode no other audit here carries. The command reaches a network, so an unreachable index has to be told from a tree with nothing against it. The exit code cannot separate them, since the underlying command exits non-zero both on advisories found and on a lookup that failed, so the record on stdout is what decides: output that parses is a measurement, and output that does not is a refusal under `no-record`.
|
|
90
|
-
|
|
91
|
-
Exit codes are `0` when nothing is published against the resolved set, `1` for a refusal, and `2` for at least one advisory.
|
|
92
|
-
|
|
93
|
-
### Why it reports rather than gates
|
|
94
|
-
|
|
95
|
-
A published advisory is a fact about the index and a judgment about this tree, since the upgrade closing it may not exist yet. A push failing on one would teach a contributor to route around the stage while nothing about the dependency has changed, which is the split every other reporting measure here rests on.
|
|
96
|
-
|
|
97
|
-
The corpus is recorded as `upstream`, a third value beside `tracked` and `per-machine`. Its count moves when someone publishes rather than when someone edits here, so a retained baseline would report growth against a tree nobody touched, and the aggregate keeps it out of the record for the mirror image of the reason it keeps gitignored scratch out.
|
|
98
|
-
|
|
99
|
-
The same value decides what an offline run reports. An index this machine could not reach is an ordinary absence rather than a broken checkout, so it lands as `absent` and moves no verdict. Reading it as unmeasured would pin the aggregate at `incomplete` on every machine without a network, which is a signal nobody reads after the second time they see it.
|
|
100
|
-
|
|
101
|
-
## Reading either from a skill
|
|
102
|
-
|
|
103
|
-
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the `--json` record instead.
|
|
104
|
-
|
|
105
|
-
The secret scan publishes `findings`, `files`, `skipped`, and `listed`, where `listed` is everything git reports so a consumer can state the bound alongside the verdict. The advisory check publishes `advisories` and a `severities` object. A refusal from either publishes `reason` and `message` and no measurement keys at all, which is what separates it from a clean run.
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Superseded values
|
|
3
|
-
description: Reading where the tree still asserts a value a changed convention no longer produces, why the sweep keys on the value rather than the file, the family stem behind a templated citation, the exemption marker, the blind spots it cannot reach, and why it reports rather than gates
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Superseded values
|
|
7
|
-
|
|
8
|
-
`canon gov superseded <superseded> <replacement>` reports where the tree still asserts a value the convention behind it no longer produces. It answers the case a diff cannot: a convention changes, and the declarations testing it keep asserting the old form with nothing pointing at them.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
canon gov superseded feature-feat- feature-
|
|
12
|
-
canon gov superseded feature-feat- feature- --json
|
|
13
|
-
canon gov superseded old-name new-name --root ../my-app
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
| Option | Behavior |
|
|
17
|
-
| --------------- | ---------------------------------------------------------- |
|
|
18
|
-
| `--root <path>` | Tree to read, defaulting to the current directory |
|
|
19
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
20
|
-
|
|
21
|
-
Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode.
|
|
22
|
-
|
|
23
|
-
## Why the value is the key
|
|
24
|
-
|
|
25
|
-
A file-scoped map from the changed rule to what cites it reaches nothing here. A fixture asserting an old output names neither the rule nor the standard behind it, so the change that superseded the value touches no file the fixture can be found from. The value both sides carry is the only key there is.
|
|
26
|
-
|
|
27
|
-
The slug transform is what proved it. Dropping the type segment left four fixtures and three scenario scripts asserting the type-carrying spelling. One surfaced as a red sandbox arm, which named a failing scenario rather than a stale declaration, and the other six were found by deriving from the rule rather than by anything reporting them.
|
|
28
|
-
|
|
29
|
-
## What it reads
|
|
30
|
-
|
|
31
|
-
The whole corpus git lists: tracked files plus untracked ones git does not ignore. The untracked half keeps a file added on this branch in scope rather than one push later.
|
|
32
|
-
|
|
33
|
-
Matching is a literal substring, for the value and for the family stem below it alike, so either one carrying regular-expression characters means what it says. Every occurrence on a line reports under its own column, since a line carrying the value twice is two edits, and a column both kinds reach reports once as the literal one.
|
|
34
|
-
|
|
35
|
-
Binary files are skipped on a NUL byte rather than on an extension list, and so is any listed path that will not open. Both are counted. A run states the files it opened against the files git listed, because a count of what passed reads as a verdict on the repository unless it also says how much it left out.
|
|
36
|
-
|
|
37
|
-
Pass an empty replacement to retire a value outright, as in `canon gov superseded feature-feat- ""`. Findings report the same way, none is annotated, since a line cannot carry a replacement that does not exist, and no templated form is read, since there is no second value for a stem to diverge from.
|
|
38
|
-
|
|
39
|
-
## The templated citation
|
|
40
|
-
|
|
41
|
-
A citation naming a family as a pattern carries neither value. `canon-*` and `internal-<X>` are the two forms this corpus writes, and a literal comparison matches neither, so a rename running the verb once per name reports clean while the family citations stay stale. Two such citations have inverted this way, stating the old prefix as a guard, which left a rule instructing the next session to flag the correct naming as a mismatch.
|
|
42
|
-
|
|
43
|
-
Beside the literal comparison the run matches a family stem, derived from the segment the two values actually differ on, with everything they share carried ahead of it. `toolkit-sync` to `canon-sync` derives `toolkit`, and `canon-cli` to `canon-shell` derives `canon-cli` rather than `canon`, which is what keeps a rename of one folder off every sibling in its family.
|
|
44
|
-
|
|
45
|
-
Three forms report, decided by the character after the stem and its separator:
|
|
46
|
-
|
|
47
|
-
| Form | Shape | What it names |
|
|
48
|
-
| ------------- | ------------ | ---------------------------------- |
|
|
49
|
-
| `glob` | `<stem>-*` | the family written as a pattern |
|
|
50
|
-
| `placeholder` | `<stem>-<X>` | the family with a stand-in segment |
|
|
51
|
-
| `prefix` | `<stem>-` | the family written bare |
|
|
52
|
-
|
|
53
|
-
The stem has to start a name. A letter, a digit, an underscore, or a separator directly before it means the stem sits mid-name, which is what a temp-directory prefix such as `canon-check-toolkit-` looks like, and a path separator or a backtick before it means a citation, which is what `claude/skills/toolkit-*` looks like.
|
|
54
|
-
|
|
55
|
-
A hit under any of the three counts as a finding and moves the exit code. A stale templated citation is as real as a literal one, and reporting it outside the count would recreate the silence being fixed one report shape further along.
|
|
56
|
-
|
|
57
|
-
Every hit in a markdown file carries the nearest heading above it. A line reads differently under the section holding it, and `Use the canon-* prefix on an internal skill` is a prohibition under `## Must not` and an instruction anywhere else. A reviewer reading that line without its heading made exactly that misreading against this tree.
|
|
58
|
-
|
|
59
|
-
## The exemption marker
|
|
60
|
-
|
|
61
|
-
A declaration disagreeing with a convention for a stated reason carries `canon-allow-superseded: <reason>` on its own line or the one directly above. The marker moves the line into the report's `Exempt` section, which is named rather than counted, so a reader weighing the report can reach the reason.
|
|
62
|
-
|
|
63
|
-
Only a marker naming a reason counts. A bare token is a line that meant to say something and did not, and honoring it would let a typo mute a finding. This is the `canon-allow-secret` shape, and both read the same placement rule through one helper.
|
|
64
|
-
|
|
65
|
-
## The blind spots
|
|
66
|
-
|
|
67
|
-
The sweep sees the value, its stem, and nothing else. A prose reference that went stale without carrying either is invisible here, which is not hypothetical: alongside the mechanical instances, one declaration had gone stale by citing the wrong standard for the transform, and it matches no string this sweep could have been given.
|
|
68
|
-
|
|
69
|
-
The second is a family written in a form the three shapes do not read: a bracket style other than `<>`, a glob with no separator ahead of it, or the family described in words. Adding a form is a change to the classifier rather than a name a caller can pass, since a list of names cannot reach this class by construction, which is the whole finding behind the stem.
|
|
70
|
-
|
|
71
|
-
A value sweep closes most of the first class and no part of either one. The help text and the frame name both on every run, so a clean report is not read as a clean tree.
|
|
72
|
-
|
|
73
|
-
## The false-positive rate
|
|
74
|
-
|
|
75
|
-
Over-reporting is the design here as it is for the literal half, and the corpus bounds it. Sweeping the retired `toolkit-` prefix over this tree named one templated hit outside the change's own fixtures, a passage recording the retired `toolkit-` prefix as history, which reads correctly in place.
|
|
76
|
-
|
|
77
|
-
A version reporting a page of hits teaches the next rename to skip the verb, which is worse than the blind spot being fixed. Read a report against that rate rather than against zero.
|
|
78
|
-
|
|
79
|
-
## Exit codes
|
|
80
|
-
|
|
81
|
-
Exit codes are `0` when nothing asserts the superseded value or its family, `1` for a refusal, and `2` for at least one finding of either kind. It refuses an empty superseded value, which would match every line rather than a convention, a superseded value equal to its replacement, which means no convention changed, and a tree git cannot list, since an empty list passes each of its zero files. An empty replacement is not among them, because retiring a value is an ordinary reason to run this.
|
|
82
|
-
|
|
83
|
-
Nothing wires this into `bun run check` or into a hook. A value sweep over-reports by construction, and gating a measure carrying a known false-positive class is what teaches contributors to route around the stage. `canon gov test-order` is the sibling precedent.
|
|
84
|
-
|
|
85
|
-
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the JSON record's `findings` array rather than the exit when a skill consumes this.
|
|
@@ -1,83 +0,0 @@
|
|
|
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`, `pending-branch`, 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. `pending-branch` fires when `--pull-request` meets a `Pending branch:` line, since a branch that ticked an outcome before `git-pr` recorded its number means the last number listed is not yet the last slice. The `detail` array names each pending branch. Selecting by stem skips both checks.
|
|
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
|
-
Every other task file linking to the moved task is repointed at its new path too. The pass reads each markdown file in `.canon/tasks/`, `archive/`, and `declined/` except the moved file, `index.md`, `priority.md`, `backlog.md`, and the session handoffs, and resolves each relative link against the citing file's own folder. Only a link resolving to the old path is rewritten, with its `#` anchor kept, so an `archive/<stem>.md` link already correct, or the same stem in another folder, stays as written. Inline links and reference-style definitions (`[label]: path`) are both rewritten. Fenced blocks and inline code spans are skipped. `priority.md` and `backlog.md` are left alone because `canon tasks validate` reads a blocker there only while its target carries no `/`. Links broken before the move stay broken, and citations from plans, groundwork, and intake records are not read. The `relinked` array on the success record carries a root-relative `file` and a `links` count per file written, and is empty when nothing cited the task. The pass runs after the move, so a file it cannot read or write never stops the ordering-row removal or the index regen that follow. Each such file is left as it was and reported in `relinkFailed` as a root-relative `file` and a `message`, with a warning on stderr, and the verb still exits `0` because the move itself landed. A caller reading the record should treat a non-empty `relinkFailed` as links still pointing at the old path. `canon hooks post-merge`, which `.husky/post-merge` calls, reads the field and prints one `Links not repointed in:` line naming the files, since it discards the archive's stderr and nothing else on that unattended path would show the failure.
|
|
34
|
-
|
|
35
|
-
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.
|
|
36
|
-
|
|
37
|
-
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.
|
|
38
|
-
|
|
39
|
-
The merge is what settles a plan. `canon hooks post-merge`, which `.husky/post-merge` calls, 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.
|
|
40
|
-
|
|
41
|
-
`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.
|
|
42
|
-
|
|
43
|
-
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.
|
|
44
|
-
|
|
45
|
-
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.
|
|
46
|
-
|
|
47
|
-
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.
|
|
48
|
-
|
|
49
|
-
Skills branch on the reason rather than on the exit code:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
canon tasks archive --pull-request 673 --json | jq -r 'if .ok then .task else .reason end'
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Decline
|
|
56
|
-
|
|
57
|
-
`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.
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
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
|
|
61
|
-
canon tasks decline v28.1-trigger-escalation --reason "no longer needed" --by Alex --json
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
| Option | Behavior |
|
|
65
|
-
| ----------------- | ------------------------------------------------- |
|
|
66
|
-
| `--reason <text>` | Why the task was decided against, required |
|
|
67
|
-
| `--by <name>` | Who decided, defaulting to `git config user.name` |
|
|
68
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
69
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
70
|
-
|
|
71
|
-
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.
|
|
72
|
-
|
|
73
|
-
`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.
|
|
74
|
-
|
|
75
|
-
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.
|
|
76
|
-
|
|
77
|
-
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. Inbound links to the declined task are repointed at `declined/<stem>.md` and reported in `relinked`, as the archive section describes. 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.
|
|
78
|
-
|
|
79
|
-
Skills branch on the reason rather than on the exit code, the same rule `canon tasks archive` states:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
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
|
|
83
|
-
```
|
|
@@ -1,152 +0,0 @@
|
|
|
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`, since the row waits on a split into one file per batch rather than on the operator. A plan stacked on a live sibling it mostly shares files with reads the same way under `Stacked sibling`, and waits on a fold or on a ``Judged apart from `feature-<parent>`: <reason>`` constraint.
|
|
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 runs its conflict check, 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 `claude/skills/git-branch/references/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
|
-
| `--add` | Append the plan to the line instead of replacing the link |
|
|
141
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
142
|
-
| `--root <path>` | Board root, defaulting to the main worktree |
|
|
143
|
-
|
|
144
|
-
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.
|
|
145
|
-
|
|
146
|
-
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`, `appended`, `corrected`, or `unchanged`, which makes a rerun against the same plan safe, and `replaced` names the target the line carried before when it differed.
|
|
147
|
-
|
|
148
|
-
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.
|
|
149
|
-
|
|
150
|
-
A sliced task ships one plan per slice, so `--add` appends the plan to the line rather than replacing the link. It keeps every link in its order, writes a bare-path target as a link so it stays readable, leaves trailing prose and the line ending where they were, adds the line when the task carries none, reports `unchanged` and writes nothing when the plan is already listed, and is allowed on a line already holding several. Without the flag the replace behavior and the `several-plans` refusal above hold.
|
|
151
|
-
|
|
152
|
-
Exit codes: `0` recorded, `1` refused. The `reason` field carries `no-board`, `no-match`, `no-plan`, `several-plans`, or `bad-input`.
|