@erclx/canon 4.119.0 → 4.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/git-pr/REQUIREMENT.md +1 -0
- package/claude/skills/git-pr/SKILL.md +34 -4
- package/claude/skills/review-pr/SKILL.md +13 -242
- package/claude/skills/review-pr/references/close-out.md +30 -0
- package/claude/skills/review-pr/references/post.md +145 -0
- package/claude/skills/review-pr/references/scope.md +82 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +9 -16
- package/claude/skills/role-orchestrator/SKILL.md +9 -153
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +4 -193
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +22 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +85 -0
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +61 -0
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +56 -0
- package/claude/skills/role-orchestrator/references/orchestrator-review-fallback.md +18 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +1 -1
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/docs/agents/commands.md +2 -1
- package/docs/agents/counts.md +1 -1
- package/docs/agents/index.md +8 -3
- package/docs/agents/pr-evidence.md +19 -3
- package/docs/agents/pr-local.md +102 -0
- package/docs/agents/pr-preview.md +2 -2
- package/docs/agents/records-push.md +61 -0
- package/docs/agents/records-validate.md +73 -0
- package/docs/agents/records.md +3 -119
- package/docs/agents/sessions-transfer.md +91 -0
- package/docs/agents/sessions.md +3 -82
- package/docs/agents/skills-audit.md +2 -2
- package/docs/agents/teach-authoring.md +112 -0
- package/docs/agents/teach.md +3 -103
- package/docs/target-projects.md +1 -1
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/visual-design-workflow.md +1 -1
- package/governance/rules/claude/570-skill.md +2 -1
- package/package.json +1 -1
- package/scripts/core/check-ignore-parity.sh +1 -1
- package/scripts/lib/sandbox-git.sh +1 -1
- package/src/commands/pr.ts +324 -2
- package/src/context/classify/extract.ts +2 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/stages.ts +2 -2
- package/src/pr/evidence.ts +42 -2
- package/src/pr/local.ts +125 -0
- package/standards/index.md +1 -0
- package/standards/skill-requirement.md +72 -0
- package/standards/skill.md +34 -152
- package/tooling/astro/reference.md +1 -1
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +32 -0
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, what each session surface can see, and moving a session to another machine
|
|
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 where moving a session to another machine is described
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sessions
|
|
@@ -117,88 +117,9 @@ 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
|
|
120
|
+
## Export and import
|
|
121
121
|
|
|
122
|
-
`canon sessions export <id>` packs one session into a
|
|
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.
|
|
122
|
+
`canon sessions export <id>` packs one session into a bundle and `canon sessions import <bundle>` places it on another machine. Both are in `sessions-transfer.md`.
|
|
202
123
|
|
|
203
124
|
## What the read depends on
|
|
204
125
|
|
|
@@ -5,7 +5,7 @@ description: Measuring both skill corpora against standards/skill.md, the checks
|
|
|
5
5
|
|
|
6
6
|
# Skill audit
|
|
7
7
|
|
|
8
|
-
`canon claude skills audit [path]` reports both skill corpora against the rules `standards/skill.md`
|
|
8
|
+
`canon claude skills audit [path]` reports both skill corpora against the rules `standards/skill.md` and `standards/skill-requirement.md` state mechanically. It reads and reports. Fixing what it finds is separate work.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
canon claude skills audit
|
|
@@ -45,7 +45,7 @@ Each finding names the file and the line, such as `claude/skills/<name>/referenc
|
|
|
45
45
|
|
|
46
46
|
## What it leaves alone
|
|
47
47
|
|
|
48
|
-
The report names its own blind spots on every run, including the run where everything passed. Whether each `Must` traces to a stated gap is the rule in
|
|
48
|
+
The report names its own blind spots on every run, including the run where everything passed. Whether each `Must` traces to a stated gap is the rule in the requirement standard worth the most and no parser reads it. Whether a gap states an observed failure rather than an intent, and whether a description routes, are the same kind of judgment. The 150-line body checkpoint is the one mechanical rule still absent here, and adding it would print a count rather than a defect, since the standard makes it a prompt to look with nothing enforcing it.
|
|
49
49
|
|
|
50
50
|
The date check reads ISO dates alone. A date written in words, a month with no day, and provenance carrying no date at all pass it.
|
|
51
51
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Teach authoring
|
|
3
|
+
description: Resolving what the next lesson needs before it is written, rendering a lesson body's blocks to HTML, and rewriting the root listing, a contents page, and each lesson's chrome and quiz stepper from its marker regions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Teach authoring
|
|
7
|
+
|
|
8
|
+
The verbs a session runs while writing a lesson. Workspace resolution, the verbs that open and record into a workspace, and the refusal reasons they share are in `teach.md`.
|
|
9
|
+
|
|
10
|
+
## Lesson
|
|
11
|
+
|
|
12
|
+
`canon teach lesson` resolves what the next lesson needs before it is written. It reads and never writes.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
canon teach lesson regular-expressions \
|
|
16
|
+
--slug capture-groups \
|
|
17
|
+
--questions 3 \
|
|
18
|
+
--options 4
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Option | Behavior |
|
|
22
|
+
| ----------------- | --------------------------------------------------------- |
|
|
23
|
+
| `--slug <kebab>` | The lesson's own topic, required |
|
|
24
|
+
| `--questions <n>` | How many questions the quiz carries, required |
|
|
25
|
+
| `--options <n>` | How many options each question carries, defaulting to `4` |
|
|
26
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
27
|
+
| `--root <path>` | Teach root, defaulting to the main worktree |
|
|
28
|
+
|
|
29
|
+
It reports three things. `lesson` is the numbered path the lesson takes, derived from the highest ordinal already in `lessons/` the way an open derives a workspace ordinal. `success` carries the mission's success lines, so a session reports progress against the exit criteria without a second read of `MISSION.md`. `quiz` carries one entry per question.
|
|
30
|
+
|
|
31
|
+
A lesson written against this record carries its chrome as four empty marker pairs rather than hand-composed markup: `canon:teach:style`, `canon:teach:header`, `canon:teach:footnav`, and `canon:teach:scripts`. `canon teach nav`, below, fills them from what the workspace holds on disk.
|
|
32
|
+
|
|
33
|
+
`canon teach stylesheet <topic>` writes the pair every lesson in the workspace renders through, seeded from the design source so a workspace renders in the system every other surface does. The two halves are written on different terms. `assets/base.css` holds the generated design layer and is rewritten on every run, which is the only way a change to the shared chrome reaches a workspace that already exists. `assets/course.css` is the half a workspace owns, so it refuses to overwrite, reporting `written` as false where the workspace already carries one, and `--force` takes the seed back over it. Each workspace used to carry a hand-authored palette, which is how the course styling forked once per workspace, so a lesson adds its own rules under the seed and reaches a value through its custom property rather than restating the hex.
|
|
34
|
+
|
|
35
|
+
Each `quiz` entry carries `order`, the authored option indices in presentation order where index `0` is the correct answer, and `answer`, the one-based position that answer lands in. Both travel together because a caller deriving the position itself is a caller that can derive it wrongly.
|
|
36
|
+
|
|
37
|
+
The order is drawn here rather than instructed, and that is the point of the verb. An author told to vary the position still varies it by judgment, and the judgment settles on the first slot, which is the defect this design departs from. The draw is uniform over the options, so the position carries no information about which answer is correct.
|
|
38
|
+
|
|
39
|
+
## Render
|
|
40
|
+
|
|
41
|
+
`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.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Option | Behavior |
|
|
48
|
+
| -------- | ---------------------------------------- |
|
|
49
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
[
|
|
57
|
+
{ "type": "paragraph", "text": "North is fixed.", "cites": [1] },
|
|
58
|
+
{
|
|
59
|
+
"type": "refs",
|
|
60
|
+
"items": [{ "title": "Compass manual", "url": "https://example.com" }]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
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`.
|
|
66
|
+
|
|
67
|
+
## Nav
|
|
68
|
+
|
|
69
|
+
`canon teach nav` rewrites the teach-root listing, a workspace's contents page, and each of its lessons' chrome, from what the workspace holds on disk.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
canon teach nav
|
|
73
|
+
canon teach nav regular-expressions --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Option | Behavior |
|
|
77
|
+
| --------------- | ------------------------------------------------- |
|
|
78
|
+
| `[topic]` | Workspace folder or topic, scoping the run to one |
|
|
79
|
+
| `--json` | Emit a machine-readable record on stdout |
|
|
80
|
+
| `--root <path>` | Teach root, defaulting to the main worktree |
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
It also deletes two links a session used to write by hand into a lesson's `<head>`, each matched as its exact string: the old icon link and `<link rel="stylesheet" href="../assets/course.css" />`. The page keeps only the icon and the stylesheets the `style` region carries. Any other link an author wrote stays.
|
|
91
|
+
|
|
92
|
+
A workspace carrying no `course.css` gets its stylesheet pair seeded first, so the contents page it links is never unstyled, and an existing `course.css` is never overwritten. The `style` region links `assets/base.css` ahead of an embedded copy of `course.css`, so the workspace's own rules still win the cascade. The embedded copy drops the seed's `@import` line, which resolved against the lesson's own folder and never loaded anything, and the link is what reaches the base sheet in its place. A workspace with no `assets/base.css`, one holding a hand-authored `course.css` from before the seed, gets no link rather than one pointing at a missing file.
|
|
93
|
+
|
|
94
|
+
That seeding fires on the absent file alone, so a workspace already holding a `course.css` has neither half rewritten here. A change to the shared chrome therefore takes `canon teach stylesheet <topic>` per workspace, which rewrites `assets/base.css`, and every lesson picks it up through its link with no run of this verb. Running this one alone leaves every lesson on the `base.css` the workspace already holds, and nothing reports it. Only an edit to a workspace's own `course.css` needs this verb, since that half is embedded.
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
One detector decides both regions. A lesson carrying `<button class="opt"`, the shape written before the stepper, takes the feedback script and no stepper, and every other lesson takes the stepper and no script. That is a detector rather than a mode, since a lesson has one shape or the other, and removing the script outright would leave the lessons already written showing no feedback at all.
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
### Reference pages
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
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.
|
package/docs/agents/teach.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Teach
|
|
3
|
-
description: Listing learning workspaces with what their records schedule next, opening one with its required files, recording sources and glossary terms,
|
|
3
|
+
description: Listing learning workspaces with what their records schedule next, opening one with its required files, recording sources and glossary terms, where the lesson authoring verbs are described, the refusal reasons, and why every write here runs through a verb
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Teach
|
|
@@ -110,109 +110,9 @@ A term already defined is refused rather than replaced. A definition the subject
|
|
|
110
110
|
|
|
111
111
|
The entry lands as the standard's shape, leading with the term as a bolded span. A definition not ending in sentence punctuation is terminated before the citation is appended, so a bare phrase does not run into the sentence naming where the term first appears.
|
|
112
112
|
|
|
113
|
-
## Lesson
|
|
113
|
+
## Lesson, render, and nav
|
|
114
114
|
|
|
115
|
-
`canon teach lesson`
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
canon teach lesson regular-expressions \
|
|
119
|
-
--slug capture-groups \
|
|
120
|
-
--questions 3 \
|
|
121
|
-
--options 4
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
| Option | Behavior |
|
|
125
|
-
| ----------------- | --------------------------------------------------------- |
|
|
126
|
-
| `--slug <kebab>` | The lesson's own topic, required |
|
|
127
|
-
| `--questions <n>` | How many questions the quiz carries, required |
|
|
128
|
-
| `--options <n>` | How many options each question carries, defaulting to `4` |
|
|
129
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
130
|
-
| `--root <path>` | Teach root, defaulting to the main worktree |
|
|
131
|
-
|
|
132
|
-
It reports three things. `lesson` is the numbered path the lesson takes, derived from the highest ordinal already in `lessons/` the way an open derives a workspace ordinal. `success` carries the mission's success lines, so a session reports progress against the exit criteria without a second read of `MISSION.md`. `quiz` carries one entry per question.
|
|
133
|
-
|
|
134
|
-
A lesson written against this record carries its chrome as four empty marker pairs rather than hand-composed markup: `canon:teach:style`, `canon:teach:header`, `canon:teach:footnav`, and `canon:teach:scripts`. `canon teach nav`, below, fills them from what the workspace holds on disk.
|
|
135
|
-
|
|
136
|
-
`canon teach stylesheet <topic>` writes the pair every lesson in the workspace renders through, seeded from the design source so a workspace renders in the system every other surface does. The two halves are written on different terms. `assets/base.css` holds the generated design layer and is rewritten on every run, which is the only way a change to the shared chrome reaches a workspace that already exists. `assets/course.css` is the half a workspace owns, so it refuses to overwrite, reporting `written` as false where the workspace already carries one, and `--force` takes the seed back over it. Each workspace used to carry a hand-authored palette, which is how the course styling forked once per workspace, so a lesson adds its own rules under the seed and reaches a value through its custom property rather than restating the hex.
|
|
137
|
-
|
|
138
|
-
Each `quiz` entry carries `order`, the authored option indices in presentation order where index `0` is the correct answer, and `answer`, the one-based position that answer lands in. Both travel together because a caller deriving the position itself is a caller that can derive it wrongly.
|
|
139
|
-
|
|
140
|
-
The order is drawn here rather than instructed, and that is the point of the verb. An author told to vary the position still varies it by judgment, and the judgment settles on the first slot, which is the defect this design departs from. The draw is uniform over the options, so the position carries no information about which answer is correct.
|
|
141
|
-
|
|
142
|
-
## Render
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
```bash
|
|
147
|
-
echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
| Option | Behavior |
|
|
151
|
-
| -------- | ---------------------------------------- |
|
|
152
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
153
|
-
|
|
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
|
-
|
|
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`.
|
|
169
|
-
|
|
170
|
-
## Nav
|
|
171
|
-
|
|
172
|
-
`canon teach nav` rewrites the teach-root listing, a workspace's contents page, and each of its lessons' chrome, from what the workspace holds on disk.
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
canon teach nav
|
|
176
|
-
canon teach nav regular-expressions --json
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
| Option | Behavior |
|
|
180
|
-
| --------------- | ------------------------------------------------- |
|
|
181
|
-
| `[topic]` | Workspace folder or topic, scoping the run to one |
|
|
182
|
-
| `--json` | Emit a machine-readable record on stdout |
|
|
183
|
-
| `--root <path>` | Teach root, defaulting to the main worktree |
|
|
184
|
-
|
|
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.
|
|
186
|
-
|
|
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.
|
|
190
|
-
|
|
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.
|
|
192
|
-
|
|
193
|
-
It also deletes two links a session used to write by hand into a lesson's `<head>`, each matched as its exact string: the old icon link and `<link rel="stylesheet" href="../assets/course.css" />`. The page keeps only the icon and the stylesheets the `style` region carries. Any other link an author wrote stays.
|
|
194
|
-
|
|
195
|
-
A workspace carrying no `course.css` gets its stylesheet pair seeded first, so the contents page it links is never unstyled, and an existing `course.css` is never overwritten. The `style` region links `assets/base.css` ahead of an embedded copy of `course.css`, so the workspace's own rules still win the cascade. The embedded copy drops the seed's `@import` line, which resolved against the lesson's own folder and never loaded anything, and the link is what reaches the base sheet in its place. A workspace with no `assets/base.css`, one holding a hand-authored `course.css` from before the seed, gets no link rather than one pointing at a missing file.
|
|
196
|
-
|
|
197
|
-
That seeding fires on the absent file alone, so a workspace already holding a `course.css` has neither half rewritten here. A change to the shared chrome therefore takes `canon teach stylesheet <topic>` per workspace, which rewrites `assets/base.css`, and every lesson picks it up through its link with no run of this verb. Running this one alone leaves every lesson on the `base.css` the workspace already holds, and nothing reports it. Only an edit to a workspace's own `course.css` needs this verb, since that half is embedded.
|
|
198
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
204
|
-
|
|
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.
|
|
206
|
-
|
|
207
|
-
One detector decides both regions. A lesson carrying `<button class="opt"`, the shape written before the stepper, takes the feedback script and no stepper, and every other lesson takes the stepper and no script. That is a detector rather than a mode, since a lesson has one shape or the other, and removing the script outright would leave the lessons already written showing no feedback at all.
|
|
208
|
-
|
|
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.
|
|
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.
|
|
115
|
+
`canon teach lesson`, `canon teach render`, and `canon teach nav` build and splice a lesson rather than editing a workspace's records. They are in `teach-authoring.md`.
|
|
216
116
|
|
|
217
117
|
## Opening a workspace
|
|
218
118
|
|
package/docs/target-projects.md
CHANGED
|
@@ -350,5 +350,5 @@ Sync also refuses a target whose working tree is dirty, so commit or stash befor
|
|
|
350
350
|
|
|
351
351
|
- [agents](agents/index.md): CLI flags, exit codes, and JSON output shapes
|
|
352
352
|
- [AI workflow](workflow/ai-workflow.md): feature-development loop inside a toolkit-managed project
|
|
353
|
-
- [tooling](../canon/context/tooling.md), [governance](../canon/context/governance/index.md), [claude plugin](../canon/context/claude-plugin/index.md), [indexes](../canon/context/indexes.md), [snippets](../canon/context/snippets.md), [standards](../canon/context/standards/index.md): per-domain mechanics
|
|
353
|
+
- [tooling](../canon/context/tooling/index.md), [governance](../canon/context/governance/index.md), [claude plugin](../canon/context/claude-plugin/index.md), [indexes](../canon/context/indexes.md), [snippets](../canon/context/snippets.md), [standards](../canon/context/standards/index.md): per-domain mechanics
|
|
354
354
|
- [sandbox](../canon/context/sandbox/index.md): scenario catalog for verifying domain flows
|
|
@@ -109,7 +109,7 @@ An execution that picks other than the suggestion rewrites the `- Suggested:` li
|
|
|
109
109
|
|
|
110
110
|
`canon records push` carries these folders off the disk they live on, and `canon records pull` brings them back. Every top-level entry under `.canon/` is backed except `tmp`, `ordinal-locks`, and `.records.git`, so a folder added later enters the payload on its own rather than waiting on a name written into a list. Push names each folder it carries the first time it sees it, which is what keeps a stray folder visible instead of silently entering the payload. It refuses the whole push, naming each path, when a new or changed file is over 25 MB or carries a credential `canon secrets scan` would report. The history lives in a second git directory at `.canon/.records.git` with `.canon/` as its work tree, so every path a task file cites stays where it is.
|
|
111
111
|
|
|
112
|
-
A person points it at a private repository once and both verbs refuse until they have, and `push` refuses when that origin is also a remote of the project, since the payload is the memory pen and the groundwork trails. `.husky/post-merge` runs the push after its archive loop, on every merge rather than only on one that closed a task. See [records](../agents/records.md) for the refusal table.
|
|
112
|
+
A person points it at a private repository once and both verbs refuse until they have, and `push` refuses when that origin is also a remote of the project, since the payload is the memory pen and the groundwork trails. `.husky/post-merge` runs the push after its archive loop, on every merge rather than only on one that closed a task. See [records push](../agents/records-push.md) for the refusal table.
|
|
113
113
|
|
|
114
114
|
`canon records size` reports what each of these folders holds, heaviest first, along with `.canon/tmp`. Each row carries the file count, the bytes, how many files were written in the last 7 and 30 days, and the dates of the least and most recently written one. Nothing fails on a number, because a record folder has no correct size. What the verb replaces is a reading somebody had to remember to take: the memory pen went from 44 entries to 236 between two counts made by hand a fortnight apart, and nothing reported the rate in between.
|
|
115
115
|
|
|
@@ -22,7 +22,7 @@ Claude Code reads both and writes the implementation. Works for CLI tools, inter
|
|
|
22
22
|
|
|
23
23
|
The toolkit seed in `tooling/claude/seeds/canon/DESIGN.md` ships a token-table template with a starting set of roles, and `standards/design.md` carries the same tables under `## Template` with placeholder rows. The column headers are what the renderer parses, so they stay verbatim in either, while the rows and values are the project's own.
|
|
24
24
|
|
|
25
|
-
The `canon:design-extract` skill drafts the file, sourcing tokens from a project's existing prose and CLI UI surfaces, or proposing them from `canon/REQUIREMENTS.md` and a `## Personality` paragraph when no UI code exists yet. `canon design render` writes an HTML plus CSS preview to `.canon/tmp/render/design/` for eyeballing the current system without leaving Claude Code. See `canon/context/design.md`.
|
|
25
|
+
The `canon:design-extract` skill drafts the file, sourcing tokens from a project's existing prose and CLI UI surfaces, or proposing them from `canon/REQUIREMENTS.md` and a `## Personality` paragraph when no UI code exists yet. `canon design render` writes an HTML plus CSS preview to `.canon/tmp/render/design/` for eyeballing the current system without leaving Claude Code. See `canon/context/design/index.md`.
|
|
26
26
|
|
|
27
27
|
A project wanting a neutral starting point to override runs `canon design install`, which copies one stylesheet to `.claude/design/base.css` carrying an achromatic token set as custom properties and two components built on them. Greys run from near-black to near-white and the accent is monochrome, so the only palette the project renders is one it writes itself. That file is toolkit-owned and `canon design sync` refreshes it, so a project overrides a value in `.claude/design/project/` instead, which sync never touches. Nothing arrives without that install, and the two channels are independent: a record drafted by the extract skill is the project's own, and the installed stylesheet is the toolkit's.
|
|
28
28
|
|
|
@@ -21,4 +21,5 @@ paths:
|
|
|
21
21
|
|
|
22
22
|
## Authority
|
|
23
23
|
|
|
24
|
-
- Follow the skill standard for skill structure, frontmatter fields, invocation rules
|
|
24
|
+
- Follow the skill standard for skill structure, frontmatter fields, and invocation rules. It is the single source. Read it with `canon standards skill`.
|
|
25
|
+
- Follow the skill requirement standard for the shape a `REQUIREMENT.md` states. Read it with `canon standards skill-requirement`.
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ SECTION="# Claude"
|
|
|
19
19
|
# The diagrams divergence is the one that cost something. A target used to track
|
|
20
20
|
# its diagrams and keep the context audit's default coverage of them, and it now
|
|
21
21
|
# ignores them with the rest of its records, so that coverage is gone unless the
|
|
22
|
-
# project names the folder itself. `canon/context/tooling.md` carries the
|
|
22
|
+
# project names the folder itself. `canon/context/tooling/manifests.md` carries the
|
|
23
23
|
# narrative.
|
|
24
24
|
#
|
|
25
25
|
# An exception mechanism is not kept against a future divergence, because an
|
|
@@ -95,7 +95,7 @@ ensure_sandbox_anchor_repo() {
|
|
|
95
95
|
# nine to a pass against something that is not the anchor. Refusing is the safe
|
|
96
96
|
# default and creating is the opt-in, the shape `canon tooling sync --write`
|
|
97
97
|
# already sets. `canon records push` refuses outright for the reason
|
|
98
|
-
# `canon/context/development/
|
|
98
|
+
# `canon/context/development/records.md` records, so the two still differ.
|
|
99
99
|
# The sibling SANDBOX_ flags are presence tests, so any non-empty value turns
|
|
100
100
|
# them on. This one allowlists instead, because a presence test would have
|
|
101
101
|
# SANDBOX_ANCHOR_CREATE=false provisioning a repository. Both spellings are
|