@erclx/canon 4.114.0 → 4.116.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/document-health/references/axes.md +1 -1
- package/claude/skills/target-setup/SKILL.md +1 -1
- package/claude/skills/target-setup/references/detect.md +43 -9
- package/claude/skills/teach-workspace/SKILL.md +10 -6
- package/claude/skills/teach-workspace/references/lesson-craft.md +7 -3
- package/docs/agents/audits.md +4 -2
- package/docs/agents/commands.md +4 -2
- package/docs/agents/context-audit-checks.md +6 -6
- package/docs/agents/context-audit.md +1 -1
- package/docs/agents/context-classify.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/sessions.md +84 -1
- package/docs/agents/teach.md +26 -4
- package/docs/target-projects.md +4 -2
- package/package.json +1 -1
- package/src/audits/catalog.ts +52 -0
- package/src/commands/context.ts +30 -10
- package/src/commands/sessions.ts +222 -0
- package/src/commands/teach.ts +22 -4
- package/src/context/audit.ts +54 -18
- package/src/records/backup.ts +1 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sessions/registry.ts +11 -7
- package/src/sessions/transfer/bundle.ts +546 -0
- package/src/sessions/transfer/transcript.ts +148 -0
- package/src/teach/components/paragraph.tsx +19 -2
- package/src/teach/components/refs.tsx +28 -0
- package/src/teach/cross-references.ts +78 -0
- package/src/teach/lesson.ts +1 -1
- package/src/teach/nav.ts +278 -16
- package/src/teach/render.ts +94 -6
- package/standards/teach.md +13 -12
- package/tooling/go/configs/.golangci.yml +25 -0
- package/tooling/go/configs/scripts/verify.sh +95 -0
- package/tooling/go/manifest.toml +13 -0
- package/tooling/go/reference.md +60 -0
- package/tooling/go/seeds/.cspell/tech-stack.txt +16 -0
- package/tooling/go/seeds/smoke_test.go +9 -0
- package/tooling/php/configs/.php-cs-fixer.dist.php +18 -0
- package/tooling/php/configs/phpstan.neon +5 -0
- package/tooling/php/configs/phpunit.xml +18 -0
- package/tooling/php/configs/scripts/verify.sh +100 -0
- package/tooling/php/manifest.toml +15 -0
- package/tooling/php/reference.md +74 -0
- package/tooling/php/seeds/.cspell/tech-stack.txt +5 -0
- package/tooling/php/seeds/tests/SmokeTest.php +13 -0
|
@@ -40,7 +40,7 @@ Weight is a judgment a reader settles rather than a defect, which the verb state
|
|
|
40
40
|
|
|
41
41
|
**Evidence class:** `measured` for the audit's findings, `classified` for the sweep's verdicts.
|
|
42
42
|
|
|
43
|
-
The audit answers required sections, entry length, citation resolution, reference form, catalog tables, provenance, and index drift. A citation resolving to nothing is a fact and gates. Entry length, reference form, table shape, and provenance are judgments under every flag.
|
|
43
|
+
The audit answers required sections, entry length, citation resolution, reference form, catalog tables, provenance, and index drift. Required sections reach wireframes as well as context entries, naming each wireframe short of the regions, states, copy, or exclusions section its standard requires, which is how a surface on an older wireframe shape reports rather than reading as clean. A citation resolving to nothing is a fact and gates. Entry length, reference form, table shape, and provenance are judgments under every flag.
|
|
44
44
|
|
|
45
45
|
The sweep classifies each section as keep, replace or rewrite, history, or move. A `move` verdict is the placement finding proper, naming content sitting in the wrong document. Each verdict carries its own confidence, which is why the class is `classified` rather than `measured`, and a low-confidence `move` is a prompt to look rather than an instruction to cut.
|
|
46
46
|
|
|
@@ -92,7 +92,7 @@ Step 2: `canon tooling sync <tooling-stack> --write` installs stack deps, script
|
|
|
92
92
|
CANON_NON_INTERACTIVE=1 canon tooling sync <tooling-stack> <target> --write
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
Monorepo with multiple language roots: run `canon init` once at the repo root so `base` (husky, prettier, cspell, commitlint, CI) lands single, then sync each subtree with `--skip base` so the shared layer is not re-dropped.
|
|
95
|
+
Monorepo with multiple language roots: run `canon init` once at the repo root so `base` (husky, prettier, cspell, commitlint, CI) lands single, then sync each subtree with `--skip base` so the shared layer is not re-dropped. Take the subtree commands from the per-root rows the preview printed rather than picking folders by hand. Detection found each root and its stack, and the two commands below show the shape.
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
CANON_NON_INTERACTIVE=1 canon tooling sync vite-react ./frontend --skip base --write
|
|
@@ -27,19 +27,53 @@ since the chain reaches the tooling resolution a moment later.
|
|
|
27
27
|
|
|
28
28
|
## Detect
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
A monorepo keeps its languages in subfolders, so read the markers from every
|
|
31
|
+
folder to depth two rather than from the root alone. Collect the language roots
|
|
32
|
+
first:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git ls-files --cached --others --exclude-standard \
|
|
36
|
+
| grep -E '(^|/)(package\.json|pyproject\.toml|go\.mod|composer\.json|Cargo\.toml)$' \
|
|
37
|
+
| awk -F/ 'NF <= 3'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Every folder holding a marker is a language root, and the repository root is
|
|
41
|
+
one when it holds a marker itself. Going through git keeps ignored trees such as
|
|
42
|
+
`node_modules/`, `vendor/`, and `.venv/` off the list, since each carries
|
|
43
|
+
markers of its own. Outside a git repository, fall back to
|
|
44
|
+
`find . -maxdepth 3 \( -path '*/.*' -o -name node_modules -o -name vendor \) -prune -o -type f -print`
|
|
45
|
+
filtered to the same five names.
|
|
46
|
+
|
|
47
|
+
Depth two reaches a service folder at depth one and a workspace package under
|
|
48
|
+
`packages/<name>/` at depth two. Deeper, the walk starts finding fixtures and
|
|
49
|
+
examples as false roots.
|
|
50
|
+
|
|
51
|
+
Then read these per language root in parallel, skipping any that do not exist:
|
|
31
52
|
|
|
32
53
|
- `package.json`: `dependencies` and `devDependencies`
|
|
33
|
-
-
|
|
54
|
+
- The root's own configs: `astro.config.*`, `next.config.*`, `vite.config.*`, `tailwind.config.*`, `tsconfig.json`, `pyproject.toml`, `go.mod`, `composer.json`, `Cargo.toml`
|
|
55
|
+
|
|
56
|
+
A non-JavaScript marker outranks a `package.json` in the same folder. A Go or
|
|
57
|
+
Composer service often carries a `package.json` only to host scripts, so that
|
|
58
|
+
folder is a Go or PHP root rather than a JavaScript one.
|
|
59
|
+
|
|
60
|
+
Read these once, from the repository root:
|
|
61
|
+
|
|
34
62
|
- `canon/REQUIREMENTS.md` and `canon/ARCHITECTURE.md` if present
|
|
35
63
|
- Directory structure via `ls -1` of the project root and `src/` if present
|
|
36
64
|
|
|
37
65
|
## Resolve arguments
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
Governance installs once, at the repository root, and a rule's `paths:` globs
|
|
68
|
+
already reach every subfolder. Tooling installs per folder. So resolve one
|
|
69
|
+
governance stack for the whole repository and one tooling stack per language
|
|
70
|
+
root.
|
|
71
|
+
|
|
72
|
+
- **Stack:** pick the closest governance stack by matching the repository root's detected runtime or framework against stack names in the catalog. If the root matched nothing, `base` is the stack, including when subfolders matched, since no subfolder owns the repository and picking one by order would state a preference nobody gave. Mark it `fallback` in the preview only when no root matched anywhere.
|
|
73
|
+
- **Tooling stack:** for each language root, pick the closest tooling stack from `canon tooling list --json` (e.g. `vite-react`, `astro`, `nextjs`). Distinct from the governance stack. Fall back to `base` for a root with no framework or language match, and carry that fallback into its preview row.
|
|
74
|
+
- **Next.js:** map `next` in a root's `package.json` dependencies, or a `next.config.ts`/`.js`/`.mjs` file in that root, to the `nextjs` governance stack and the `nextjs` tooling stack. The dependency name does not match either stack name by itself.
|
|
75
|
+
- **Extras:** identify technologies not already covered by the picked governance stack, across every language root. A subfolder that matched a governance stack of its own contributes that stack's rules, read from `canon gov list --json`, and every root contributes its language rule. For each technology, find a rule whose `description` or `paths` points at it and pass it via `--add`. Dedupe against the picked stack's own rules. Do not add a rule the stack already pulls in.
|
|
76
|
+
- **Subfolder tooling:** a language root below the repository root takes its own `canon tooling sync <stack> <path> --skip base`, since `canon init` already lands `base` once at the root. Leave a root whose stack resolves to `base` out of the commands, since that sync would land nothing. A repository-root `package.json` hosting only formatters and hooks is that case, and it adds no row.
|
|
43
77
|
- **Skip (`--skip`):** `wiki` installs by default. Add `--skip wiki` only when the user explicitly wants it left out.
|
|
44
78
|
|
|
45
79
|
## Gap handling
|
|
@@ -67,10 +101,10 @@ toolkit repository, and option 2 hands the project-local file to `create-rule`.
|
|
|
67
101
|
|
|
68
102
|
Before executing, output:
|
|
69
103
|
|
|
70
|
-
- **Detected:** each technology with its evidence file
|
|
104
|
+
- **Detected:** each technology with its evidence file, path included, so a subfolder marker reads as one
|
|
71
105
|
- **Stack:** picked governance stack and resolved rule count. Mark it `fallback` when no detected runtime or framework matched a catalog name.
|
|
72
|
-
- **Tooling stack:** picked tooling stack
|
|
73
|
-
- **Extras:** each `--add` rule with a one-line reason
|
|
106
|
+
- **Tooling stack:** one row per language root, each carrying its path, its picked tooling stack, a `fallback` mark on the same test, and its own command. On the repository root's row, name what `base` lands: configs, seeds, and gitignore entries in every case, plus the JavaScript development dependencies, scripts, and hook activation wherever a `package.json` exists to carry them. A project outside that ecosystem runs none of the second group and keeps the first. Omit these rows entirely on the `gov` phase, which syncs no tooling.
|
|
107
|
+
- **Extras:** each `--add` rule with a one-line reason, listed in full. A second matched stack reaches the install only through this list, since `canon init --stack` takes one stack, so a shortened list reads as that stack dropped.
|
|
74
108
|
- **Skip:** any `--skip` entries with reason
|
|
75
109
|
- **Target:** resolved target path
|
|
76
110
|
- **Commands:** the full chain that will run
|
|
@@ -86,7 +86,7 @@ Reading the last record alone is what the schedule replaces. A topic missed thre
|
|
|
86
86
|
Two outputs with two lifetimes, and the split decides the format.
|
|
87
87
|
|
|
88
88
|
- A lesson is a self-contained page carrying its own quiz, a teach-back block, and the feedback for each question. It embeds one shared stylesheet rather than restating styles, and it is disposable and never promoted.
|
|
89
|
-
- A reference page goes to `reference/<slug>.md`, written for a reader with no learner in it. This is the half that survives the workspace, so it is written in markdown to pass the authoring gates a promotion would put it through.
|
|
89
|
+
- A reference page goes to `reference/<slug>.md`, written for a reader with no learner in it. This is the half that survives the workspace, so it is written in markdown to pass the authoring gates a promotion would put it through. `canon teach nav` renders it to a `reference/<slug>.html` sibling, and a lesson links that sibling, since a served `.md` opens as plain text.
|
|
90
90
|
|
|
91
91
|
Resolve the lesson before writing it, rather than composing its name or its quiz order by hand:
|
|
92
92
|
|
|
@@ -113,7 +113,7 @@ Write each option as a `<label class="opt" data-k="<letter>">` holding a radio `
|
|
|
113
113
|
|
|
114
114
|
Then carry a teach-back block beside the quiz. A quiz is recognition and the pedagogy prefers production, so a lesson offering only a quiz tests the weak form. Ask for an explanation to a named audience, and carry a `<details>` listing what a complete explanation covers, closed by default, so a learner reading with no session in the room can grade themselves. The reference states both shapes in full.
|
|
115
115
|
|
|
116
|
-
Compose the quiz and the teach-back block as one string. Below, that string is the block list's
|
|
116
|
+
Compose the quiz and the teach-back block as one string. Below, that string is the block list's last `raw` entry, followed only by `refs`, rather than markup written into the file by hand.
|
|
117
117
|
|
|
118
118
|
### Building the lesson body
|
|
119
119
|
|
|
@@ -126,12 +126,14 @@ echo '[
|
|
|
126
126
|
{"type":"heading","level":1,"text":"<title>"},
|
|
127
127
|
{"type":"paragraph","lede":true,"text":"<the dek>"},
|
|
128
128
|
{"type":"paragraph","text":"<a body paragraph>"},
|
|
129
|
+
{"type":"paragraph","text":"<a claim resting on a source>","cites":[1]},
|
|
129
130
|
{"type":"list","ordered":true,"items":["<step one>","<step two>"]},
|
|
130
|
-
{"type":"raw","html":"<the quiz and teach-back block composed above>"}
|
|
131
|
+
{"type":"raw","html":"<the quiz and teach-back block composed above>"},
|
|
132
|
+
{"type":"refs","items":[{"title":"<source name>","url":"<https URL>"}]}
|
|
131
133
|
]' | canon teach render --json
|
|
132
134
|
```
|
|
133
135
|
|
|
134
|
-
A `heading`, a `paragraph` (`lede: true` for the dek
|
|
136
|
+
A `heading`, a `paragraph` (`lede: true` for the dek, `cites` for a claim resting on a source), a `list`, and the one `refs` block the cites number into cover the structural body. Reach for `raw` only where none of those can carry the content, never as a shortcut around composing one, and give the quiz and teach-back block the array's last `raw` entry every time, followed only by `refs`, since their fixed contract is not a components concern. Take the call's `html` field and write it between the header's close marker and the footnav's open marker, and nothing else anywhere in the file.
|
|
135
137
|
|
|
136
138
|
Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the lesson body by hand as a fallback. That is the state this section exists to end, and a target holds this skill body before it holds the verb, since a plugin skill reaches a target the moment it merges while the CLI reaches one only when a release publishes.
|
|
137
139
|
|
|
@@ -167,12 +169,14 @@ Follow `${CLAUDE_SKILL_DIR}/references/lesson-craft.md` for what makes a lesson
|
|
|
167
169
|
|
|
168
170
|
### Hand over a link, never a path
|
|
169
171
|
|
|
170
|
-
A lesson is a page carrying a script, and an editor preview cannot run it, so a path alone opens markup with no working quiz. Serve the teach root and give the learner a link they can click:
|
|
172
|
+
A lesson is a page carrying a script, and an editor preview cannot run it, so a path alone opens markup with no working quiz. Serve the teach root and give the learner a link they can click. Take the serve line from the `Open` step of `canon teach list <topic>`, which resolves the teach folder against the directory the session runs in:
|
|
171
173
|
|
|
172
174
|
```bash
|
|
173
|
-
canon
|
|
175
|
+
canon teach list <topic>
|
|
174
176
|
```
|
|
175
177
|
|
|
178
|
+
Run the line it prints with `--json` appended, rather than writing `canon serve .canon/teach` by hand. `canon serve` resolves its folder against the cwd, so that literal serves an absent folder from a linked worktree, where the list verb reads the main worktree root.
|
|
179
|
+
|
|
176
180
|
Start it in the background so the session keeps going, and read `url` off the record rather than composing one. The verb walks past a port already in use, so the port it took is exactly the half a guessed URL gets wrong. Report the refusal and its `reason` when `ok` is false, and report it rather than proceeding silently when the verb does not resolve at all, which is an installed CLI predating it.
|
|
177
181
|
|
|
178
182
|
Do this on every run that opens or resumes a workspace, including one that writes no lesson, since the learner's route into what is already there is the same link.
|
|
@@ -14,6 +14,7 @@ A workspace accumulates lessons over weeks. The learner reads them as one body o
|
|
|
14
14
|
- `canon teach nav` embeds and re-embeds the shared stylesheet into every lesson on each run, so growing that stylesheet, by promoting anything used a second time into it, is the only step left by hand.
|
|
15
15
|
- Keep the structural furniture identical across lessons: where the title sits, where the quiz sits, what a correct answer looks like
|
|
16
16
|
- Do not restate styles inside a lesson. A local override is a decision the next lesson has to either copy or contradict.
|
|
17
|
+
- Refer to another lesson in the same workspace as "lesson" followed by its four-digit number, such as "lesson 0003", and let `canon teach nav` turn the mention into a link and report a number naming no lesson. A hand-written link is the one form nav never checks.
|
|
17
18
|
|
|
18
19
|
## Typography
|
|
19
20
|
|
|
@@ -73,14 +74,17 @@ The stepper hides and shows and sets nothing else, so how a selected option look
|
|
|
73
74
|
|
|
74
75
|
## The block list
|
|
75
76
|
|
|
76
|
-
`canon teach render` takes a JSON array of blocks and renders it through the same components a lesson body composes.
|
|
77
|
+
`canon teach render` takes a JSON array of blocks and renders it through the same components a lesson body composes. Five types, and every structural body composes from them:
|
|
77
78
|
|
|
78
79
|
- `{"type":"heading","level":1|2,"text":"<text>"}`: an `<h1>` or `<h2>`.
|
|
79
|
-
- `{"type":"paragraph","text":"<text>","lede":true}`: a `<p>`, marked `lede` for the dek that opens the lesson.
|
|
80
|
+
- `{"type":"paragraph","text":"<text>","lede":true,"cites":[1]}`: a `<p>`, marked `lede` for the dek that opens the lesson. `cites` adds a footnote marker per reference number after the text, and a lede takes none.
|
|
80
81
|
- `{"type":"list","items":["<text>", ...],"ordered":true}`: a `<ul>` or, with `ordered`, an `<ol>`.
|
|
82
|
+
- `{"type":"refs","items":[{"title":"<name>","url":"<https URL>","note":"<text>"}, ...]}`: the lesson's one reference list, numbered from 1 in item order. `url` and `note` are optional, and a `url` must be http or https.
|
|
81
83
|
- `{"type":"raw","html":"<markup>"}`: the escape hatch, passed through unescaped.
|
|
82
84
|
|
|
83
|
-
The quiz and the teach-back block above are the one case `raw` is always needed for, since their fixed contract is not a components concern. Give them the array's
|
|
85
|
+
The quiz and the teach-back block above are the one case `raw` is always needed for, since their fixed contract is not a components concern. Give them the array's last `raw` entry, followed only by `refs`, rather than reaching for `raw` anywhere the other types could carry the content instead.
|
|
86
|
+
|
|
87
|
+
A claim resting on a source carries a `cites` number, and the source goes in the lesson's one `refs` block rather than into the sentence. Draw every reference from what `canon teach resource --read` recorded, never from a lead, since a lead is a source nobody opened. Render refuses a cite no reference resolves, so number the list before citing into it.
|
|
84
88
|
|
|
85
89
|
## Teach back
|
|
86
90
|
|
package/docs/agents/audits.md
CHANGED
|
@@ -24,11 +24,13 @@ canon audits list --json
|
|
|
24
24
|
|
|
25
25
|
## What it runs
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Twenty-one verbs, listed by `canon audits list`. Each runs once in its fullest form, and the aggregate reads that verb's own record rather than imposing a shared envelope on it. Every one of those records already has consumers naming its keys, so a common shape would be a breaking change bought for tidiness.
|
|
28
28
|
|
|
29
29
|
The verbs walk separate trees and share no state, so they run together. Measured on the authoring machine at twelve verbs, a run finished in 0.8 seconds of wall clock against 4.4 seconds of processor, which is under every other stage in `bun run check`. `canon deps audit` is the one that changes that reading, since it reaches a network rather than a tree and its latency is the index's rather than this machine's: three clean runs measured 44.7, 77.6, and 62.9 seconds, and a stalled lookup runs to an explicit 120-second timeout rather than to bun's own 299-second ceiling. `--corpus` is what keeps a caller from paying that cost when it wants only the corpora on this disk.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Eighteen of the twenty-one read a tree on this disk, the one added by `restated.md` and the one added by `census.md` among them, since the first reads four such trees against each other and the second counts every file in one. The two added by `state-scoped-risk.md` read committed state rather than an arriving change, which is the gap every review surface here leaves by construction, and the one added by `label-coverage.md` reads a branch range against a map the project declares.
|
|
32
|
+
|
|
33
|
+
The placement entry runs `canon context classify sweep` pinned to `--backend off`, so it reads the regex layer alone whatever classifier setting the project resolves. A count that moves with whichever model a machine runs is not comparable across clones, and the model layer turned a sub-second read into one measured at about 17 minutes over this repository's context and wireframe sections on the authoring machine. It retains one tally per verdict, `keep`, `rewrite`, and `move`, and gates on nothing, since each verdict is a judgment. `document-health` is the surface that reads the same sections with the model.
|
|
32
34
|
|
|
33
35
|
Each is invoked as the CLI the caller is running rather than as a global `canon`. A globally installed binary resolves to the main checkout no matter which worktree is executing, so the aggregate would measure a tree the branch never touched and report a pass over it.
|
|
34
36
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -47,8 +47,8 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
|
|
|
47
47
|
| `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
|
|
48
48
|
| `canon teach lesson` | Resolve the numbered path, shared stylesheet, mission success lines, and one quiz option order per question for the next lesson, writing nothing (`--json`) |
|
|
49
49
|
| `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
|
|
50
|
-
| `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page,
|
|
51
|
-
| `canon teach render` | Render a JSON block list (heading, paragraph
|
|
50
|
+
| `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, each lesson's chrome from its four marker regions, and each reference page's rendered HTML sibling (`--json`) |
|
|
51
|
+
| `canon teach render` | Render a JSON block list (heading, paragraph with `cites`, list, refs, raw) from stdin to HTML through the shared components, refusing `bad-input` by block index (`--json`) |
|
|
52
52
|
| `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
|
|
53
53
|
| `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
|
|
54
54
|
| `canon records ordinal` | Report or claim the next ordinal `intake` and `groundwork` share, creating the folder atomically (`--claim`, `--json`) |
|
|
@@ -65,6 +65,8 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
|
|
|
65
65
|
| `canon migrate scratch-evidence` | Move a folder under `.tmp/` that a durable record cites as evidence to a numbered `.canon/evidence/<nn>-<folder>/`, repointing every citation live or archived, reporting the plan without `--write` (`--root`, `--json`) |
|
|
66
66
|
| `canon migrate record-layout` | Fold review receipts and the retired-entry archive under the memory pen, and move everything under `.canon/review/` that is not a review out to its own folder, repointing each citation that reaches into a moved folder or names a moved file, live or archived, reporting the plan without `--write` (`--root`, `--json`) |
|
|
67
67
|
| `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
|
|
68
|
+
| `canon sessions export` | Pack a session transcript, its side folder, and a manifest into one local gzip bundle, never uploading it (`--out`, `--json`) |
|
|
69
|
+
| `canon sessions import` | Place an exported session under the local project folder so `claude --resume` finds it, refusing to overwrite (`--root`, `--json`) |
|
|
68
70
|
| `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
|
|
69
71
|
| `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
|
|
70
72
|
| `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
@@ -9,11 +9,11 @@ What each finding from `canon context audit` means. The command surface, its fla
|
|
|
9
9
|
|
|
10
10
|
## Required sections
|
|
11
11
|
|
|
12
|
-
The required-section check reports what does not declare `## Overview` and `## Layout`, the two sections `standards/context.md` marks required.
|
|
12
|
+
The required-section check reports what does not declare the sections its folder's standard requires. A context entry owes `## Overview` and `## Layout`, the two sections `standards/context.md` marks required. A wireframe owes `## Regions`, `## States`, `## Copy`, and `## Not on this surface`, the sections answering the four questions `standards/wireframes.md` tests a surface against. `## Behavior` is expected of a wireframe rather than required, since a static surface has no interaction to state. The lists are held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
|
|
13
13
|
|
|
14
|
-
Which unit answers depends on the folder. Entries of the folder named under `.claude/` are one domain each, so each answers for itself and a finding names the entry. Entries of a folder a domain split into describe that one domain between them, so any sibling answers and a finding names the folder. Holding a split folder to the rule per file would report every child beside its `overview.md`, and rolling the named folder up would let one conforming entry stand in for every other domain sitting next to it.
|
|
14
|
+
Which unit answers depends on the folder. Entries of the folder named under `.claude/` are one domain each, so each answers for itself and a finding names the entry. Entries of a folder a domain split into describe that one domain between them, so any sibling answers and a finding names the folder. Holding a split folder to the rule per file would report every child beside its `overview.md`, and rolling the named folder up would let one conforming entry stand in for every other domain sitting next to it. A wireframe answers for itself in every folder, nested or not, since its standard keeps one surface per file and a subfolder of wireframes groups surfaces rather than splitting one.
|
|
15
15
|
|
|
16
|
-
It reports rather than gates by default, the closer call because a missing section reads more like a fact than the other judgments. What settles it is that the standard sanctions omitting `## Layout` from a domain owning no paths in the repo, and no measure separates that from an entry that forgot it. A domain covering only external tools is that case, and it reports on every run. The JSON record carries the findings as `missingSections` and the list as `checkpoints.requiredSections
|
|
16
|
+
It reports rather than gates by default, the closer call because a missing section reads more like a fact than the other judgments. What settles it is that the standard sanctions omitting `## Layout` from a domain owning no paths in the repo, and no measure separates that from an entry that forgot it. A domain covering only external tools is that case, and it reports on every run. The JSON record carries the findings as `missingSections`, the lists per folder name as `checkpoints.requiredSectionsByFolder`, and the context list alone as `checkpoints.requiredSections`, the flat key it carried before wireframes owed a section.
|
|
17
17
|
|
|
18
18
|
`--gate` promotes the finding to a failing exit code, which the seed stage runs and no other caller does. That mode needs an answer to the sanctioned omission above, so a file declaring `stub: true` in its frontmatter is dropped before the check and reported nowhere. Both are described in `context-audit.md`.
|
|
19
19
|
|
|
@@ -127,11 +127,11 @@ All three wireframe findings are printed under a bare run. The states-mismatch f
|
|
|
127
127
|
|
|
128
128
|
## Which folders each check reaches
|
|
129
129
|
|
|
130
|
-
The provenance
|
|
130
|
+
The provenance and narration checks cover `canon/context/` alone, the reference-form check covers the split folders inside it, the required-section check covers `canon/context/` and `canon/wireframes/`, length and the table finding reach every audited folder, and the wireframe states check covers `canon/wireframes/` alone.
|
|
131
131
|
|
|
132
|
-
What narrows the
|
|
132
|
+
What narrows the first two is stated in `standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere, and a diagram entry carries a heading per kind rather than a run of bullets deciding anything. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
|
|
133
133
|
|
|
134
|
-
Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason:
|
|
134
|
+
Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: each list is one standard's own, so it reaches the folder that standard governs. The context and wireframe standards each state a set, and the diagram standard states none.
|
|
135
135
|
|
|
136
136
|
The same test is what moved depth and bullet weight out of this command entirely. A rule stated at the attribute tier reaches every markdown file, and a check reaching every markdown file has no reason to require a folder that resolves.
|
|
137
137
|
|
|
@@ -88,7 +88,7 @@ The pattern spells both record-root prefixes, so a citation into a folder that h
|
|
|
88
88
|
|
|
89
89
|
## The wireframe states gate
|
|
90
90
|
|
|
91
|
-
Every entry under `canon/wireframes/` carrying a `## States` table is checked one-to-one against its evidence folders, matching the table's `State` and `Evidence` columns by header text rather than position, so a project reordering the template's own columns still resolves. An entry with no such table
|
|
91
|
+
Every entry under `canon/wireframes/` carrying a `## States` table is checked one-to-one against its evidence folders, matching the table's `State` and `Evidence` columns by header text rather than position, so a project reordering the template's own columns still resolves. An entry with no such table has no rows to match, and the required-section check reports it as short of `## States` instead, so a project on an older wireframe shape is told what it lacks rather than read as clean.
|
|
92
92
|
|
|
93
93
|
An evidence cell resolves as the literal path it names, taken from the project root. A state whose cited folder does not exist is a states mismatch, and so is a folder sitting under a cited evidence root that no row names. A `not captured` cell is the one exception the standard admits, and it excuses that row from both readings. A `plaintext` sketch still carried once a state's evidence exists on disk is a third finding this check adds, reading the entry as a whole rather than one row.
|
|
94
94
|
|
|
@@ -96,4 +96,4 @@ Sweep mode's findings carry a `heading` field alongside `file` and use the sweep
|
|
|
96
96
|
| `0` | the run completed, whatever the findings say |
|
|
97
97
|
| `1` | refused: a bad range, an unreadable file, or a malformed flag |
|
|
98
98
|
|
|
99
|
-
Findings never set a failing exit code, in either mode. Diff mode refuses on a git range it cannot resolve. Both modes refuse on a file the run could not read.
|
|
99
|
+
Findings never set a failing exit code, in either mode. `canon audits run` carries sweep mode as its placement entry for the same reason, pinned to `--backend off` so the tally it retains is the regex layer's on every machine. `docs/agents/audits.md` states why. Diff mode refuses on a git range it cannot resolve. Both modes refuse on a file the run could not read.
|
package/docs/agents/index.md
CHANGED
|
@@ -40,7 +40,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
40
40
|
- [Rule citations](rule-citations.md): Resolving every path a governance rule cites and every frontmatter glob the internal corpus declares, the three forms a citation is written in, the shapes that look like citations and are not, the two classes where an absent path is correct, why the glob half reads one corpus, and why this one gates
|
|
41
41
|
- [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
|
|
42
42
|
- [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
|
|
43
|
-
- [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field,
|
|
43
|
+
- [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, what each session surface can see, and moving a session to another machine with export and import
|
|
44
44
|
- [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
|
|
45
45
|
- [Citation reach](skills-reach.md): Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
|
|
46
46
|
- [Standard success criteria](standards-audit.md): Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
|
package/docs/agents/sandbox.md
CHANGED
|
@@ -39,7 +39,7 @@ The verdict `state` is `pass`, `fail`, or `unchecked`. An arm with no `expect.to
|
|
|
39
39
|
|
|
40
40
|
Omitting `--writes`, `--escapes`, or `--envelope` does not silently drop the assertion kinds that need them. Write scope, escape scope, the turn ceiling, and the reply assertion report as unchecked and appear in the count, so the standalone command cannot claim more coverage than it had. Supplying `--escapes` without `--escapes-watched` reports the same way: a zero-escape result with no root confirmed watched is unmeasured rather than a pass. A verdict never reports `pass` with zero assertions.
|
|
41
41
|
|
|
42
|
-
An arm declaring `escape_scope` asserts a bound on `run.sh`'s own escape watch rather than on the sandbox tree. `write_scope` skips when the run wrote nothing, since a required output missing is itself a finding, but `escape_scope` passes on zero escapes outright, since a clean run producing none is the expected outcome for a destination nothing requires a skill to touch, provided `--escapes-watched` confirms a root held something to watch. Declaring `escape_scope = []` asserts that a correct run reaches none of the watched destinations at all. `canon/context/sandbox/
|
|
42
|
+
An arm declaring `escape_scope` asserts a bound on `run.sh`'s own escape watch rather than on the sandbox tree. `write_scope` skips when the run wrote nothing, since a required output missing is itself a finding, but `escape_scope` passes on zero escapes outright, since a clean run producing none is the expected outcome for a destination nothing requires a skill to touch, provided `--escapes-watched` confirms a root held something to watch. Declaring `escape_scope = []` asserts that a correct run reaches none of the watched destinations at all. `canon/context/sandbox/isolation.md` names what the watch reaches and what it cannot.
|
|
43
43
|
|
|
44
44
|
An envelope that parses but carries no `result` field skips the reply assertion the same way an absent file does. An envelope carrying an empty `result` fails it, since a run that returned no text is a finding rather than a gap in the input.
|
|
45
45
|
|
package/docs/agents/sessions.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Sessions
|
|
3
|
-
description: Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field,
|
|
3
|
+
description: Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, what each session surface can see, and moving a session to another machine with export and import
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sessions
|
|
@@ -117,6 +117,89 @@ Every row carries `statusUpdatedAt`, the stamp a client writes beside `status` a
|
|
|
117
117
|
|
|
118
118
|
The dwell is what separates a status that resolves on its own from one that does not. `busy` and `idle` transition without help, so a long dwell there is ordinary. `waiting` does not: a session in that state is blocked on something outside itself, and a dwell that keeps growing past the ordinary span of a prompt is a session stalled rather than paused. `canon sessions list` renders the dwell beside the status at the coarsest unit that keeps it a whole number, and the JSON record carries both fields on every row.
|
|
119
119
|
|
|
120
|
+
## Export
|
|
121
|
+
|
|
122
|
+
`canon sessions export <id>` packs one session into a single gzip tarball so it can resume on another machine. The bundle holds the transcript, its side folder with every path kept, and a manifest.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
canon sessions export <id>
|
|
126
|
+
canon sessions export <id> --out ~/session.tar.gz --json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Option | Behavior |
|
|
130
|
+
| -------------- | ---------------------------------------------------------- |
|
|
131
|
+
| `--out <path>` | Write the bundle here rather than under the scratch folder |
|
|
132
|
+
| `--json` | Add a machine-readable record on stdout |
|
|
133
|
+
|
|
134
|
+
Without `--out` the bundle lands at `.canon/tmp/session-export/<id>.tar.gz` under the main worktree root, which is gitignored and reclaimed by `canon records prune-tmp`. It writes locally and never uploads. A transcript holds whatever tool output passed through the session, credentials included where one was printed, so treat the bundle as private.
|
|
135
|
+
|
|
136
|
+
Exit codes: `0` the bundle was written, `1` refused. The refusal carries a `reason` of `not-found`, where no project folder holds a transcript for the id, or `no-archive`, where the running Bun predates `Bun.Archive`.
|
|
137
|
+
|
|
138
|
+
The lookup walks every folder under the configuration directory's `projects/`, so a transcript filed under any encoding is found. The transcript is read up to its last newline and never rewritten, so a session exporting itself mid-append drops at most the half line it was writing.
|
|
139
|
+
|
|
140
|
+
Export runs no skill. Write a handoff with `canon:session-compact` and push it with `canon records push` first where one is wanted. The manifest reports whether a handoff exists and whether it reached the records history, and whatever calls the verb owns that order.
|
|
141
|
+
|
|
142
|
+
### The manifest
|
|
143
|
+
|
|
144
|
+
| Field | Holds |
|
|
145
|
+
| ------------------ | ---------------------------------------------------------------------------- |
|
|
146
|
+
| `format` | The bundle format version, `1` |
|
|
147
|
+
| `sessionId` | The exported id |
|
|
148
|
+
| `title` | The last custom title the transcript recorded, or `null` |
|
|
149
|
+
| `cwd` | The last working directory the transcript recorded, or `null` |
|
|
150
|
+
| `root` | The main worktree root of the repository the export read |
|
|
151
|
+
| `origin` | The `origin` remote reduced to `host/path`, or `null` |
|
|
152
|
+
| `branch`, `head` | The branch and commit of the checkout the export ran in |
|
|
153
|
+
| `recordsHead` | The records history `HEAD`, or `null` where no records history exists |
|
|
154
|
+
| `compacted` | Whether the transcript carries a compaction boundary |
|
|
155
|
+
| `live` | Whether the id was live in the roster at export |
|
|
156
|
+
| `handoff` | The newest `.canon/compact/` note written since the session began, or `null` |
|
|
157
|
+
| `handoffCommitted` | Whether that note is committed in the records history |
|
|
158
|
+
| `recommend` | `handoff` where a handoff exists, `transcript` otherwise |
|
|
159
|
+
|
|
160
|
+
`recommend` prefers the handoff even though the transcript resumes in full. A resumed transcript carries the source machine's absolute paths into a session working somewhere else, and a handoff states the work without them.
|
|
161
|
+
|
|
162
|
+
## Import
|
|
163
|
+
|
|
164
|
+
`canon sessions import <bundle>` places an exported session under this machine's configuration directory so `claude` can resume it.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
canon sessions import session.tar.gz
|
|
168
|
+
canon sessions import session.tar.gz --root ~/repos/canon --json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Option | Behavior |
|
|
172
|
+
| --------------- | ---------------------------------------------------------- |
|
|
173
|
+
| `--root <path>` | The repository to resume in. Defaults to the main worktree |
|
|
174
|
+
| `--json` | Add a machine-readable record on stdout |
|
|
175
|
+
|
|
176
|
+
The transcript and side folder land under `projects/<encoding>/`, where the encoding is the root with every character outside `[A-Za-z0-9]` turned into a dash. Import writes new files only and never overwrites.
|
|
177
|
+
|
|
178
|
+
Exit codes: `0` the session was placed, `1` refused. The refusal carries one of these reasons:
|
|
179
|
+
|
|
180
|
+
- `not-a-bundle`: the file is no gzip tarball, carries no manifest at format `1`, or names a path outside its own session, such as one climbing out with `..` or an absolute one
|
|
181
|
+
- `other-repository`: the local `origin` differs from the one the bundle recorded
|
|
182
|
+
- `exists`: the target transcript or side folder is already there, or any other project folder already holds a transcript for the id, which `--resume <id>` could reach first
|
|
183
|
+
- `live-here`: the id is live in the local roster, so a second copy would fork it
|
|
184
|
+
- `write-failed`: a write failed partway for a reason other than a copy appearing, such as a bundle carrying a file where it also needs a folder
|
|
185
|
+
- `no-archive`: the running Bun predates `Bun.Archive`
|
|
186
|
+
|
|
187
|
+
A write that fails partway removes every file this run placed and every folder it created before refusing, so a retry of the same bundle does not refuse on its own leftovers. Any path it could not remove is named in the message. A copy that appears between the existence check and the write refuses as `exists` on the same terms.
|
|
188
|
+
|
|
189
|
+
A placed session can still lag the source. The record's `behind` list carries `repository-behind` when this checkout lacks the exported `HEAD`, and `records-behind` when the records history lacks the exported records commit, which `canon records pull` brings. Neither refuses, since each has a remedy the reader runs before resuming.
|
|
190
|
+
|
|
191
|
+
On success the record carries two `routes`. One runs `claude --resume <id>` from the root, and the other starts `claude` there and runs `/canon:session-resume` bare against the handoff.
|
|
192
|
+
|
|
193
|
+
### What resume depends on
|
|
194
|
+
|
|
195
|
+
Measured on `claude` 2.1.281 on 2026-09-24, `--resume <id>` found a copied transcript in any folder under `projects/` and chained the next turn onto its full history, run from a repository path the transcript never recorded. `--continue` found it only under the local encoding, which is why import derives the folder rather than keeping the source machine's.
|
|
196
|
+
|
|
197
|
+
The encoding is inferred from observed folders rather than read from a published rule, and how a client shortens a path past its length limit was never observed. A client changing the rule leaves `--continue` looking elsewhere while `--resume <id>` still works. The framed report names the derived folder so a reader can compare.
|
|
198
|
+
|
|
199
|
+
The side folder did not change the conversation sent. What it carries is `tool-results/`, which a `persisted-output` pointer in the transcript names by the source machine's absolute path, and `subagents/`, which holds background agent transcripts. On a machine whose path matches, those pointers resolve. On one whose path differs, they dangle.
|
|
200
|
+
|
|
201
|
+
Resuming on the target while the source session keeps running forks the conversation, and nothing detects that across machines. The manifest's `live` field and the printed routes are the only warning.
|
|
202
|
+
|
|
120
203
|
## What the read depends on
|
|
121
204
|
|
|
122
205
|
The records live under the Claude Code configuration directory, which the verb resolves from `CLAUDE_CONFIG_DIR` and falls back to `~/.claude`. Their location, their filenames, and the fields inside them are a client implementation detail rather than a published interface, so a client change can move them. The verb reports an absent registry as a refusal rather than as a machine running no sessions, which is what surfaces the move instead of burying it in an empty roster.
|
package/docs/agents/teach.md
CHANGED
|
@@ -141,7 +141,7 @@ The order is drawn here rather than instructed, and that is the point of the ver
|
|
|
141
141
|
|
|
142
142
|
## Render
|
|
143
143
|
|
|
144
|
-
`canon teach render` renders a lesson body's structural blocks to HTML, through the
|
|
144
|
+
`canon teach render` renders a lesson body's structural blocks to HTML, through the four lesson components. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
147
|
echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
|
|
@@ -151,9 +151,21 @@ echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach re
|
|
|
151
151
|
| -------- | ---------------------------------------- |
|
|
152
152
|
| `--json` | Emit a machine-readable record on stdout |
|
|
153
153
|
|
|
154
|
-
It reads a JSON array of blocks from stdin, each a `heading`, `paragraph`, `list`, or `raw` block, and reports `{ ok: true, html }` on `--json` or the bare rendered HTML on stdout otherwise. Content none of the
|
|
154
|
+
It reads a JSON array of blocks from stdin, each a `heading`, `paragraph`, `list`, `refs`, or `raw` block, and reports `{ ok: true, html }` on `--json` or the bare rendered HTML on stdout otherwise. Content none of the components can express takes a `raw` block, carrying its own HTML verbatim and unescaped, which is the shape the quiz and the teach-back block travel in.
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
A citation takes two shapes. A `paragraph` carries an optional `cites` array of reference numbers, rendered as `sup.cite` markers after its text. The one `refs` block carries `items` of `{ title, url?, note? }`, rendered as `ol.refs` and numbered from 1, so cite `1` links to the first item.
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
[
|
|
160
|
+
{ "type": "paragraph", "text": "North is fixed.", "cites": [1] },
|
|
161
|
+
{
|
|
162
|
+
"type": "refs",
|
|
163
|
+
"items": [{ "title": "Compass manual", "url": "https://example.com" }]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It refuses `bad-input` on empty stdin, on malformed JSON, on stdin that does not parse to an array, and on a block carrying an unrecognized `type` or a field of the wrong shape for its type, naming the block's index in the message. Citations add four refusals, each naming the offending block: a cite that resolves to no reference, `cites` on a lede paragraph, a second `refs` block, and a reference `url` that is not `http` or `https`.
|
|
157
169
|
|
|
158
170
|
## Nav
|
|
159
171
|
|
|
@@ -172,7 +184,9 @@ canon teach nav regular-expressions --json
|
|
|
172
184
|
|
|
173
185
|
With no topic it rewrites every workspace. The teach-root listing is always rewritten regardless, since it reports on every workspace and a scoped run leaving it stale would be a second kind of drift this verb exists to end.
|
|
174
186
|
|
|
175
|
-
A lesson carries its chrome as four marker pairs the authoring skill writes empty: `canon:teach:style`, `canon:teach:header`, `canon:teach:footnav`, and `canon:teach:scripts`. This verb splices each one from the current workspace state, embedding the toolkit's favicon, the shared stylesheet, and the script that settles the course panel before the page paints, rebuilding the course sidebar, the breadcrumb and jump menus, the prev/next footer nav, and the behavior scripts. It leaves the authored `<h1>`, lede, body, and quiz between the header and the footnav as written,
|
|
187
|
+
A lesson carries its chrome as four marker pairs the authoring skill writes empty: `canon:teach:style`, `canon:teach:header`, `canon:teach:footnav`, and `canon:teach:scripts`. This verb splices each one from the current workspace state, embedding the toolkit's favicon, the shared stylesheet, and the script that settles the course panel before the page paints, rebuilding the course sidebar, the breadcrumb and jump menus, the prev/next footer nav, and the behavior scripts. It leaves the authored `<h1>`, lede, body, and quiz between the header and the footnav as written, with two exceptions. It wraps them in `<main>` when the lesson holds none, since the column width hangs off that element, and a lesson that already carries a `<main>` keeps its own.
|
|
188
|
+
|
|
189
|
+
It also links each "lesson NNNN" mention in that region to the lesson file in the same workspace carrying that number, skipping text inside `a`, `code`, `pre`, `script`, `style`, `label`, `summary`, and comments. A link the verb writes carries `data-lesson-ref`, and it is the only kind the verb rewrites: every run points one at the current filename for its number, or unwraps it to plain text once that number names no lesson. A link an author wrote is never touched.
|
|
176
190
|
|
|
177
191
|
Every workspace label in that chrome, being the breadcrumb, the sidebar heading, the workspace switcher, and the root listing's row, reads the mission's `title` and falls back to the folder topic in sentence case where the mission names none.
|
|
178
192
|
|
|
@@ -184,6 +198,8 @@ That seeding fires on the absent file alone, so a workspace already holding a `c
|
|
|
184
198
|
|
|
185
199
|
A lesson missing one of the four marker pairs is refused by name rather than rewritten, and every other lesson in the run still rewrites. The record's `skipped` list carries the refused files and which marker each is missing.
|
|
186
200
|
|
|
201
|
+
A mention whose number names no lesson in its workspace stays plain text and lands in the record's `unresolved` list, one `{ file, lesson }` entry per distinct number per lesson, with `file` relative to the root the way a `skipped` entry is. The lesson still rewrites and counts toward `lessons`, and the exit stays 0. A mention of the lesson's own number stays plain and is not reported, and a refused lesson reports nothing.
|
|
202
|
+
|
|
187
203
|
The `style` region carries the quiz stepper after the workspace stylesheet, for every lesson but the one shape named three paragraphs down, so it wins the cascade at equal specificity and reaches a workspace seeded before it existed. The stepper is CSS over the radio inputs a quiz is written from: it hides every question that follows one holding no checked input, and hides a question's feedback until that question does. The general sibling combinator is what keeps that from depending on the markup contract holding, since it selects the same questions as the adjacent one for a quiz written correctly and goes on gating when anything sits between two of them, where the adjacent one matches nothing and shows them all at once. Gating there rather than in a script is what keeps a later stem from answering an earlier question with nothing to bind, and the whole block sits inside `@supports selector(:has(*))`, so an engine without `:has()` renders every question rather than a stepper showing nothing. Feedback there follows the workspace stylesheet, shown by one that says nothing about `.fb` and hidden by one grown under the retired script.
|
|
188
204
|
|
|
189
205
|
The block hides and shows and sets nothing else, which leaves a workspace's own layout for a question and its own appearance for an option and a feedback panel. The one value it names is `display: block` on revealed feedback, since every stylesheet grown under the retired script carries `.fb { display: none }` and nothing adds the class that used to reveal it.
|
|
@@ -192,6 +208,12 @@ One detector decides both regions. A lesson carrying `<button class="opt"`, the
|
|
|
192
208
|
|
|
193
209
|
Splicing the stepper into a lesson of either shape was measured against a browser and reverted. Its rules match a button question, which can never hold a checked radio, so a lesson given both hides every question past the first for good and outranks the class the script reveals feedback with. Nothing else caught that: the markup is valid, the splice succeeds, and the page renders.
|
|
194
210
|
|
|
211
|
+
### Reference pages
|
|
212
|
+
|
|
213
|
+
Each reference page is rendered to a `reference/<slug>.html` sibling carrying the same chrome, and the contents page links that sibling rather than the markdown, which `canon serve` hands over as plain text. The render drops the frontmatter, turns tables into `<table>` markup, and escapes raw HTML written in the markdown rather than passing it through. A body with no H1 gets one from the frontmatter `title`. A relative link to another reference page's markdown points at that page's `.html` sibling, and one to the workspace `GLOSSARY.md` points at the glossary on the contents page, `../index.html#gloss`. Every other link, such as `../RESOURCES.md`, stays as written. The markdown file is never touched.
|
|
214
|
+
|
|
215
|
+
Every rendered page carries a `<meta name="generator" content="canon teach nav">` tag. A `.html` file under `reference/` whose markdown is gone is deleted when it carries that tag, and a hand-written HTML file there, carrying none, is left alone. The record's `reference` field counts the pages rendered. On a Bun with no `Bun.markdown`, each reference page lands in `skipped` with `Bun.markdown` as what is missing, and the rest of the run goes on.
|
|
216
|
+
|
|
195
217
|
## Opening a workspace
|
|
196
218
|
|
|
197
219
|
No `canon teach` verb serves the workspace. `canon serve` does, taking the teach root as its directory and the workspace contents page as its entry:
|
package/docs/target-projects.md
CHANGED
|
@@ -86,13 +86,15 @@ Each diagram entry records the commit and date it was last verified against, and
|
|
|
86
86
|
|
|
87
87
|
The default path is `base`. `canon init` on `base` installs base tooling configs, Claude seeds, and governance core rules, and scaffolds an empty `.claude/wiki/`. Most projects need nothing more.
|
|
88
88
|
|
|
89
|
-
Escalate only for real web apps. The `target-setup` skill reads `package.json` and
|
|
89
|
+
Escalate only for real web apps. The `target-setup` skill reads `package.json` and the framework configs, then picks the matching tooling stack (`canon tooling list --json` names the current set) and the matching governance stack (`react`, `nextjs`, `astro`, `node`). A Next.js app lands on `nextjs`, which extends `react` with the App Router rule a Vite React app does not get.
|
|
90
|
+
|
|
91
|
+
In a monorepo the skill reads each folder holding a `package.json`, `pyproject.toml`, `go.mod`, `composer.json`, or `Cargo.toml` to depth two, skipping what git ignores. Each of those folders gets its own tooling stack and a `canon tooling sync <stack> <folder> --skip base` in the preview. Governance still installs once at the root, since a rule's `paths:` globs reach every subfolder, so a subfolder's language rules join the root install through `--add`.
|
|
90
92
|
|
|
91
93
|
`node-server` is named rather than detected. It carries the server-side security and persistence rules for a project writing request handlers or a persistence layer in TypeScript, and the detect step matches a runtime or a framework against stack names, so nothing there marks a project as a backend. Pass it deliberately with `canon init --stack node-server` or `canon gov install node-server <target>`.
|
|
92
94
|
|
|
93
95
|
Markdown-heavy projects, CLI tools, docs sites, research notebooks, and scripting repos stay on `base`. Escalation is a ceiling move, not a default.
|
|
94
96
|
|
|
95
|
-
A project the toolkit ships no stack for lands on `base` the same way, and the skill marks that resolution as a fallback in its preview rather than reporting it as a match. Configs, seeds, and gitignore entries land either way, and the JavaScript development dependencies, scripts, and hook activation land only where a `package.json` exists to carry them. A project outside that ecosystem declines at the preview and takes `canon:target-setup gov` for the governance layer alone, which is language-neutral. Go and PHP
|
|
97
|
+
A project the toolkit ships no stack for lands on `base` the same way, and the skill marks that resolution as a fallback in its preview rather than reporting it as a match. Configs, seeds, and gitignore entries land either way, and the JavaScript development dependencies, scripts, and hook activation land only where a `package.json` exists to carry them. A project outside that ecosystem declines at the preview and takes `canon:target-setup gov` for the governance layer alone, which is language-neutral. Go and PHP ship tooling stacks but no governance stack, so a project in either takes the `go` or `php` tooling stack, lands on `base` for governance, and picks up its language and testing rules through `--add`, for example `canon gov install base --add 130-go,335-testing-go .`
|
|
96
98
|
|
|
97
99
|
Run `canon tooling list --json` and `canon gov list --json` to see the current catalogs. Never hardcode stack names.
|
|
98
100
|
|