@erclx/canon 4.38.0 → 4.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-design-extract/SKILL.md +1 -1
- package/claude/skills/claude-pr-review/SKILL.md +1 -1
- package/claude/skills/claude-review/SKILL.md +1 -0
- package/claude/skills/claude-tasks/SKILL.md +1 -1
- package/claude/skills/setup-indexes/SKILL.md +2 -5
- package/docs/agents/tasks.md +7 -7
- package/package.json +1 -1
- package/scripts/docs/list.sh +1 -1
- package/src/gate/measures.ts +26 -8
- package/src/shipped/references.ts +95 -5
- package/standards/tasks.md +6 -6
- package/standards/versioning.md +1 -1
- package/tooling/base/reference.md +2 -2
|
@@ -35,7 +35,7 @@ Read these on both paths, skipping any that do not exist:
|
|
|
35
35
|
- `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: word, punctuation, and formatting constraints
|
|
36
36
|
- The `write-human` skill: tone and sentence construction constraints
|
|
37
37
|
|
|
38
|
-
On the source path, also read the UI surfaces matched in Step 1 plus
|
|
38
|
+
On the source path, also read the UI surfaces matched in Step 1 plus `canon docs output-shape` and `canon docs index`, for output shape or framing rules already documented in the toolkit's own reference. Skip either that fails to resolve, since a project keeping its framing rules elsewhere is read there instead.
|
|
39
39
|
|
|
40
40
|
On the greenfield path, also read `.claude/ARCHITECTURE.md` for platform, tech stack, and surface type. Do not scan `src/`, stylesheets, or UI modules. Step 1 already established they hold nothing.
|
|
41
41
|
|
|
@@ -105,7 +105,7 @@ Read each changed file in scope. Skip deleted files. Run reads in parallel.
|
|
|
105
105
|
|
|
106
106
|
## Step 3: review
|
|
107
107
|
|
|
108
|
-
Review the diff and files for the same axes as `claude-review` (bugs, edge cases, error handling, logic flaws, security, rule violations), then add the three lenses a self-review structurally cannot apply:
|
|
108
|
+
Review the diff and files for the same axes as `claude-review` (bugs, edge cases, error handling, logic flaws, security, rule violations, checkout assumptions in a shipped file), then add the three lenses a self-review structurally cannot apply:
|
|
109
109
|
|
|
110
110
|
- Integration: does this fit the board's order, the shared wiring seam, and any sibling PR in flight?
|
|
111
111
|
- Contract: does a contract downstream features depend on land correctly, and should the plan itself be questioned?
|
|
@@ -66,6 +66,7 @@ Review the full diff and changed file contents for:
|
|
|
66
66
|
3. Logic flaws that will cause problems when the code is extended
|
|
67
67
|
4. Security issues relevant to the project context
|
|
68
68
|
5. Violations of rules from `.claude/rules/` that apply to the changed files
|
|
69
|
+
6. When the diff touches a file this project ships to a target holding none of its own history: a repository-relative path, a phase label naming a gitignored board, a same-repository pull request or commit reference, or a layout, stack, or config-path claim true of this checkout but stated as if it were general
|
|
69
70
|
|
|
70
71
|
Use `CLAUDE.md`, `.claude/REQUIREMENTS.md`, `.claude/ARCHITECTURE.md`, and the auto-loaded `.claude/rules/` as project context to inform what is intentional vs problematic. Do not fix, rewrite, or suggest refactors outside the scope of a finding.
|
|
71
72
|
|
|
@@ -31,7 +31,7 @@ Accept work whose origin is the conversation itself only when the user says so e
|
|
|
31
31
|
|
|
32
32
|
List the existing filenames in `.canon/tasks/` and read `index.md` for what each holds. Propose the next label from what is already on the board, and say which neighbors it sits between.
|
|
33
33
|
|
|
34
|
-
Do not derive the label from a version file. `${CLAUDE_SKILL_DIR}/../../standards/versioning.md` permits free renumbering, so the board is the only surface that knows what a label currently means. Pad the phase to two digits in the filename, since index entries sort by filename alone and a bare `v9.0` sorts after `v15.0`.
|
|
34
|
+
Do not derive the label from a version file. `${CLAUDE_SKILL_DIR}/../../standards/versioning.md` permits free renumbering, so the board is the only surface that knows what a label currently means. Pad the phase to two digits in the filename, since index entries sort by filename alone and a bare `v9.0` sorts after `v15.0`. <!-- canon-allow-reference: illustrates the padding rule's own sort collision, not a citation of a real task -->
|
|
35
35
|
|
|
36
36
|
### Step 3: write the file
|
|
37
37
|
|
|
@@ -7,7 +7,7 @@ description: Bootstraps the toolkit's `index.md` system in a target project. Sca
|
|
|
7
7
|
|
|
8
8
|
Adds the index.md system to a project that does not have it. Operates in the active Claude session: Claude reads files, drafts frontmatter, confirms with the user, and writes. The CLI handles validation and regeneration only.
|
|
9
9
|
|
|
10
|
-
Read
|
|
10
|
+
Read `canon docs indexes` from the toolkit if context on the system is needed before scanning.
|
|
11
11
|
|
|
12
12
|
## Scope
|
|
13
13
|
|
|
@@ -147,7 +147,4 @@ Replace bracketed values with the values from this run. Drop the "Folders bootst
|
|
|
147
147
|
|
|
148
148
|
## Reference
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- The toolkit's `.claude/context/indexes.md`: system rationale, frontmatter contract, when to adopt
|
|
153
|
-
- The toolkit's `docs/agents/indexes.md`: `canon indexes regen` flags, exit codes, JSON shape
|
|
150
|
+
- Run `canon docs indexes` for `canon indexes regen` flags, exit codes, and JSON shape. It resolves from the toolkit rather than from the target's own tree.
|
package/docs/agents/tasks.md
CHANGED
|
@@ -12,7 +12,7 @@ description: Selecting a shipped task by stem or pull request, recording a numbe
|
|
|
12
12
|
Name the task by its filename stem, or by the pull request it carries:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
canon tasks archive v28.1-trigger-escalation
|
|
15
|
+
canon tasks archive v28.1-trigger-escalation # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
16
16
|
canon tasks archive --pull-request 673 --json
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -66,7 +66,7 @@ Branch on `reason` rather than on the exit code, which is the rule the archive s
|
|
|
66
66
|
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.
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
canon tasks plan-citations v28.1-trigger-escalation --json | jq -r '.location'
|
|
69
|
+
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
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## Plan answers
|
|
@@ -111,7 +111,7 @@ canon tasks plan-answers dispatch-answer-gate --json | jq -r '.launchable'
|
|
|
111
111
|
Name the task by its filename stem, or by the plan its `Plan:` line points at:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
canon tasks pull-request 673 v28.1-trigger-escalation
|
|
114
|
+
canon tasks pull-request 673 v28.1-trigger-escalation # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
115
115
|
canon tasks pull-request 673 --plan worktree-scratch-routing --json
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -132,7 +132,7 @@ A malformed argument refuses as `bad-input` instead, which sits outside that set
|
|
|
132
132
|
`canon tasks outcome` marks outcomes `[x]` on a task by their position in its outcome list, counting every checkbox in file order from 1.
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
|
-
canon tasks outcome v28.1-trigger-escalation --close 1 --close 3
|
|
135
|
+
canon tasks outcome v28.1-trigger-escalation --close 1 --close 3 # canon-allow-reference: illustrates the stem-selection form, not a citation of a real task
|
|
136
136
|
canon tasks outcome --plan worktree-scratch-routing --close 2 --json
|
|
137
137
|
```
|
|
138
138
|
|
|
@@ -188,7 +188,7 @@ The comparative phrase is bounded the same way and for the same reason. A closed
|
|
|
188
188
|
|
|
189
189
|
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.
|
|
190
190
|
|
|
191
|
-
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.
|
|
191
|
+
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 -->
|
|
192
192
|
|
|
193
193
|
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.
|
|
194
194
|
|
|
@@ -207,7 +207,7 @@ The other three kinds rest on a person's judgment, so a row neither half reached
|
|
|
207
207
|
"untested": [
|
|
208
208
|
{
|
|
209
209
|
"group": "Needs a plan",
|
|
210
|
-
"subject": "v50.6-a-standard-no-skill-reads",
|
|
210
|
+
"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
|
|
211
211
|
"message": "..."
|
|
212
212
|
}
|
|
213
213
|
]
|
|
@@ -223,7 +223,7 @@ A `## Run now` row whose Touches column names a bare folder lands in a third arr
|
|
|
223
223
|
"claims": [
|
|
224
224
|
{
|
|
225
225
|
"group": "Run now",
|
|
226
|
-
"subject": "v1.0-first",
|
|
226
|
+
"subject": "v1.0-first", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
227
227
|
"message": "claims the whole src/tasks folder, so it collides with every row written under it."
|
|
228
228
|
}
|
|
229
229
|
]
|
package/package.json
CHANGED
package/scripts/docs/list.sh
CHANGED
|
@@ -88,7 +88,7 @@ collect_docs() {
|
|
|
88
88
|
# It stops at the folder where listTopics goes on to name each sub-area file.
|
|
89
89
|
# This is the downstream catalog and that one answers what a caller could have
|
|
90
90
|
# typed, so a reachable name absent here is the same divergence the internal
|
|
91
|
-
# topics already carry, and docs
|
|
91
|
+
# topics already carry, and `canon docs docs` states it.
|
|
92
92
|
collect_context() {
|
|
93
93
|
local file name description
|
|
94
94
|
{
|
package/src/gate/measures.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
REFERENCE_MARKER,
|
|
14
14
|
referencesIn,
|
|
15
15
|
SHIPPED_CORPORA,
|
|
16
|
+
type ShippedReference,
|
|
16
17
|
} from '@/shipped/references'
|
|
17
18
|
|
|
18
19
|
export interface CommandResult {
|
|
@@ -482,9 +483,28 @@ function shippedCorpusFiles(root: string): string[] {
|
|
|
482
483
|
return files.sort()
|
|
483
484
|
}
|
|
484
485
|
|
|
486
|
+
/** The clause `shippedReferences` appends after naming a reference's text. */
|
|
487
|
+
function describeShippedReference(reference: ShippedReference): string {
|
|
488
|
+
if (reference.selfCitation) {
|
|
489
|
+
return "a citation of this repository's own history that resolves for no reader outside it, qualified or not"
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
switch (reference.kind) {
|
|
493
|
+
case 'commit':
|
|
494
|
+
return 'a commit sha that resolves nowhere for a reader in a target'
|
|
495
|
+
case 'pull-request':
|
|
496
|
+
return 'a pull request number that resolves elsewhere for a reader in a target'
|
|
497
|
+
case 'docs-path':
|
|
498
|
+
return 'a path into this repository that a registry install never carries'
|
|
499
|
+
case 'phase-label':
|
|
500
|
+
return 'a phase label that names a board no target holds'
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
485
504
|
/**
|
|
486
|
-
* A pull request number
|
|
487
|
-
* resolve, over the seven corpora
|
|
505
|
+
* A pull request number, a commit sha, a docs-path citation, or a phase
|
|
506
|
+
* label that a reader in a target cannot resolve, over the seven corpora
|
|
507
|
+
* that reach one.
|
|
488
508
|
*
|
|
489
509
|
* Every instance on the trunk was written by a branch that passed review, this
|
|
490
510
|
* row's own planning session included, so the only instrument before this stage
|
|
@@ -513,7 +533,7 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
513
533
|
}
|
|
514
534
|
|
|
515
535
|
const found = files.flatMap((file) =>
|
|
516
|
-
referencesIn(file, readFileSync(join(ctx.root, file), 'utf8')),
|
|
536
|
+
referencesIn(file, readFileSync(join(ctx.root, file), 'utf8'), ctx.root),
|
|
517
537
|
)
|
|
518
538
|
|
|
519
539
|
if (found.length === 0) {
|
|
@@ -529,15 +549,13 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
529
549
|
return {
|
|
530
550
|
emissions: found.map((reference) =>
|
|
531
551
|
warn(
|
|
532
|
-
reference.
|
|
533
|
-
? `${reference.file}:${reference.line} carries ${reference.text}, a citation of this repository's own history that resolves for no reader outside it, qualified or not`
|
|
534
|
-
: `${reference.file}:${reference.line} carries ${reference.text}, a ${reference.kind === 'commit' ? 'commit sha that resolves nowhere' : 'pull request number that resolves elsewhere'} for a reader in a target`,
|
|
552
|
+
`${reference.file}:${reference.line} carries ${reference.text}, ${describeShippedReference(reference)}`,
|
|
535
553
|
),
|
|
536
554
|
),
|
|
537
555
|
failure:
|
|
538
556
|
found.length === 1
|
|
539
|
-
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, state a same-repository citation as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
540
|
-
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, state a same-repository citation as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
557
|
+
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
558
|
+
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
541
559
|
}
|
|
542
560
|
}
|
|
543
561
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
1
3
|
import { isMarked } from '@/exempt-marker'
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -125,11 +127,53 @@ const COMMIT_SHA = /(?<![0-9A-Za-z_@/#])([0-9a-f]{7,40})(?![0-9A-Za-z_])/g
|
|
|
125
127
|
const SAME_REPOSITORY =
|
|
126
128
|
/(?<![0-9A-Za-z_/])erclx\/canon(#[0-9]+|@[0-9a-f]{7,40})(?![0-9A-Za-z_])/g
|
|
127
129
|
|
|
130
|
+
/**
|
|
131
|
+
* A repository-relative path under `docs/`, reported only where `isResolvable`
|
|
132
|
+
* confirms it resolves against this checkout.
|
|
133
|
+
*
|
|
134
|
+
* Shape alone cannot separate a citation of this repository's own reference
|
|
135
|
+
* corpus from an illustration naming a target's own tree, since both are
|
|
136
|
+
* `docs/...md` tokens: `docs/agents/tasks.md` and `docs/retry.md` read
|
|
137
|
+
* identically to a pattern with no filesystem behind it. Of 124 such tokens
|
|
138
|
+
* across the shipped corpora, four distinct paths resolved here and the rest
|
|
139
|
+
* named a target's own tree, measured on 2026-09-03. Resolving against `root`
|
|
140
|
+
* is what turns that 124-hit pattern into the four-hit gate.
|
|
141
|
+
*
|
|
142
|
+
* `.claude/context/` carries no equivalent pattern. The same resolution test
|
|
143
|
+
* cannot separate a path every scaffolded project holds, such as
|
|
144
|
+
* `.claude/context/index.md`, from this repository's own domain entry, such
|
|
145
|
+
* as `.claude/context/indexes.md`, since both resolve here and only the
|
|
146
|
+
* second is a defect. That is a semantic read no pattern makes, so it stays
|
|
147
|
+
* with the rule and the review checklist rather than joining this reader.
|
|
148
|
+
*/
|
|
149
|
+
const DOCS_PATH = /(?<![\w./-])docs\/[^\s`)\]]*\.md\b/g
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A phase-label-shaped token: exactly two numeric groups, with a negative
|
|
153
|
+
* lookahead rejecting a third.
|
|
154
|
+
*
|
|
155
|
+
* This does not reuse `VERSION_TOKEN` from `src/labels/phase.ts`, which
|
|
156
|
+
* admits one or two decimal groups and therefore matches a three-group
|
|
157
|
+
* semver tag as readily as a two-group phase label. That is the right shape
|
|
158
|
+
* there, since `scanPhaseLabels` sorts the two namespaces apart by asking
|
|
159
|
+
* whether the pull request carrying the token is release-please's own, a
|
|
160
|
+
* signal this reader has no equivalent of: a shipped-corpus file carries no
|
|
161
|
+
* pull request to test. Shape is the only discriminator available here, and
|
|
162
|
+
* every phase label this board issues carries exactly two groups while every
|
|
163
|
+
* semver tag carries three, so narrowing to two removes the semver class by
|
|
164
|
+
* construction rather than by a marker standing in for the missing signal.
|
|
165
|
+
*
|
|
166
|
+
* A target holds no board to resolve a label against, so a bare instance
|
|
167
|
+
* here is unresolvable the same way a same-repository citation is, and is
|
|
168
|
+
* muted the same way when it names the label format rather than a real row.
|
|
169
|
+
*/
|
|
170
|
+
const PHASE_LABEL = /\bv\d+\.\d+(?!\.\d)\b/g
|
|
171
|
+
|
|
128
172
|
export interface ShippedReference {
|
|
129
173
|
readonly file: string
|
|
130
174
|
/** One-based, matching the `file:line` form a reader clicks. */
|
|
131
175
|
readonly line: number
|
|
132
|
-
readonly kind: 'pull-request' | 'commit'
|
|
176
|
+
readonly kind: 'pull-request' | 'commit' | 'docs-path' | 'phase-label'
|
|
133
177
|
/** The reference as written, so a report names the token to qualify. */
|
|
134
178
|
readonly text: string
|
|
135
179
|
/**
|
|
@@ -140,13 +184,36 @@ export interface ShippedReference {
|
|
|
140
184
|
readonly selfCitation?: true
|
|
141
185
|
}
|
|
142
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Whether a `DOCS_PATH` match names a real file in this checkout.
|
|
189
|
+
*
|
|
190
|
+
* `file` gates the `docs/` corpus out by the caller's own location rather
|
|
191
|
+
* than by a pattern exemption: a citation from inside `docs/` is read
|
|
192
|
+
* together with the rest of that corpus through the same `canon docs`
|
|
193
|
+
* resolution, which is a weaker claim than one from a skill body with no
|
|
194
|
+
* `docs/` sibling at all.
|
|
195
|
+
*/
|
|
196
|
+
function isDocsPathResolvable(
|
|
197
|
+
file: string,
|
|
198
|
+
path: string,
|
|
199
|
+
root: string,
|
|
200
|
+
): boolean {
|
|
201
|
+
if (file.startsWith('docs/')) return false
|
|
202
|
+
return existsSync(join(root, path))
|
|
203
|
+
}
|
|
204
|
+
|
|
143
205
|
/**
|
|
144
206
|
* Every reference in one shipped file that no marker mutes.
|
|
145
207
|
*
|
|
146
|
-
* The corpus walk is deliberately absent, which lets the shape be
|
|
147
|
-
* against a string rather than against a fixture. That is the seam
|
|
208
|
+
* The corpus walk is deliberately absent, which lets most of the shape be
|
|
209
|
+
* tested against a string rather than against a fixture. That is the seam
|
|
148
210
|
* `headingCitationsIn` draws in `src/claude/skills-headings.ts` and `citationsIn`
|
|
149
|
-
* draws in `skills-reach.ts`.
|
|
211
|
+
* draws in `skills-reach.ts`. `DOCS_PATH` is the one pattern that still needs
|
|
212
|
+
* a filesystem, since resolving against this checkout is the only thing that
|
|
213
|
+
* separates its two readings, so it takes `root` as the one caller-supplied
|
|
214
|
+
* exception to that rule. `root` is required rather than defaulted, since a
|
|
215
|
+
* caller that dropped it silently would report zero docs-path findings
|
|
216
|
+
* rather than raising, which is the wrong failure direction for a gate.
|
|
150
217
|
*
|
|
151
218
|
* The unit is the match rather than the line, unlike those two, because one
|
|
152
219
|
* line here can carry three separate tokens each needing its own repair and a
|
|
@@ -156,7 +223,11 @@ export interface ShippedReference {
|
|
|
156
223
|
* marker mutes a line and nothing narrower. A real citation later added beside
|
|
157
224
|
* a marked illustration ships unreported.
|
|
158
225
|
*/
|
|
159
|
-
export function referencesIn(
|
|
226
|
+
export function referencesIn(
|
|
227
|
+
file: string,
|
|
228
|
+
text: string,
|
|
229
|
+
root: string,
|
|
230
|
+
): ShippedReference[] {
|
|
160
231
|
const lines = text.split('\n')
|
|
161
232
|
const references: ShippedReference[] = []
|
|
162
233
|
|
|
@@ -190,6 +261,25 @@ export function referencesIn(file: string, text: string): ShippedReference[] {
|
|
|
190
261
|
selfCitation: true,
|
|
191
262
|
})
|
|
192
263
|
}
|
|
264
|
+
|
|
265
|
+
for (const match of line.matchAll(DOCS_PATH)) {
|
|
266
|
+
if (!isDocsPathResolvable(file, match[0], root)) continue
|
|
267
|
+
references.push({
|
|
268
|
+
file,
|
|
269
|
+
line: index + 1,
|
|
270
|
+
kind: 'docs-path',
|
|
271
|
+
text: match[0],
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const match of line.matchAll(PHASE_LABEL)) {
|
|
276
|
+
references.push({
|
|
277
|
+
file,
|
|
278
|
+
line: index + 1,
|
|
279
|
+
kind: 'phase-label',
|
|
280
|
+
text: match[0],
|
|
281
|
+
})
|
|
282
|
+
}
|
|
193
283
|
}
|
|
194
284
|
|
|
195
285
|
return references
|
package/standards/tasks.md
CHANGED
|
@@ -31,8 +31,8 @@ Does not govern:
|
|
|
31
31
|
├── priority.md ← hand-maintained execution order
|
|
32
32
|
├── backlog.md ← unordered, what is not being scheduled
|
|
33
33
|
├── session-<slug>.md ← optional, what a compaction is about to destroy
|
|
34
|
-
├── v09.0-sync-paths.md
|
|
35
|
-
└── v13.0-toolkit-drift.md
|
|
34
|
+
├── v09.0-sync-paths.md # canon-allow-reference: illustrates the vXX.Y-slug filename this section defines
|
|
35
|
+
└── v13.0-toolkit-drift.md # canon-allow-reference: illustrates the vXX.Y-slug filename this section defines
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
One file per task is what keeps the board safe under parallel sessions. Two sessions working different tasks never write the same file, which matters because a gitignored board has no history to recover a clobbered write from.
|
|
@@ -137,13 +137,13 @@ Add no fourth readiness group in place of this file. The three group names are t
|
|
|
137
137
|
|
|
138
138
|
## Validation
|
|
139
139
|
|
|
140
|
-
`canon tasks validate` reads the board against the tree, and what it checks, what it refuses on, and what it reports are at `docs
|
|
140
|
+
`canon tasks validate` reads the board against the tree, and what it checks, what it refuses on, and what it reports are at `canon docs tasks`. Run it when the readiness claim is made rather than on a schedule, since the board is gitignored per-machine scratch and no shared moment exists to hang it on.
|
|
141
141
|
|
|
142
142
|
## Filenames
|
|
143
143
|
|
|
144
144
|
`vXX.Y-<slug>.md`, where the version is the phase label zero-padded to two digits and the slug is kebab-case.
|
|
145
145
|
|
|
146
|
-
Padding is load-bearing. Index entries sort by filename and nothing else, so a bare `v9.0` sorts after `v15.0` and the catalog reads out of board order. `standards/versioning.md` governs the label itself and permits free renumbering, so expect the occasional rename. Nothing points at a task filename, since a `Plan:` line runs from task to plan rather than the reverse.
|
|
146
|
+
Padding is load-bearing. Index entries sort by filename and nothing else, so a bare `v9.0` sorts after `v15.0` and the catalog reads out of board order. `standards/versioning.md` governs the label itself and permits free renumbering, so expect the occasional rename. Nothing points at a task filename, since a `Plan:` line runs from task to plan rather than the reverse. <!-- canon-allow-reference: illustrates the padding rule's own sort collision, not a citation of a real task -->
|
|
147
147
|
|
|
148
148
|
## Frontmatter
|
|
149
149
|
|
|
@@ -151,7 +151,7 @@ Every task file carries both fields. The index walker fails the whole folder whe
|
|
|
151
151
|
|
|
152
152
|
```yaml
|
|
153
153
|
---
|
|
154
|
-
title: 'v13.0: Detect and close toolkit drift in target projects'
|
|
154
|
+
title: 'v13.0: Detect and close toolkit drift in target projects' # canon-allow-reference: illustrates the quoted title shape, not a citation of a real task
|
|
155
155
|
description: Record what a target installed and report the delta against the toolkit
|
|
156
156
|
---
|
|
157
157
|
```
|
|
@@ -245,7 +245,7 @@ The line is what lets a merge close its own task. Every merge on `main` is a squ
|
|
|
245
245
|
|
|
246
246
|
## Archiving
|
|
247
247
|
|
|
248
|
-
Never delete a task file. A shipped task moves to `.canon/tasks/archive/` under its own name, and the live index regenerates without it. `canon tasks archive` owns that move, and what it does and what it refuses on are at `docs
|
|
248
|
+
Never delete a task file. A shipped task moves to `.canon/tasks/archive/` under its own name, and the live index regenerates without it. `canon tasks archive` owns that move, and what it does and what it refuses on are at `canon docs tasks`.
|
|
249
249
|
|
|
250
250
|
The archive nests inside `.canon/tasks/` rather than sitting beside it as a flat `.claude/task-archive/`. Nesting is what lets a reader tell the two shapes apart on sight: the flat sibling is what a binary predating this convention still writes, so meeting one names an older checkout rather than a second archive to reconcile against this one.
|
|
251
251
|
|
package/standards/versioning.md
CHANGED
|
@@ -25,7 +25,7 @@ Internal coordination vocabulary used in the task board and chat.
|
|
|
25
25
|
|
|
26
26
|
- Format is project-specific.
|
|
27
27
|
- Used to order work and disambiguate streams during planning.
|
|
28
|
-
- Re-numbers freely as scope shifts. Inserting a half-step between two existing labels (a `v1.5` between `v1` and `v2`) is fine.
|
|
28
|
+
- Re-numbers freely as scope shifts. Inserting a half-step between two existing labels (a `v1.5` between `v1` and `v2`) is fine. <!-- canon-allow-reference: illustrates the renumbering rule's own format, not a citation of a real row -->
|
|
29
29
|
- Does not have to map to any external release.
|
|
30
30
|
|
|
31
31
|
## Semver tags
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, spelling, shell linting, conventional commits, git hooks, CI, and one maintenance script. Every other stack extends it, so a decision made here is one every stack inherits.
|
|
6
6
|
|
|
7
|
-
A repository with several language roots layers this stack once at the root and skips it per subtree, since git honors only one `core.hooksPath` and a re-dropped husky silently breaks. `docs
|
|
7
|
+
A repository with several language roots layers this stack once at the root and skips it per subtree, since git honors only one `core.hooksPath` and a re-dropped husky silently breaks. `canon docs target-projects` carries the recipe.
|
|
8
8
|
|
|
9
9
|
## What ships as golden configs
|
|
10
10
|
|
|
@@ -97,4 +97,4 @@ Sticky negative knowledge. Do not relearn.
|
|
|
97
97
|
|
|
98
98
|
`bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatters could not fix. The pull request workflow calls `check:format`, `check:spell`, and `check:shell` directly, and those three are the gate.
|
|
99
99
|
|
|
100
|
-
`scripts/verify.sh`'s `Markdown bans` stage is the exception: it gates rather than repairs. It runs `canon markdown audit` over every tracked markdown file except `CHANGELOG.md`, since a generated changelog carries commit subjects nobody wrote against the ban set, and exits `2` on a finding or `3` on a shipped-empty ban set, failing `bun run check` and the `pre-push` hook that calls it. A refusal to measure exits `1` and is logged as a skip rather than a failure, as does a corpus with no file left to check once `CHANGELOG.md` is set aside. The stage needs `canon` on PATH. `docs
|
|
100
|
+
`scripts/verify.sh`'s `Markdown bans` stage is the exception: it gates rather than repairs. It runs `canon markdown audit` over every tracked markdown file except `CHANGELOG.md`, since a generated changelog carries commit subjects nobody wrote against the ban set, and exits `2` on a finding or `3` on a shipped-empty ban set, failing `bun run check` and the `pre-push` hook that calls it. A refusal to measure exits `1` and is logged as a skip rather than a failure, as does a corpus with no file left to check once `CHANGELOG.md` is set aside. The stage needs `canon` on PATH. `canon docs target-projects` names the install command. Without it, the stage is skipped and logged, and coverage falls back to the three checks above.
|