@erclx/canon 4.67.0 → 4.69.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-autoship → auto-ship}/REQUIREMENT.md +3 -3
- package/claude/skills/{claude-autoship → auto-ship}/SKILL.md +36 -36
- package/claude/skills/canon-cli/REQUIREMENT.md +2 -2
- package/claude/skills/canon-cli/SKILL.md +2 -2
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +1 -1
- package/claude/skills/canon-feedback-triage/SKILL.md +3 -3
- package/claude/skills/canon-operator/REQUIREMENT.md +1 -1
- package/claude/skills/canon-operator/SKILL.md +4 -4
- package/claude/skills/canon-rollout/REQUIREMENT.md +6 -6
- package/claude/skills/canon-rollout/SKILL.md +7 -7
- package/claude/skills/{claude-design-extract → design-extract}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-design-extract → design-extract}/SKILL.md +1 -1
- package/claude/skills/{claude-docs → docs-fold}/REQUIREMENT.md +3 -3
- package/claude/skills/{claude-docs → docs-fold}/SKILL.md +14 -14
- package/claude/skills/{claude-docs → docs-fold}/references/anchor-sweep.md +1 -1
- package/claude/skills/{claude-docs → docs-fold}/references/wireframe-sweep.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +3 -3
- package/claude/skills/docs-sync/SKILL.md +1 -1
- package/claude/skills/draft-and-pick/REQUIREMENT.md +4 -4
- package/claude/skills/draft-and-pick/SKILL.md +6 -6
- package/claude/skills/draft-context/REQUIREMENT.md +2 -2
- package/claude/skills/draft-context/SKILL.md +3 -3
- package/claude/skills/{claude-diagram → draft-diagram}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-diagram → draft-diagram}/SKILL.md +3 -3
- package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
- package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
- package/claude/skills/draft-wireframes/SKILL.md +2 -2
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- package/claude/skills/git-pr/SKILL.md +3 -3
- package/claude/skills/git-ship/REQUIREMENT.md +2 -2
- package/claude/skills/git-ship/SKILL.md +8 -8
- package/claude/skills/git-worktree/REQUIREMENT.md +2 -2
- package/claude/skills/git-worktree/SKILL.md +3 -3
- package/claude/skills/identity/REQUIREMENT.md +2 -2
- package/claude/skills/identity/SKILL.md +3 -3
- package/claude/skills/{claude-markdown-propose → markdown-propose}/REQUIREMENT.md +8 -8
- package/claude/skills/{claude-markdown-propose → markdown-propose}/SKILL.md +5 -5
- package/claude/skills/{claude-markdown-propose → markdown-propose}/references/format.md +1 -1
- package/claude/skills/{claude-memory-capture → memory-capture}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-memory-capture → memory-capture}/SKILL.md +13 -13
- package/claude/skills/{claude-memory-review → memory-review}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-memory-review → memory-review}/SKILL.md +10 -10
- package/claude/skills/migration-context/SKILL.md +1 -1
- package/claude/skills/migration-standards-drop/REQUIREMENT.md +1 -1
- package/claude/skills/migration-superseded/REQUIREMENT.md +1 -1
- package/claude/skills/{claude-feature → plan-feature}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-feature → plan-feature}/SKILL.md +4 -4
- package/claude/skills/{claude-groundwork → plan-groundwork}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-groundwork → plan-groundwork}/SKILL.md +8 -8
- package/claude/skills/{claude-intake → plan-intake}/REQUIREMENT.md +6 -6
- package/claude/skills/{claude-intake → plan-intake}/SKILL.md +10 -10
- package/claude/skills/{claude-intake-answer → plan-intake-answer}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-intake-answer → plan-intake-answer}/SKILL.md +5 -5
- package/claude/skills/{claude-address-review → review-address}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-address-review → review-address}/SKILL.md +6 -6
- package/claude/skills/{claude-address-review → review-address}/references/rebase-conflicts.md +1 -1
- package/claude/skills/{claude-review → review-branch}/REQUIREMENT.md +4 -4
- package/claude/skills/{claude-review → review-branch}/SKILL.md +4 -4
- package/claude/skills/{claude-pr-review → review-pr}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-pr-review → review-pr}/SKILL.md +11 -11
- package/claude/skills/{claude-orchestrate → role-orchestrator}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-orchestrate → role-orchestrator}/SKILL.md +20 -20
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-dispatch.md +30 -30
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-handoff.md +2 -2
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-parked.md +8 -8
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-poll.md +8 -8
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-resume.md +1 -1
- package/claude/skills/{claude-orchestrate → role-orchestrator}/references/orchestrator-sweep.md +1 -1
- package/claude/skills/{claude-orchestrate → role-orchestrator}/scripts/poll.sh +6 -6
- package/claude/skills/{claude-planner → role-planner}/REQUIREMENT.md +12 -12
- package/claude/skills/{claude-planner → role-planner}/SKILL.md +4 -4
- package/claude/skills/{claude-worker → role-worker}/REQUIREMENT.md +8 -8
- package/claude/skills/{claude-worker → role-worker}/SKILL.md +6 -6
- package/claude/skills/{claude-seed-sync → seed-sync}/REQUIREMENT.md +2 -2
- package/claude/skills/{claude-seed-sync → seed-sync}/SKILL.md +3 -3
- package/claude/skills/session-map/REQUIREMENT.md +2 -2
- package/claude/skills/session-map/SKILL.md +2 -2
- package/claude/skills/session-resume/REQUIREMENT.md +2 -2
- package/claude/skills/session-resume/SKILL.md +2 -2
- package/claude/skills/{claude-worktree → session-worktree}/REQUIREMENT.md +3 -3
- package/claude/skills/{claude-worktree → session-worktree}/SKILL.md +6 -6
- package/claude/skills/setup-plugins/references/plugin-catalog.md +1 -1
- package/claude/skills/{claude-standards-audit → standards-audit}/REQUIREMENT.md +2 -2
- package/claude/skills/{claude-standards-audit → standards-audit}/SKILL.md +2 -2
- package/claude/skills/systematic-debugging/REQUIREMENT.md +1 -1
- package/claude/skills/{claude-tasks → task-board}/REQUIREMENT.md +3 -3
- package/claude/skills/{claude-tasks → task-board}/SKILL.md +7 -7
- package/claude/skills/{claude-teach → teach-workspace}/REQUIREMENT.md +2 -2
- package/claude/skills/{claude-teach → teach-workspace}/SKILL.md +4 -4
- package/claude/skills/test-first/REQUIREMENT.md +2 -2
- package/claude/skills/{claude-ui-test → ui-test}/REQUIREMENT.md +3 -3
- package/claude/skills/{claude-ui-test → ui-test}/SKILL.md +3 -3
- package/claude/skills/{claude-ux-audit → ux-audit}/REQUIREMENT.md +6 -6
- package/claude/skills/{claude-ux-audit → ux-audit}/SKILL.md +5 -5
- package/claude/skills/{claude-ux-measure → ux-measure}/REQUIREMENT.md +5 -5
- package/claude/skills/{claude-ux-measure → ux-measure}/SKILL.md +5 -5
- package/docs/agents/commands.md +1 -0
- package/docs/agents/install-and-sync.md +1 -1
- package/docs/agents/key-changes.md +3 -3
- package/docs/agents/markdown-audit.md +1 -1
- package/docs/agents/restated.md +1 -1
- package/docs/agents/review-classification.md +1 -1
- package/docs/agents/sandbox.md +13 -10
- package/docs/agents/sessions.md +1 -1
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/targets.md +1 -1
- package/docs/agents/tasks.md +4 -4
- package/docs/agents/teach.md +1 -1
- package/docs/target-projects.md +10 -10
- package/docs/workflow/ai-workflow.md +84 -84
- package/docs/workflow/operating-model.md +17 -17
- package/docs/workflow/visual-design-workflow.md +6 -6
- package/governance/rules/core/045-memory.md +1 -1
- package/governance/rules/core/085-worktrees.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-agent-fixture.sh +1 -1
- package/scripts/core/regen-hero.sh +7 -7
- package/scripts/lib/sandbox-dispatch.sh +8 -0
- package/snippets/claude/decision-memo.md +1 -1
- package/src/autoship/paths.ts +1 -1
- package/src/claude/cases/all.ts +2 -2
- package/src/claude/cases/{claude-workflow.ts → workflow.ts} +33 -33
- package/src/claude/plugin-update.ts +48 -0
- package/src/commands/claude.ts +281 -1
- package/src/commands/feedback.ts +15 -5
- package/src/commands/gate.ts +3 -1
- package/src/commands/sandbox.ts +13 -4
- package/src/commands/sync.ts +3 -3
- package/src/design/components.ts +12 -0
- package/src/design/tokens.ts +1 -1
- package/src/gate/measures.ts +47 -1
- package/src/gov/restated.ts +2 -2
- package/src/markdown/structure.ts +1 -1
- package/src/migrate/rename.ts +4 -3
- package/src/pr/bijection.ts +1 -1
- package/src/pr/paths.ts +2 -2
- package/src/sandbox/expect.ts +26 -1
- package/src/shipped/references.ts +1 -1
- package/src/sync/seeds-report.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +4 -4
- package/src/tasks/record.ts +2 -2
- package/src/tasks/validate.ts +1 -1
- package/src/teach/nav.ts +97 -3
- package/standards/glossary.md +8 -0
- package/standards/groundwork.md +1 -1
- package/standards/snippets.md +1 -1
- package/standards/tasks.md +3 -3
- package/standards/teach.md +2 -2
- package/tooling/base/configs/.husky/post-merge +3 -3
- package/tooling/claude/reference.md +5 -5
- package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +2 -2
- /package/claude/skills/{claude-memory-review → memory-review}/references/receipt-format.md +0 -0
- /package/claude/skills/{claude-orchestrate → role-orchestrator}/scripts/watch.sh +0 -0
- /package/claude/skills/{claude-teach → teach-workspace}/references/lesson-craft.md +0 -0
- /package/claude/skills/{claude-teach → teach-workspace}/references/pedagogy.md +0 -0
- /package/claude/skills/{claude-teach → teach-workspace}/references/promotion.md +0 -0
package/src/commands/sandbox.ts
CHANGED
|
@@ -78,6 +78,7 @@ interface CheckOptions {
|
|
|
78
78
|
readonly writes?: string
|
|
79
79
|
readonly escapes?: string
|
|
80
80
|
readonly escapesWatched?: boolean
|
|
81
|
+
readonly concurrentSessions?: string
|
|
81
82
|
readonly json?: boolean
|
|
82
83
|
readonly strict?: boolean
|
|
83
84
|
}
|
|
@@ -349,7 +350,7 @@ function runCheck(
|
|
|
349
350
|
|
|
350
351
|
const parsed = parseTarget(target)
|
|
351
352
|
if (parsed === undefined) {
|
|
352
|
-
logError('Invalid target. Use <category>:<command>, e.g. claude:docs.')
|
|
353
|
+
logError('Invalid target. Use <category>:<command>, e.g. claude:docs-fold.')
|
|
353
354
|
outro()
|
|
354
355
|
process.exitCode = 1
|
|
355
356
|
return
|
|
@@ -376,6 +377,7 @@ function runCheck(
|
|
|
376
377
|
options.escapes === undefined
|
|
377
378
|
? undefined
|
|
378
379
|
: options.escapesWatched === true,
|
|
380
|
+
concurrentSessions: readPathList(options.concurrentSessions),
|
|
379
381
|
envelope: readEnvelope(options.envelope),
|
|
380
382
|
},
|
|
381
383
|
)
|
|
@@ -414,7 +416,10 @@ export function register(program: Command): void {
|
|
|
414
416
|
sandbox
|
|
415
417
|
.command('check')
|
|
416
418
|
.description('Check a provisioned sandbox against a scenario expectation')
|
|
417
|
-
.argument(
|
|
419
|
+
.argument(
|
|
420
|
+
'<target>',
|
|
421
|
+
'Scenario as <category>:<command>, e.g. claude:docs-fold',
|
|
422
|
+
)
|
|
418
423
|
.argument('[arm]', 'Named scenario arm, e.g. drift')
|
|
419
424
|
.helpOption('-h, --help', 'Show this help message')
|
|
420
425
|
.option('--envelope <file>', 'Run envelope JSON from claude -p')
|
|
@@ -427,6 +432,10 @@ export function register(program: Command): void {
|
|
|
427
432
|
'--escapes-watched',
|
|
428
433
|
'At least one watched root held a target this run, so a zero-escape file is a clean watch rather than one with nothing to watch',
|
|
429
434
|
)
|
|
435
|
+
.option(
|
|
436
|
+
'--concurrent-sessions <file>',
|
|
437
|
+
'Newline-delimited sessions live in the registry both before and after this run, a witness for an unbounded escape',
|
|
438
|
+
)
|
|
430
439
|
.option('--json', 'Emit the verdict as JSON on stdout')
|
|
431
440
|
.option('--strict', 'Exit non-zero when the arm declares no expectation')
|
|
432
441
|
.addHelpText(
|
|
@@ -434,8 +443,8 @@ export function register(program: Command): void {
|
|
|
434
443
|
[
|
|
435
444
|
'',
|
|
436
445
|
'Examples:',
|
|
437
|
-
' canon sandbox check claude:docs drift',
|
|
438
|
-
' canon sandbox check claude:docs drift --envelope run.json --json',
|
|
446
|
+
' canon sandbox check claude:docs-fold drift',
|
|
447
|
+
' canon sandbox check claude:docs-fold drift --envelope run.json --json',
|
|
439
448
|
'',
|
|
440
449
|
'Exit codes: 0 on pass or unchecked, 1 on failure.',
|
|
441
450
|
'With --strict, unchecked exits 1 as well.',
|
package/src/commands/sync.ts
CHANGED
|
@@ -265,7 +265,7 @@ function renderTooling(report: CheckReport): void {
|
|
|
265
265
|
/**
|
|
266
266
|
* Seeds print their own section because no sync command applies them. A `stale`
|
|
267
267
|
* seed is safe to take whole and a `drifted` one holds edits, which is the split
|
|
268
|
-
* `
|
|
268
|
+
* `seed-sync` reads to decide what needs a section-level merge.
|
|
269
269
|
*/
|
|
270
270
|
function renderSeeds(report: CheckReport): void {
|
|
271
271
|
const notable = report.seeds.entries.filter(
|
|
@@ -283,7 +283,7 @@ function renderSeeds(report: CheckReport): void {
|
|
|
283
283
|
logWarn(`${entry.rel} (${entry.state})`)
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
logInfo('Run /canon:
|
|
286
|
+
logInfo('Run /canon:seed-sync to reconcile these section by section.')
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/**
|
|
@@ -399,7 +399,7 @@ async function runSync(target: string): Promise<number> {
|
|
|
399
399
|
|
|
400
400
|
if (existsSync(join(resolved, '.claude'))) {
|
|
401
401
|
process.stderr.write(
|
|
402
|
-
`${GREY}Tip: run \`/
|
|
402
|
+
`${GREY}Tip: run \`/seed-sync\` to audit seed drift per section, preserving local customizations.${NC}\n`,
|
|
403
403
|
)
|
|
404
404
|
}
|
|
405
405
|
|
package/src/design/components.ts
CHANGED
|
@@ -998,6 +998,18 @@ h2 .count {
|
|
|
998
998
|
line-height: 1.55;
|
|
999
999
|
}
|
|
1000
1000
|
|
|
1001
|
+
.gloss-group {
|
|
1002
|
+
margin: 1.1rem 0 0.4rem;
|
|
1003
|
+
font-family: var(--teach-sans);
|
|
1004
|
+
font-size: 0.8125rem;
|
|
1005
|
+
font-weight: 600;
|
|
1006
|
+
color: var(--color-muted);
|
|
1007
|
+
text-transform: uppercase;
|
|
1008
|
+
letter-spacing: 0.02em;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.gloss-group:first-of-type { margin-top: 0; }
|
|
1012
|
+
|
|
1001
1013
|
.gterm b { font-weight: 700; }
|
|
1002
1014
|
/* The markdown source separates a term from its definition with a colon,
|
|
1003
1015
|
and the house standard bans an em dash outright, so the view mirrors
|
package/src/design/tokens.ts
CHANGED
|
@@ -354,7 +354,7 @@ export const TOKENS: DesignTokens = {
|
|
|
354
354
|
'Motion is not used. No transition, animation, or keyframe declaration appears on any rendered surface, and the capture pipeline screenshots a static frame.',
|
|
355
355
|
|
|
356
356
|
iconography:
|
|
357
|
-
"No icon library is installed. `assets/brand/mark.svg` is the one authored icon, embedded inline in the hero topbar, and the surfaces otherwise draw literal glyph characters: `│ ├ ✓ ! ✗ + - ◆ ◇ ❯` for the terminal framing. The same mark also ships as a favicon on every rendered surface, as three independently-maintained copies that track different accents by design rather than by drift: `regen-hero.sh` derives one from the live SVG colored with whatever `--color-accent` (`#e0724b`) the fetched token CSS carries, `src/design/render.ts` carries the path data as a hardcoded literal colored via `colorValue('light-accent')` (`#a4471c`), since a data URI has no CSS context and that page renders on light chrome, and `
|
|
357
|
+
"No icon library is installed. `assets/brand/mark.svg` is the one authored icon, embedded inline in the hero topbar, and the surfaces otherwise draw literal glyph characters: `│ ├ ✓ ! ✗ + - ◆ ◇ ❯` for the terminal framing. The same mark also ships as a favicon on every rendered surface, as three independently-maintained copies that track different accents by design rather than by drift: `regen-hero.sh` derives one from the live SVG colored with whatever `--color-accent` (`#e0724b`) the fetched token CSS carries, `src/design/render.ts` carries the path data as a hardcoded literal colored via `colorValue('light-accent')` (`#a4471c`), since a data URI has no CSS context and that page renders on light chrome, and `teach-workspace`'s `SKILL.md` names one in prose colored `rgb(224,114,75)`, the same value as the dark accent written as decimal rather than hex to clear the shipped-references gate's commit-sha check. Unifying the three or repairing the one that looks drifted would break the fit each was chosen for.",
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
/** A role's value, or `undefined` where the record declares no such role. */
|
package/src/gate/measures.ts
CHANGED
|
@@ -110,6 +110,17 @@ export const GOV_EXPECTED_UNREFERENCED = ['260-shadcn', '320-tanstack-query']
|
|
|
110
110
|
*/
|
|
111
111
|
export const SANDBOX_UNDECLARED_CEILING = 47
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Skills the sandbox skill census reports `asserted`, taken from `canon
|
|
115
|
+
* sandbox coverage --skills` against a clean tree. A dropped pairing lowers
|
|
116
|
+
* this directly, unlike the ceiling above, which a rename can starve without
|
|
117
|
+
* moving: fourteen arms drifted off their skill's name in one branch and the
|
|
118
|
+
* scenario-level ceiling stayed green throughout, since it counts scenarios
|
|
119
|
+
* declaring an expectation rather than skills a scenario reaches. Lowering
|
|
120
|
+
* this floor is a deliberate edit that says which skill lost its arm and why.
|
|
121
|
+
*/
|
|
122
|
+
export const SANDBOX_ASSERTED_FLOOR = 26
|
|
123
|
+
|
|
113
124
|
/**
|
|
114
125
|
* The retained counts the audit stage compares each run against. Spelled here
|
|
115
126
|
* rather than derived, because this stage only ever names the file in a remedy
|
|
@@ -740,10 +751,45 @@ export const sandboxCoverage: Measure = async (ctx) => {
|
|
|
740
751
|
}
|
|
741
752
|
}
|
|
742
753
|
|
|
754
|
+
const scenarioEmission = info(
|
|
755
|
+
`${armed} of ${total} scenarios declare expectations, ${undeclared} undeclared against a ceiling of ${SANDBOX_UNDECLARED_CEILING}`,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
const skillsRun = await ctx.cli(['sandbox', 'coverage', '--skills', '--json'])
|
|
759
|
+
|
|
760
|
+
if (skillsRun.exitCode !== 0) {
|
|
761
|
+
return {
|
|
762
|
+
emissions: [scenarioEmission],
|
|
763
|
+
unmeasured: `The skill census did not report (exit ${skillsRun.exitCode}). It ships in the checkout beside the scenario report, so a run that does not report is a broken command rather than an absent census.`,
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const skillsRecord = parseJson(skillsRun.stdout) as
|
|
768
|
+
| { asserted?: unknown; totalSkills?: unknown }
|
|
769
|
+
| undefined
|
|
770
|
+
const asserted = skillsRecord?.asserted
|
|
771
|
+
const totalSkills = skillsRecord?.totalSkills
|
|
772
|
+
|
|
773
|
+
if (typeof asserted !== 'number' || typeof totalSkills !== 'number') {
|
|
774
|
+
return {
|
|
775
|
+
emissions: [scenarioEmission],
|
|
776
|
+
failure:
|
|
777
|
+
'The skill census carried no asserted total, so the stage measured nothing. Run bun src/cli.ts sandbox coverage --skills --json.',
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (asserted < SANDBOX_ASSERTED_FLOOR) {
|
|
782
|
+
return {
|
|
783
|
+
emissions: [scenarioEmission],
|
|
784
|
+
failure: `${asserted} of ${totalSkills} skills asserted, under the floor of ${SANDBOX_ASSERTED_FLOOR}. A rename or a moved arm likely dropped a skill's pairing; repair the arm's filename against its skill and say which skill lost its arm, or lower SANDBOX_ASSERTED_FLOOR in src/gate/measures.ts.`,
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
743
788
|
return {
|
|
744
789
|
emissions: [
|
|
790
|
+
scenarioEmission,
|
|
745
791
|
info(
|
|
746
|
-
`${
|
|
792
|
+
`${asserted} of ${totalSkills} skills asserted, against a floor of ${SANDBOX_ASSERTED_FLOOR}`,
|
|
747
793
|
),
|
|
748
794
|
],
|
|
749
795
|
}
|
package/src/gov/restated.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const RULES_REL = join('governance', 'rules')
|
|
|
24
24
|
/**
|
|
25
25
|
* Path pairs whose duplication is deliberate and already recorded.
|
|
26
26
|
*
|
|
27
|
-
* The seed is authored from the always-loaded file and `
|
|
27
|
+
* The seed is authored from the always-loaded file and `seed-sync`
|
|
28
28
|
* exists to reconcile the two, so a bullet appearing in both is the design
|
|
29
29
|
* rather than a defect. Excluding by pair rather than by content is what the
|
|
30
30
|
* plan settled on: the duplication is a location fact this repository already
|
|
@@ -574,7 +574,7 @@ function authorityFor(
|
|
|
574
574
|
if (candidate.kind === 'seed') {
|
|
575
575
|
return {
|
|
576
576
|
authority: 'claude-md',
|
|
577
|
-
reason: `${INSTRUCTIONS_REL} is authored first and the seed carries it to a target, so an edit starts there and reaches the seed through
|
|
577
|
+
reason: `${INSTRUCTIONS_REL} is authored first and the seed carries it to a target, so an edit starts there and reaches the seed through seed-sync`,
|
|
578
578
|
}
|
|
579
579
|
}
|
|
580
580
|
|
|
@@ -26,7 +26,7 @@ const HEADING = /^#{1,6}\s/
|
|
|
26
26
|
* run and a false one shortens every run around it until the measure stops
|
|
27
27
|
* reporting, which is the dearer of the two.
|
|
28
28
|
*
|
|
29
|
-
* A colon ends most markers and not all of them. `
|
|
29
|
+
* A colon ends most markers and not all of them. `review-pr` writes
|
|
30
30
|
* three colon-less ones into every body it posts and a bold path heading for
|
|
31
31
|
* each file it reviews, so requiring the colon held a real seam out. Two
|
|
32
32
|
* signals stand in where the colon is absent, because the shape a marker has to
|
package/src/migrate/rename.ts
CHANGED
|
@@ -46,8 +46,9 @@ export interface RenameRuleSpec {
|
|
|
46
46
|
* A rename whose tokens are whole names wants this, and one whose tokens are
|
|
47
47
|
* word stems cannot have it. `aitk` is a stem that legitimately carries a
|
|
48
48
|
* suffix, as in `aitk-allow-superseded`, so requiring a boundary there would
|
|
49
|
-
* leave every hyphenated form behind. A skill name is not a stem, and
|
|
50
|
-
*
|
|
49
|
+
* leave every hyphenated form behind. A skill name is not a stem, and the
|
|
50
|
+
* spelling below is the pre-rename one on purpose (canon-keep-retired):
|
|
51
|
+
* `claude-worktree` inside `claude-worktrees` is a different subject, the
|
|
51
52
|
* wiki page about the harness feature, which the rename must not move.
|
|
52
53
|
*/
|
|
53
54
|
readonly wholeToken?: boolean
|
|
@@ -88,7 +89,7 @@ const NEVER_MATCHES = '(?!)'
|
|
|
88
89
|
*
|
|
89
90
|
* A plain word boundary rejects a following letter and accepts a following
|
|
90
91
|
* hyphen, since `\b` reads a hyphen as the end of a word. That leaves
|
|
91
|
-
* `
|
|
92
|
+
* `plan-intake` matching inside `plan-intake-answer`, with the ordering of
|
|
92
93
|
* the alternation the only thing standing between them. Naming the characters
|
|
93
94
|
* that continue an identifier holds on both, so the ordering and the boundary
|
|
94
95
|
* each cover what the other could miss, and a slash, a dot, or a backtick
|
package/src/pr/bijection.ts
CHANGED
|
@@ -99,7 +99,7 @@ function owesNoBullet(path: string): boolean {
|
|
|
99
99
|
* names.
|
|
100
100
|
*
|
|
101
101
|
* An unanchored claim matches on a segment-anchored suffix, which is what lets
|
|
102
|
-
* `
|
|
102
|
+
* `role-worker/SKILL.md` credit `claude/skills/role-worker/SKILL.md`. That
|
|
103
103
|
* asymmetry is deliberate: a partial spelling can confirm a changed file was
|
|
104
104
|
* named and never accuse one of being absent, because nothing here separates a
|
|
105
105
|
* path written short from a path written wrong.
|
package/src/pr/paths.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface PathClaim {
|
|
|
20
20
|
* True when the first segment names an entry the tree actually holds.
|
|
21
21
|
*
|
|
22
22
|
* An unanchored claim is a path written partially, such as
|
|
23
|
-
* `
|
|
23
|
+
* `role-worker/SKILL.md` for a file under `claude/skills/`. It can confirm
|
|
24
24
|
* that a changed file was named and can never accuse one of being absent,
|
|
25
25
|
* because the comparison has no way to tell a partial spelling from a
|
|
26
26
|
* genuinely wrong one.
|
|
@@ -145,7 +145,7 @@ function maskSpans(text: string): string {
|
|
|
145
145
|
* that came out. A path past the comma was left out of the claim set entirely
|
|
146
146
|
* and fell to the unnamed direction, accepted here on the ground that the
|
|
147
147
|
* direction reports without grading. It graded anyway, one consumer removed:
|
|
148
|
-
* `
|
|
148
|
+
* `review-pr` reads `unnamed` as a question to put to the branch author,
|
|
149
149
|
* and on 2026-09-01 the question went to three pull requests over bullets that
|
|
150
150
|
* had named the files all along, with `#1329` gaining bullets it did not need.
|
|
151
151
|
*
|
package/src/sandbox/expect.ts
CHANGED
|
@@ -78,6 +78,14 @@ export interface CheckInput {
|
|
|
78
78
|
* nothing to watch, both of which produce the same empty `escapes` list.
|
|
79
79
|
*/
|
|
80
80
|
readonly escapesWatched?: boolean
|
|
81
|
+
/**
|
|
82
|
+
* Names of sessions the client's own registry carried both before and after
|
|
83
|
+
* the run, so present rather than dispatched by it. Undefined when the
|
|
84
|
+
* caller supplied none, which is not the same as a run that had no witness:
|
|
85
|
+
* the registry carries no contract, so a client that stops writing a record
|
|
86
|
+
* per session makes this list empty regardless of who else was live.
|
|
87
|
+
*/
|
|
88
|
+
readonly concurrentSessions?: readonly string[]
|
|
81
89
|
readonly envelope?: RunEnvelope
|
|
82
90
|
}
|
|
83
91
|
|
|
@@ -386,11 +394,19 @@ function checkWriteScope(
|
|
|
386
394
|
* empty `escapes` list. `watched` is what tells them apart: a run that had
|
|
387
395
|
* nothing to watch reports unmeasured rather than passing on a diff it never
|
|
388
396
|
* had the target to take.
|
|
397
|
+
*
|
|
398
|
+
* A witnessed concurrent session never softens the verdict. `concurrentSessions`
|
|
399
|
+
* is evidence a reader can act on without a second lookup, appended to the
|
|
400
|
+
* message on an unbounded escape, never a reason to pass or skip one: a session
|
|
401
|
+
* merely alive throughout the run proves someone else was busy, not that a
|
|
402
|
+
* given file is theirs, and a real dispatch escape reads identically to an
|
|
403
|
+
* innocent sibling's write either way.
|
|
389
404
|
*/
|
|
390
405
|
function checkEscapeScope(
|
|
391
406
|
expectation: Expectation,
|
|
392
407
|
escapes: readonly string[] | undefined,
|
|
393
408
|
watched: boolean | undefined,
|
|
409
|
+
concurrentSessions: readonly string[] | undefined,
|
|
394
410
|
): KindOutcome {
|
|
395
411
|
if (expectation.escapeScope === undefined) return { results: [], skipped: [] }
|
|
396
412
|
|
|
@@ -416,12 +432,20 @@ function checkEscapeScope(
|
|
|
416
432
|
}
|
|
417
433
|
|
|
418
434
|
const globs = expectation.escapeScope.map((glob) => new Bun.Glob(glob))
|
|
435
|
+
const witnessCount = concurrentSessions?.length ?? 0
|
|
436
|
+
const witnessSuffix =
|
|
437
|
+
witnessCount > 0
|
|
438
|
+
? ` (${witnessCount} session${witnessCount === 1 ? '' : 's'} live throughout the run)`
|
|
439
|
+
: ''
|
|
419
440
|
|
|
420
441
|
return {
|
|
421
442
|
results: escapes.map((path) =>
|
|
422
443
|
globs.some((glob) => glob.match(path))
|
|
423
444
|
? { ok: true, message: `declared escape: ${path}` }
|
|
424
|
-
: {
|
|
445
|
+
: {
|
|
446
|
+
ok: false,
|
|
447
|
+
message: `unbounded escape: ${path}${witnessSuffix}`,
|
|
448
|
+
},
|
|
425
449
|
),
|
|
426
450
|
skipped: [],
|
|
427
451
|
}
|
|
@@ -514,6 +538,7 @@ export function checkExpectation(
|
|
|
514
538
|
expectation,
|
|
515
539
|
input.escapes,
|
|
516
540
|
input.escapesWatched,
|
|
541
|
+
input.concurrentSessions,
|
|
517
542
|
)
|
|
518
543
|
const reply = checkReply(expectation, input.envelope)
|
|
519
544
|
const envelope = checkEnvelope(expectation, input.envelope)
|
|
@@ -61,7 +61,7 @@ export const REFERENCE_MARKER = 'canon-allow-reference'
|
|
|
61
61
|
* The leading boundary is what excludes the repair form by construction rather
|
|
62
62
|
* than by exemption, and that is load bearing. A lookbehind rejecting a word
|
|
63
63
|
* character before `#` never matches `erclx/canon#1299`, so a qualified
|
|
64
|
-
* reference passes with no marker, and `claude/skills/
|
|
64
|
+
* reference passes with no marker, and `claude/skills/session-worktree/SKILL.md`
|
|
65
65
|
* already ships `anthropics/claude-code#58345` in exactly that form.
|
|
66
66
|
*
|
|
67
67
|
* The trailing boundary is what the first shape of this pattern lacked, and it
|
package/src/sync/seeds-report.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface SeedsReport {
|
|
|
34
34
|
* Classifies every seed the toolkit ships against the target's copy, and never
|
|
35
35
|
* returns a change. Seeds are copy-once files a project is expected to edit, so
|
|
36
36
|
* the engine's copy path would overwrite `CLAUDE.md` wholesale. Reporting alone
|
|
37
|
-
* is what lets `
|
|
37
|
+
* is what lets `seed-sync` merge one section at a time instead.
|
|
38
38
|
*
|
|
39
39
|
* Attribution reuses the history reader rather than the engine's own recovery
|
|
40
40
|
* pass, which is private and takes a `SyncAdapter` seeds have no way to supply.
|
package/src/targets/pulls.ts
CHANGED
|
@@ -7,8 +7,8 @@ const GH_TIMEOUT_MS = 30_000
|
|
|
7
7
|
/**
|
|
8
8
|
* The two headings a review pass posts under.
|
|
9
9
|
*
|
|
10
|
-
* Owned by `
|
|
11
|
-
* the way `
|
|
10
|
+
* Owned by `review-pr`, which states the full set once, and pinned here
|
|
11
|
+
* the way `role-orchestrator/scripts/poll.sh` pins them. All three surfaces
|
|
12
12
|
* ship separately, so a heading added in that skill goes stale here with
|
|
13
13
|
* nothing comparing the copies.
|
|
14
14
|
*/
|
package/src/tasks/answers.ts
CHANGED
|
@@ -186,7 +186,7 @@ export function resolvePlanReference(
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
// An archived plan answers every question and would report as launchable, so
|
|
189
|
-
// the name would clear a dispatch that `
|
|
189
|
+
// the name would clear a dispatch that `auto-ship` Step 1 then refuses
|
|
190
190
|
// as already-shipped work. Catching it here is a step earlier than the worker.
|
|
191
191
|
// Both roots, for the reason `resolveLivePlan` carries: the reference is a
|
|
192
192
|
// string a caller wrote, and one spelling the root this tree has since left is
|
package/src/tasks/archive.ts
CHANGED
|
@@ -269,7 +269,7 @@ function isRowFor(line: string, target: string): boolean {
|
|
|
269
269
|
|
|
270
270
|
/**
|
|
271
271
|
* Resolves the `Plan:` target against the board and against the project root
|
|
272
|
-
* both, which is how `
|
|
272
|
+
* both, which is how `docs-fold` reads the same line. It accepts `../plans/x.md`
|
|
273
273
|
* and `.canon/plans/x.md` as one file, so a gate reading only the first form
|
|
274
274
|
* would pass the second and strand the plan this exists to protect.
|
|
275
275
|
*
|
|
@@ -323,7 +323,7 @@ function atOneRoot(path: string, plans: string[], root: string): string {
|
|
|
323
323
|
|
|
324
324
|
/**
|
|
325
325
|
* Names the other live tasks whose `Plan:` line lands on the same file. This is
|
|
326
|
-
* the rule `
|
|
326
|
+
* the rule `docs-fold` applies before it archives a plan, held here so one
|
|
327
327
|
* question has one implementation: a plan another live task still cites is a
|
|
328
328
|
* plan the sweep is correct to leave, and a guard that read the folder instead
|
|
329
329
|
* refused every task sharing one plan and deadlocked the board against the
|
|
@@ -380,7 +380,7 @@ export type CitationOutcome = PlanCitations | ArchiveRefused
|
|
|
380
380
|
|
|
381
381
|
/**
|
|
382
382
|
* Answers where one task's plan sits and who else holds it, which is the whole
|
|
383
|
-
* of the last-live-citation rule. `
|
|
383
|
+
* of the last-live-citation rule. `docs-fold` reads this rather than scanning
|
|
384
384
|
* the board itself, so the sweep that moves a plan and the gate that refuses a
|
|
385
385
|
* task archive cannot drift into disagreeing about which plan is free.
|
|
386
386
|
*
|
|
@@ -564,7 +564,7 @@ async function resolveStem(
|
|
|
564
564
|
|
|
565
565
|
/**
|
|
566
566
|
* Archives one task as a single unit: the move, the ordering-row removal, and
|
|
567
|
-
* the index regen. The hook and `
|
|
567
|
+
* the index regen. The hook and `task-board` both call this, so every gate
|
|
568
568
|
* refuses rather than reports. A caller with nobody watching cannot act on a
|
|
569
569
|
* warning, and two callers gating differently is the drift this exists to stop.
|
|
570
570
|
*/
|
package/src/tasks/record.ts
CHANGED
|
@@ -311,7 +311,7 @@ export async function recordPullRequest(
|
|
|
311
311
|
|
|
312
312
|
/**
|
|
313
313
|
* Records a plan's path on the task it belongs to, as the task's `Plan:` line.
|
|
314
|
-
* `
|
|
314
|
+
* `plan-feature` runs this right after the plan file lands, resolving the
|
|
315
315
|
* reference the same two ways `canon tasks plan-answers` does, through
|
|
316
316
|
* `planCandidates`, so a bare slug and a board-relative path both resolve.
|
|
317
317
|
*
|
|
@@ -346,7 +346,7 @@ export async function recordPlan(
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
/**
|
|
349
|
-
* Marks the named outcomes `[x]` in place. `
|
|
349
|
+
* Marks the named outcomes `[x]` in place. `docs-fold` runs this against a
|
|
350
350
|
* board it can read and cannot edit from a linked worktree, and the positions
|
|
351
351
|
* come from the read it already made.
|
|
352
352
|
*/
|
package/src/tasks/validate.ts
CHANGED
|
@@ -557,7 +557,7 @@ async function listTaskStems(dir: string): Promise<string[]> {
|
|
|
557
557
|
|
|
558
558
|
/**
|
|
559
559
|
* Resolves a pointer against the board and against the project root both, the
|
|
560
|
-
* way `
|
|
560
|
+
* way `docs-fold` reads the same line. A fragment is dropped first, since an
|
|
561
561
|
* anchor is part of the link and never part of the path.
|
|
562
562
|
*/
|
|
563
563
|
function resolves(target: string, dir: string, root: string): boolean {
|
package/src/teach/nav.ts
CHANGED
|
@@ -109,6 +109,17 @@ const GLOSSARY_FILTER_SCRIPT = `<script>
|
|
|
109
109
|
var input = document.getElementById("gfilter");
|
|
110
110
|
var list = document.getElementById("gloss");
|
|
111
111
|
if (!input || !list) return;
|
|
112
|
+
function updateGroups() {
|
|
113
|
+
list.querySelectorAll(".gloss-group").forEach(function (heading) {
|
|
114
|
+
var el = heading.nextElementSibling;
|
|
115
|
+
var any = false;
|
|
116
|
+
while (el && !el.classList.contains("gloss-group")) {
|
|
117
|
+
if (el.style.display !== "none") any = true;
|
|
118
|
+
el = el.nextElementSibling;
|
|
119
|
+
}
|
|
120
|
+
heading.style.display = any ? "" : "none";
|
|
121
|
+
});
|
|
122
|
+
}
|
|
112
123
|
input.addEventListener("input", function () {
|
|
113
124
|
var q = input.value.toLowerCase();
|
|
114
125
|
var n = 0;
|
|
@@ -118,6 +129,7 @@ const GLOSSARY_FILTER_SCRIPT = `<script>
|
|
|
118
129
|
if (match) n++;
|
|
119
130
|
});
|
|
120
131
|
list.classList.toggle("none", n === 0);
|
|
132
|
+
updateGroups();
|
|
121
133
|
});
|
|
122
134
|
var clear = list.querySelector(".clear");
|
|
123
135
|
if (clear) {
|
|
@@ -457,8 +469,90 @@ function renderGlossaryEntry(entry: string): string {
|
|
|
457
469
|
return `<div class="gterm"><b>${escapeHtml(term)}</b><span>${escapeHtml(definition)}</span></div>`
|
|
458
470
|
}
|
|
459
471
|
|
|
460
|
-
|
|
461
|
-
|
|
472
|
+
const FIRST_SEEN_PATTERN = / First seen in (.+)\.$/
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The lesson or reference page an entry's own "First seen in" sentence
|
|
476
|
+
* names, absent when the entry predates that citation convention.
|
|
477
|
+
*/
|
|
478
|
+
function firstSeenFile(entry: string): string | undefined {
|
|
479
|
+
return FIRST_SEEN_PATTERN.exec(entry)?.[1]
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* `firstSeenFile` names a page free-form, per the `--first-seen` flag it
|
|
484
|
+
* comes from, so it may carry a directory prefix a lesson's own `file` does
|
|
485
|
+
* not. Comparing basenames is what keeps `lessons/0001-x.html` and
|
|
486
|
+
* `0001-x.html` resolving to the same lesson without a suffix match risking
|
|
487
|
+
* a false hit across two differently-prefixed filenames.
|
|
488
|
+
*/
|
|
489
|
+
function matchingLesson(
|
|
490
|
+
file: string,
|
|
491
|
+
metas: readonly LessonMeta[],
|
|
492
|
+
): LessonMeta | undefined {
|
|
493
|
+
const basename = file.split('/').pop()
|
|
494
|
+
return metas.find((meta) => meta.file === basename)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
interface GlossaryGroup {
|
|
498
|
+
readonly heading: string
|
|
499
|
+
readonly entries: readonly string[]
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const OTHER_TERMS_HEADING = 'Other terms'
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Groups already-alphabetical glossary entries by the lesson their own
|
|
506
|
+
* "First seen in" sentence names, in lesson order. An entry naming a
|
|
507
|
+
* reference page instead, or carrying no citation at all, cannot be
|
|
508
|
+
* attributed to a lesson and trails in its own group, keeping the
|
|
509
|
+
* alphabetical order the source entries already carry.
|
|
510
|
+
*/
|
|
511
|
+
function groupGlossaryEntries(
|
|
512
|
+
entries: readonly string[],
|
|
513
|
+
metas: readonly LessonMeta[],
|
|
514
|
+
): readonly GlossaryGroup[] {
|
|
515
|
+
const byLesson = new Map<string, string[]>()
|
|
516
|
+
const other: string[] = []
|
|
517
|
+
|
|
518
|
+
for (const entry of entries) {
|
|
519
|
+
const file = firstSeenFile(entry)
|
|
520
|
+
const lesson = file ? matchingLesson(file, metas) : undefined
|
|
521
|
+
|
|
522
|
+
if (lesson) {
|
|
523
|
+
const list = byLesson.get(lesson.file) ?? []
|
|
524
|
+
list.push(entry)
|
|
525
|
+
byLesson.set(lesson.file, list)
|
|
526
|
+
} else {
|
|
527
|
+
other.push(entry)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const groups: GlossaryGroup[] = []
|
|
532
|
+
for (const meta of metas) {
|
|
533
|
+
const list = byLesson.get(meta.file)
|
|
534
|
+
if (list) groups.push({ heading: meta.title, entries: list })
|
|
535
|
+
}
|
|
536
|
+
if (other.length > 0) {
|
|
537
|
+
groups.push({ heading: OTHER_TERMS_HEADING, entries: other })
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return groups
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function renderGlossaryGroup(group: GlossaryGroup): string {
|
|
544
|
+
const entries = group.entries.map(renderGlossaryEntry).join('')
|
|
545
|
+
|
|
546
|
+
return `<h3 class="gloss-group">${escapeHtml(group.heading)}</h3>${entries}`
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function renderGlossarySection(
|
|
550
|
+
entries: readonly string[],
|
|
551
|
+
metas: readonly LessonMeta[],
|
|
552
|
+
): string {
|
|
553
|
+
const rendered = groupGlossaryEntries(entries, metas)
|
|
554
|
+
.map(renderGlossaryGroup)
|
|
555
|
+
.join('')
|
|
462
556
|
|
|
463
557
|
return `<h2>Glossary <span class="count">${entries.length}</span></h2>
|
|
464
558
|
<input class="filter" type="search" id="gfilter" aria-label="Filter glossary terms" aria-controls="gloss" placeholder="term">
|
|
@@ -610,7 +704,7 @@ async function renderContentsPage(
|
|
|
610
704
|
referenceRows
|
|
611
705
|
? `<h2>Reference pages</h2>\n<ul class="toc">${referenceRows}</ul>`
|
|
612
706
|
: '',
|
|
613
|
-
renderGlossarySection(detail.glossary),
|
|
707
|
+
renderGlossarySection(detail.glossary, metas),
|
|
614
708
|
]
|
|
615
709
|
.filter((section) => section !== '')
|
|
616
710
|
.join('\n\n')
|
package/standards/glossary.md
CHANGED
|
@@ -58,6 +58,14 @@ A glossary failing these is non-conforming even when it satisfies every shape ru
|
|
|
58
58
|
- Name each category so a reader picks it from the term alone. A category a reader cannot predict makes the grouping a second thing to search.
|
|
59
59
|
- State a departure from any rule above in the file itself, naming what it departs from and why. A glossary serving no single body of material is the case that produces one, since a term drawn from everywhere has no first appearance to name.
|
|
60
60
|
|
|
61
|
+
## Rendered grouping
|
|
62
|
+
|
|
63
|
+
Applies to a rendered glossary page, never to the source file above, which stays the flat alphabetical list the `## Grouping` rules above govern.
|
|
64
|
+
|
|
65
|
+
- Group a rendered glossary by the lesson its own "First seen in" citation names, ordered by lesson order, under a sub-heading naming the lesson's title rather than its filename.
|
|
66
|
+
- Trail with an "Other terms" group holding any entry the citation cannot attribute to a lesson, whether it names a reference page instead or carries no citation at all. Keep it in the alphabetical order the source file already carries.
|
|
67
|
+
- Keep the workspace-wide term filter matching against every group, and drop a group's own heading once filtering leaves nothing under it.
|
|
68
|
+
|
|
61
69
|
## Template
|
|
62
70
|
|
|
63
71
|
```markdown
|
package/standards/groundwork.md
CHANGED
|
@@ -156,7 +156,7 @@ A lean is weaker than the suggestion a plan file carries. It records the current
|
|
|
156
156
|
## Conventions
|
|
157
157
|
|
|
158
158
|
- State a number with what it settles. The strongest sections are the ones where a measurement answers a named question and says so.
|
|
159
|
-
- Route a finding that would change an existing standard or rule through `
|
|
159
|
+
- Route a finding that would change an existing standard or rule through `plan-intake`. Only a demonstrated failure changes one.
|
|
160
160
|
- Let the file count follow the number of genuinely separable questions, not the importance of the topic. A large topic with one question is a small folder.
|
|
161
161
|
|
|
162
162
|
## Anti-patterns
|
package/standards/snippets.md
CHANGED
|
@@ -38,7 +38,7 @@ Overlapping a skill that does the same job is not disqualifying on its own. A sn
|
|
|
38
38
|
## Use patterns
|
|
39
39
|
|
|
40
40
|
- Run-as-is: invoke and send immediately. The snippet is self-contained and needs no extra context.
|
|
41
|
-
- Invoke-then-add-context: invoke the snippet, then append specifics in the same message (e.g. invoke `
|
|
41
|
+
- Invoke-then-add-context: invoke the snippet, then append specifics in the same message (e.g. invoke `plan-feature`, then add the feature name or extra constraints)
|
|
42
42
|
- Invoke-on-history: invoke after a discussion. The snippet uses prior conversation as implicit context with no additional input needed (e.g. invoke `claude-figma` after discussing a design).
|
|
43
43
|
|
|
44
44
|
## Authoring
|
package/standards/tasks.md
CHANGED
|
@@ -47,7 +47,7 @@ The handoff takes one file per session for the reason a task does. A single shar
|
|
|
47
47
|
|
|
48
48
|
The catalog is the one reader that filters nothing, so it carries a row per sibling alongside the tasks. That is what a folder catalog is for, and the handoffs are what make it worth stating: a board accumulates one row per session that ever wrote one, with nothing pruning them. Anything reading the catalog as the backlog therefore does its own filtering, and a reader that takes every row as a task reports the handoffs as queued work.
|
|
49
49
|
|
|
50
|
-
The `
|
|
50
|
+
The `task-board` skill creates and archives task files, and the archive carries the task's plan with it. `docs-fold` marks outcomes `[x]` in an existing file. Neither does the other's job.
|
|
51
51
|
|
|
52
52
|
## Ordering
|
|
53
53
|
|
|
@@ -219,9 +219,9 @@ A project that archived plans before the folder nested under `.canon/plans/` hol
|
|
|
219
219
|
|
|
220
220
|
One plan per task. A plan cited by two tasks is a misfile rather than a shape to design for, which is why the sweep counts citations before archiving: the count is a guard against the misfile stranding a pointer, not support for the shape.
|
|
221
221
|
|
|
222
|
-
`canon tasks plan-link <task> <plan>` writes or corrects the `Plan:` line, mirroring how `canon tasks pull-request` writes its own. `
|
|
222
|
+
`canon tasks plan-link <task> <plan>` writes or corrects the `Plan:` line, mirroring how `canon tasks pull-request` writes its own. `plan-feature` calls it right after the plan file lands, when an existing task names the feature, so the line is a mechanical write rather than hand-edited markdown.
|
|
223
223
|
|
|
224
|
-
`Groundwork:` points at `../groundwork/<slug>/`, the folder `
|
|
224
|
+
`Groundwork:` points at `../groundwork/<slug>/`, the folder `plan-groundwork` fills. It names the surface it points at the way `Plan:` does. Use this key alone. `Research record` and `Decision record` are earlier spellings of the same thing and both convert to it.
|
|
225
225
|
|
|
226
226
|
`Intake:` points at `../intake/<slug>/`, the folder an intake pass fills. Use it rather than `Groundwork:`, because a groundwork track measures one question in depth while an intake dispositions many across a tree, and one key covering both loses which kind of pass produced the task. The line names the folder rather than an item inside it. A task routinely promotes several items at once, so an anchored line would name one and drop the rest, and the item numbers belong in that task's `## Findings`.
|
|
227
227
|
|
package/standards/teach.md
CHANGED
|
@@ -15,7 +15,7 @@ Governs a learning workspace under `.canon/teach/<nn>-<topic>/`: folder layout,
|
|
|
15
15
|
|
|
16
16
|
Does not govern:
|
|
17
17
|
|
|
18
|
-
- The frontmatter, entry shape, and ordering of the glossary the workspace holds: the `
|
|
18
|
+
- The frontmatter, entry shape, and ordering of the glossary the workspace holds: the `teach-workspace` skill, which carries that reference
|
|
19
19
|
- What a lesson teaches, how it sequences difficulty, and what makes one worth returning to, which belong to the surface driving the workspace
|
|
20
20
|
- Where a durable page goes once it leaves the workspace, which belongs to the routing test the destination surface states
|
|
21
21
|
- One question measured in depth before anyone can plan against it: `groundwork.md`
|
|
@@ -113,7 +113,7 @@ Take the date and the rung from what the surface driving the workspace reports r
|
|
|
113
113
|
|
|
114
114
|
## GLOSSARY.md
|
|
115
115
|
|
|
116
|
-
Required in every workspace, holding one entry per term the subject defines. The glossary reference the `canon:
|
|
116
|
+
Required in every workspace, holding one entry per term the subject defines. The glossary reference the `canon:teach-workspace` skill carries fixes what an entry looks like, how the file orders and groups them, and which terms it carries, so this standard states only that the file exists and sits at the workspace root. That reference ships with the plugin rather than installing here, because a promoted glossary keeps its shape wherever it lands and no project folder covers every destination. Say so and stop rather than working the shape from memory when the project has no plugin to read it from.
|
|
117
117
|
|
|
118
118
|
Name the lesson or reference page a term first appears in as that reference requires. A workspace is the case it was written for, so a glossary here has a first appearance to name.
|
|
119
119
|
|