@erclx/canon 4.109.0 → 4.111.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.
Files changed (33) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/{canon-feedback-file → canon-feedback}/REQUIREMENT.md +7 -6
  3. package/claude/skills/{canon-feedback-file → canon-feedback}/SKILL.md +9 -19
  4. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +1 -1
  5. package/claude/skills/canon-feedback-triage/SKILL.md +1 -1
  6. package/claude/skills/canon-operator/REQUIREMENT.md +2 -2
  7. package/claude/skills/canon-operator/SKILL.md +3 -4
  8. package/claude/skills/draft-wiki/REQUIREMENT.md +42 -0
  9. package/claude/skills/draft-wiki/SKILL.md +83 -0
  10. package/claude/skills/git-issue/REQUIREMENT.md +1 -1
  11. package/claude/skills/git-issue/SKILL.md +1 -1
  12. package/claude/skills/target-check/references/domains.md +2 -2
  13. package/docs/agents/install-and-sync.md +4 -13
  14. package/docs/agents/teach.md +4 -2
  15. package/docs/target-projects.md +0 -2
  16. package/docs/workflow/ai-workflow.md +8 -11
  17. package/package.json +1 -1
  18. package/scripts/core/capture-home.sh +59 -0
  19. package/src/claude/cases/authoring.ts +5 -0
  20. package/src/claude/cases/setup.ts +3 -24
  21. package/src/commands/sync.ts +0 -16
  22. package/src/design/components.ts +459 -147
  23. package/src/shipped/references.ts +1 -2
  24. package/src/sync/reverse.ts +3 -74
  25. package/src/teach/nav.ts +410 -58
  26. package/claude/skills/migration-claude-md/REQUIREMENT.md +0 -40
  27. package/claude/skills/migration-claude-md/SKILL.md +0 -79
  28. package/claude/skills/migration-context/REQUIREMENT.md +0 -36
  29. package/claude/skills/migration-context/SKILL.md +0 -97
  30. package/claude/skills/migration-standards-drop/REQUIREMENT.md +0 -55
  31. package/claude/skills/migration-standards-drop/SKILL.md +0 -113
  32. package/claude/skills/migration-superseded/REQUIREMENT.md +0 -44
  33. package/claude/skills/migration-superseded/SKILL.md +0 -115
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.109.0",
4
+ "version": "4.111.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: canon-feedback-file
3
- description: Why a toolkit defect is reported from session context alone, and what shipping through the CLI replaces
2
+ name: canon-feedback
3
+ description: Why a toolkit defect is reported from session context alone, and what opening an issue through the CLI replaces
4
4
  ---
5
5
 
6
- # Canon feedback file requirement
6
+ # Canon feedback requirement
7
7
 
8
8
  ## Gap
9
9
 
@@ -18,8 +18,9 @@ The last failure is the session's own effort. Asked to report, a session starts
18
18
  - Build the report from what the session already holds
19
19
  - Name the surface and its type, since the toolkit routes on that rather than on the symptom
20
20
  - Write the stated fallback for a field the session cannot fill, so an empty field reads as absent rather than as unreported
21
- - Ship through the CLI so the report lands without a copy-paste, and print the returned path on its own line
22
- - Offer the durable route for a report worth keeping across sessions and machines
21
+ - Open a GitHub issue on the toolkit repo through the CLI so the report lands where triage reads it, and print the returned URL on its own line
22
+ - Say which file and which reason when the issue call fails and the report falls back to a local folder, since a silent fallback leaves it where nothing reads it
23
+ - Hold a report the operator marks sensitive, since the issue is public
23
24
  - Fall back to printing the block when the CLI is absent, so the report still exists
24
25
 
25
26
  ## Must not
@@ -36,5 +37,5 @@ The last failure is the session's own effort. Asked to report, a session starts
36
37
 
37
38
  - Fixing the defect, which happens in the toolkit repository against its own source
38
39
  - Draining the queue this fills: `canon-feedback-triage`
39
- - Filing an issue against the current project: `git-issue`
40
+ - Filing an issue against the current project, which `git-issue` does and this skill cannot delegate to, since that skill targets the current repository
40
41
  - Complaints about tooling the toolkit does not own
@@ -1,11 +1,11 @@
1
1
  ---
2
- name: canon-feedback-file
3
- description: Format a paste-back report about something broken, missing, or off in canon and write it directly to the toolkit's `.canon/feedback/` folder via `canon feedback`. Use when asked to "send this to the toolkit", "report this to canon", "file toolkit feedback", or "give the toolkit feedback about X". Do NOT use for general complaints about other tooling, IDE issues, or in-project bugs that do not implicate canon surfaces.
2
+ name: canon-feedback
3
+ description: Format a report about something broken, missing, or off in canon and open it as a GitHub issue on the toolkit repo via `canon feedback --github`, where the toolkit's triage reads it. Use when asked to "send this to the toolkit", "report this to canon", "file toolkit feedback", or "give the toolkit feedback about X". Do NOT use for general complaints about other tooling, IDE issues, or in-project bugs that do not implicate canon surfaces.
4
4
  ---
5
5
 
6
- # Canon feedback file
6
+ # Canon feedback
7
7
 
8
- Format a `## Toolkit feedback` block from the current session, then ship it to the toolkit repo without manual copy-paste.
8
+ Format a `## Toolkit feedback` block from the current session, then open it as a GitHub issue labeled `feedback` on the toolkit repo. `canon-feedback-triage` reads that queue.
9
9
 
10
10
  ## Guards
11
11
 
@@ -69,9 +69,9 @@ Detect whether `canon` is on PATH:
69
69
  command -v canon >/dev/null 2>&1
70
70
  ```
71
71
 
72
- Before either pipe below, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the composed block. `.canon/review/` is gitignored, so no hook reaches either write, and this scan is the only gate. The banned-character half runs on both branches, including the local write. The phase-label half applies only on the `--github` branch, where the block reaches a remote.
72
+ The destination is a public GitHub issue on the toolkit repo, so the report leaves the machine. Hold it and ask when the operator has marked it sensitive, and never send it on a guess.
73
73
 
74
- Local scratch only writes from a toolkit checkout, since `canon feedback` resolves the destination against the toolkit's own `.claude/` folder and a target project never carries one. Pipe the block to `canon feedback --github` first.
74
+ Before the pipe below, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the composed block, phase-label half included. Nothing else checks what reaches the remote, so this scan is the only gate.
75
75
 
76
76
  ```bash
77
77
  cat <<'EOF' | canon feedback --github
@@ -81,19 +81,9 @@ cat <<'EOF' | canon feedback --github
81
81
  EOF
82
82
  ```
83
83
 
84
- The CLI opens a GitHub issue on the toolkit repo and prints the issue URL. It needs `gh` authenticated, and falls back to local scratch with the reason on stderr when the call produces no URL and the session holds a toolkit checkout to fall back onto.
84
+ The CLI opens a GitHub issue labeled `feedback` on the toolkit repo and prints the issue URL, which this skill reports back on its own line. It needs `gh` authenticated. `git-issue` is not the route, since it files on the current repository and this report belongs on the toolkit's.
85
85
 
86
- From a toolkit checkout, plain `canon feedback` reaches the same destination without opening an issue:
87
-
88
- ```bash
89
- cat <<'EOF' | canon feedback
90
- ## Toolkit feedback
91
-
92
- ...
93
- EOF
94
- ```
95
-
96
- It writes to `.canon/feedback/feedback-<slug>-<ts>.md` and prints the absolute path on stdout. Report the printed path back to the user on its own line, in the form the project's instruction file sets under `## Output`.
86
+ From a toolkit checkout, a call that produces no URL falls back to `.canon/feedback/feedback-<slug>-<ts>.md` and prints the reason on stderr. Say so in the same reply, naming the file and the reason, since a silent fallback leaves the report where nothing reads it. A target project has no such folder, so a failed call there prints the block in chat.
97
87
 
98
88
  If `canon` is not on PATH, fall back: print the block in chat and tell the user `📋 Copy the block above into a toolkit-repo session.`
99
89
 
@@ -109,4 +99,4 @@ A `--github` run from a toolkit checkout whose GitHub call fails warns and write
109
99
  ## Notes
110
100
 
111
101
  - `canon feedback` resolves the toolkit root from the running `canon` binary's source location. If multiple toolkit clones exist on the machine, the first `canon` on PATH wins.
112
- - The destination `.canon/review/` is gitignored in the toolkit repo. Feedback lives as session scratch for the next toolkit-side triage, not as a durable archive.
102
+ - The local folder is a fallback for a failed issue call, and nothing reads it on a schedule. The issue is the durable record.
@@ -35,6 +35,6 @@ The queue also fails to drain even when the work ships. A fix merged with no lin
35
35
 
36
36
  ## Out of scope
37
37
 
38
- - Filing new feedback: `canon-feedback-file`
38
+ - Filing new feedback: `canon-feedback`
39
39
  - Writing the plan a plan-worthy issue needs: `plan-feature`
40
40
  - Triage of issues carrying any other label, which surface here by design only under the feedback label
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: canon-feedback-triage
3
- description: Triage open GitHub issues labeled `feedback` in the toolkit repo. List them, pick one, classify it as a direct fix or plan-worthy work, route to the right skill, and link the issue for close-out. Use when asked to "triage toolkit feedback", "work through the feedback issues", "process feedback issues", or "what feedback is open". Do NOT use to file new feedback (that is `canon-feedback-file`), or for general GitHub issue triage unrelated to toolkit feedback.
3
+ description: Triage open GitHub issues labeled `feedback` in the toolkit repo. List them, pick one, classify it as a direct fix or plan-worthy work, route to the right skill, and link the issue for close-out. Use when asked to "triage toolkit feedback", "work through the feedback issues", "process feedback issues", or "what feedback is open". Do NOT use to file new feedback (that is `canon-feedback`), or for general GitHub issue triage unrelated to toolkit feedback.
4
4
  ---
5
5
 
6
6
  # Canon feedback triage
@@ -26,8 +26,8 @@ The last failure is a section no route reaches. `## Route` maps an intent or a d
26
26
  - Resolve every stack, rule, snippet, and standard name from a catalog at runtime
27
27
  - Run the CLI non-interactively and report the command run, what changed, and the full path of anything written
28
28
  - Read the overwrite contract from the body before any install or sync, and name each surface it lists as overwritten before running one
29
- - Read the reverse walk beside the forward sections, so a folder the toolkit stopped shipping and a migration with a live case both reach the user
30
- - Hand off a named migration to the skill the report names, and name an unmigrated domain's root and install path for the user to move themselves when no skill covers it
29
+ - Read the reverse walk beside the forward sections, so a folder the toolkit stopped shipping reaches the user
30
+ - Name an unmigrated domain's root and install path for the user to move themselves, since no skill covers the move
31
31
  - Route a measurement intent to the audit offers from the route table itself, rather than leaving the section reachable only by reading past the table
32
32
  - Rank a lifecycle row against the audit offers where the route table itself states it, so a session acting on the lifecycle row reads the ranking without opening the section being ranked
33
33
  - Offer every audit whose surface the target carries, and offer none whose surface it lacks
@@ -25,10 +25,10 @@ Run `canon sync --check . --json` before routing. It reports what a target is be
25
25
  Read seven fields off the report and carry each to `## Route`:
26
26
 
27
27
  - `unmigrated`: a domain sitting at the root layout with nothing under `.claude/`. The most urgent finding, because that domain reports no drift of its own while being entirely behind
28
- - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own, so this routes to the skill that proposes the split rather than ending at the report
28
+ - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own, so this routes to `canon:target-check` for the full per-domain reading rather than ending at the report
29
29
  - `seeds`: entries are `matching`, `stale`, `drifted`, or `missing`. Anything but `matching` needs the seed handoff
30
30
  - `domains[].entries`: per-file `stale`, `customized`, `stranded`, and `orphaned` as before
31
- - `reverse`: what the target holds that no live catalog claims. `unclaimed` lists folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`. Act on the first two and leave `project` alone, which history proved the project owns. `migrations` names a proposal-only skill with a live case here and the `reason` it was measured from
31
+ - `reverse`: what the target holds that no live catalog claims. `unclaimed` lists folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`. Act on the first two and leave `project` alone, which history proved the project owns
32
32
  - `historyUnavailable` on a domain, on `seeds`, or on `reverse`: nothing could be dated, so treat every difference as unverified and say so rather than reporting a file as untouched. It is set on `reverse` when the toolkit itself ships without history, which is the registry-install case, so the walk found nothing rather than finding a clean target
33
33
  - `tooling`: read `measured` first. Every count under it is zero when it is false, which is an absence of measurement rather than a measured zero. Past that, `chain` names the stacks the install resolved, nearest first, and `counts.gitignore` counts the managed ignore entries the target is missing.
34
34
 
@@ -43,8 +43,7 @@ Map the stated intent, or what `## Diagnose` found, to one lifecycle phase, then
43
43
  The two can name different rows, and a reply answers both rather than picking one. Run or hand off what the finding names, then answer the intent in the same reply. A measurement intent therefore reaches the audit offers below even on a target whose diagnostic found scaffold work, which is the case a session leaving from the scaffold row would otherwise never read.
44
44
 
45
45
  - A domain in `unmigrated`: name it, the root path, and the install path. No command moves it, so the user runs the relocation themselves
46
- - An entry in `migrations`: hand off to the skill its `skill` field names, spelled as the report spells it. Both of them propose without writing, so the handoff is where this stops
47
- - Anything in `superseded`: name which files and what replaced them, then hand off to `migration-superseded`. Both skills propose, so neither moves nor deletes the file
46
+ - Anything in `superseded`: name which files and what replaced them, then hand off to `canon:target-check`, which reports and never moves or deletes the file
48
47
  - A folder in `unclaimed`: name it and the attribution it carries. No command moves it and the content may be the project's own, so the decision is the user's
49
48
 
50
49
  - First-time scaffold of a fresh project: hand off to `target-setup`
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: draft-wiki
3
+ description: Why a brand-new wiki reference page needs both placement tests, a catalog collision check, and a sourced read before anything is written
4
+ ---
5
+
6
+ # Wiki draft requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session asked for a wiki page writes one from training knowledge, which is the single thing the wiki standard forbids outright, and nothing in the resulting file shows that it happened. It also files the page by where the request came from rather than by who owns the subject, so material about the project itself lands in the wiki and material about a third-party tool acquires a folder the standard never sanctioned.
11
+
12
+ The standard can state all of this and still be read by nothing, which is the condition this skill was written into in the toolkit. A page written under that condition conforms by the author's memory rather than by a read, and it ships looking identical to one that was checked.
13
+
14
+ ## Must
15
+
16
+ - Read the wiki standard before drafting, since the placement tests and the sourcing rule are the two things a session gets wrong from memory
17
+ - Run both placement tests and refuse on either, since a page passing one and failing the other is still non-conforming
18
+ - Check the subject against the catalog by title and description, not by slug alone, since a subject can be covered under a name the slug does not guess
19
+ - Build the draft on a fetched read and stop when the fetch fails, since a page written from recall is indistinguishable from a checked one once it ships
20
+ - Confirm the path, the source, and the content with the user before writing, since the standard forbids writing a wiki file unasked
21
+ - Name the folder by resolving what the standard states rather than by carrying a path in the body, since the body ships to projects whose layout this repository does not set
22
+
23
+ ## Must not
24
+
25
+ - Widen the folder split to admit a subject the second placement test rejects. That is a change to the standard, argued there.
26
+ - Edit or refresh a page that already exists. A collision refuses toward editing the existing page rather than drafting a second one.
27
+ - Substitute recall for a failed fetch, even for a subject the session believes it knows.
28
+
29
+ ## Guards
30
+
31
+ - No subject given: stop and ask what subject the page should cover.
32
+ - The wiki root does not resolve: say so and ask before creating it, since a project holding no wiki has never taken the placement rule.
33
+ - The slug already resolves to a page: stop and point at editing that page.
34
+ - The catalog already covers the subject under a different name: stop the same way, checked against the titles and descriptions the Collision step already reads.
35
+
36
+ ## Out of scope
37
+
38
+ - Editing or refreshing a wiki page that already exists: a plain edit against the same standard
39
+ - Drafting a `docs/*.md` page: `draft-docs`
40
+ - Drafting a `canon/context/<domain>.md` entry: `draft-context`
41
+ - Voice, rhythm, and sentence construction in the drafted page: the `write-human` skill
42
+ - Punctuation, headings, and word choice in the drafted page: the markdown standard
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: draft-wiki
3
+ description: Drafts a brand-new wiki reference page for a subject owned outside this repository, against the wiki standard. Checks the two placement tests and the catalog for a collision, sources the subject from its owner rather than from training knowledge, confirms with the user, then writes. Use when asked to "write a wiki page for X", "add a wiki reference for X", "document X in the wiki", or "create a reference page for X" where no page covers the subject yet. Do NOT use to edit or refresh a page that already exists, which is a plain edit against the same standard. Do NOT use for a subject this repository owns, which is `draft-docs` or `draft-context`.
4
+ ---
5
+
6
+ # Wiki draft
7
+
8
+ Drafts one brand-new wiki reference page end to end: read the standard, settle placement, check the catalog for a collision, source the subject from its owner, confirm the draft with the user, then write.
9
+
10
+ Read these files in parallel:
11
+
12
+ - `${CLAUDE_SKILL_DIR}/../../standards/wiki.md`: the two placement tests, the frontmatter contract, the naming convention, the sourcing rule, and the page template
13
+ - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
14
+ - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
15
+
16
+ The standard's `## Placement` section names the folder a page lands in and the vendor subfolder under it. Resolve both from the project root and call them `<wiki-root>` and `<vendor>` below, so the body states the rule once and the standard stays the one owner of the path.
17
+
18
+ ## Guards
19
+
20
+ - If no subject is given, stop: `❌ No subject given. Name the subject this page should cover.`
21
+ - If `<wiki-root>` does not resolve, say so and ask whether to create it before drafting. A project carrying no such folder has never taken the standard's placement rule, so creating it is a decision rather than a step.
22
+
23
+ ## Placement
24
+
25
+ The standard sets two tests and a page has to pass both. Run them before drafting anything, since a page failing either is non-conforming however well it is written.
26
+
27
+ - **Owned outside this repository.** A subject describing how this repository works fails. Refuse and route it: `❌ <subject> is owned by this project, so it belongs in the docs surface, a context entry, or a skill body. Use draft-docs or draft-context.`
28
+ - **Owned by the vendor the folder split covers.** The standard names one owner and one subfolder. A third-party tool or a vendor-neutral concept fails, however reference-like it reads. Refuse and route it: `❌ <subject> is outside the wiki's folder split. Route it to the docs surface or a skill body rather than adding a second folder for it.`
29
+
30
+ Do not widen the split to admit a subject. Adding a sibling folder is a change to the standard, argued there, rather than a placement this skill takes on its own.
31
+
32
+ ## Collision
33
+
34
+ - Derive the page slug per the standard's `## Naming` section and check whether `<wiki-root>/<vendor>/<slug>.md` already exists. If it does, stop: `❌ <path> already covers this subject. Edit that page against the standard instead of drafting a second one.`
35
+ - Read `<wiki-root>/<vendor>/index.md` and check every title and description it lists against the subject. The slug check above only catches an exact filename collision, and a subject already covered under a different name still resolves here at no extra cost, since this read already runs.
36
+ - Stop the same way on a match: `❌ <path> already covers this subject under a different name. Edit that page instead.`
37
+
38
+ ## Source
39
+
40
+ The standard forbids working from training knowledge, so the draft is built on a read rather than on recall.
41
+
42
+ - Fetch current information through the `claude-code-guide` agent for any subject that agent covers. Its answer is the source the page is written from.
43
+ - Reach a subject the agent does not cover through the owner's own published documentation.
44
+ - Say the fetch failed and stop rather than substituting recall. A page sourced from memory is the failure the rule exists to prevent, and it ships looking identical to one that was checked.
45
+ - Close the intro with the `Source:` sentence the standard requires, linking the canonical page where one exists and naming the owner alone where the subject has no single URL.
46
+ - Stamp the read date on a source revised without notice, as `read <date>`. A claim traced to an undated read cannot be checked against what was actually read.
47
+
48
+ ## Draft
49
+
50
+ - Draft `title` and `description` frontmatter against the standard's contract, then the intro paragraph closing on `Source:`, then the reference sections.
51
+ - Draft against the standard's template and keep its ordering.
52
+ - Write what the fetch returned rather than what the subject is assumed to do.
53
+ - Answer one question per section and stop. A reader arrives to settle something specific rather than to read the page through.
54
+ - Mark a claim the fetch left unsettled rather than smoothing over it. An unmarked gap reads as a checked fact.
55
+
56
+ ## Confirm
57
+
58
+ - Show the resolved path, the source the page was built from, and the full drafted content before writing.
59
+ - Confirm all three with the user. This skill waits for that answer rather than treating the tool permission dialog as the gate, since the standard states outright that a wiki file is not written unasked.
60
+
61
+ ## Write
62
+
63
+ - Write the file at the confirmed path.
64
+ - Run `canon markdown audit <path>`.
65
+ - Run `canon indexes regen <wiki-root>/<vendor>`, naming the folder the page landed in rather than the root above it. The verb rewrites the `index.md` sitting in the folder it is given and does not walk down, so regenerating at the root leaves the vendor catalog untouched and the new page missing from the one the Collision step reads. A page absent there is invisible to the next run, which then drafts a second page on the same subject.
66
+
67
+ ## Response format
68
+
69
+ ### Preview
70
+
71
+ **Subject:** `<subject>`
72
+ **Placement:** `<path>`
73
+ **Source:** `<owner, and the read it came from>`
74
+
75
+ ```markdown
76
+ <drafted frontmatter and body>
77
+ ```
78
+
79
+ ### After confirmation
80
+
81
+ ```plaintext
82
+ ✅ Drafted: <path>
83
+ ```
@@ -33,6 +33,6 @@ Without this skill, an issue takes the shape of whatever the session was saying.
33
33
 
34
34
  ## Out of scope
35
35
 
36
- - Reporting a toolkit defect from a target project, which `canon-feedback-file` owns. That path writes into the toolkit rather than the current repository.
36
+ - Reporting a toolkit defect from a target project, which `canon-feedback` owns. That path opens an issue on the toolkit repository rather than the current one.
37
37
  - Triaging issues already filed, which `canon-feedback-triage` owns
38
38
  - Opening a pull request, which `git-pr` owns
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: git-issue
3
- description: Format a bug or task from the current session into a GitHub issue and file it on the current repo via `gh issue create`. Use when asked to "file an issue", "open an issue", "log this bug", "raise an issue", or "track this as an issue". Do NOT use to report a toolkit defect from a target project (that is `canon-feedback-file`), or to open a pull request (that is `git-pr`).
3
+ description: Format a bug or task from the current session into a GitHub issue and file it on the current repo via `gh issue create`. Use when asked to "file an issue", "open an issue", "log this bug", "raise an issue", or "track this as an issue". Do NOT use to report a toolkit defect from a target project (that is `canon-feedback`), or to open a pull request (that is `git-pr`).
4
4
  ---
5
5
 
6
6
  # Git issue
@@ -25,7 +25,7 @@ Read three fields off the same report, then two verbs:
25
25
 
26
26
  - `unmigrated`: a domain still at the root layout with nothing under `.claude/`. The most urgent finding, because such a domain reports no drift of its own while being entirely behind.
27
27
  - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own.
28
- - `reverse`: `unclaimed` names folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`. `migrations` names a proposal-only skill with a live case here and the `reason` behind it.
28
+ - `reverse`: `unclaimed` names folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`.
29
29
  - `canon context audit --json`: required sections, entry length, citations, and index drift across the context folder.
30
30
  - `canon records validate <kind> --json`, once per record folder the target carries.
31
31
 
@@ -33,7 +33,7 @@ Read three fields off the same report, then two verbs:
33
33
  - Behind: any of those carrying an entry. Name the entry and its attribution.
34
34
  - Unread: `historyUnavailable` set on `reverse`, a `reverse` key the report does not carry at all, or either verb refusing. A missing `reverse` key is a binary predating the field rather than a target with nothing unclaimed.
35
35
 
36
- Routes by finding. `unmigrated` routes to a relocation the user runs, since no command moves it. `superseded` routes to `migration-superseded`. A `migrations` entry routes to the skill its `skill` field names, spelled as the report spells it. An `unclaimed` folder carrying `project` attribution routes nowhere, since history proved the project owns it.
36
+ Routes by finding. `unmigrated` routes to a relocation the user runs, since no command moves it. `superseded` routes to a split the user makes, since no command moves the content and no skill proposes it. An `unclaimed` folder carrying `project` attribution routes nowhere, since history proved the project owns it.
37
37
 
38
38
  ## 3. Governance rules
39
39
 
@@ -256,11 +256,9 @@ seed tree rather than from a fixed set of filenames, so a folder added later is
256
256
  covered without a code change. Only an exact stem matches, which leaves a
257
257
  suffixed variant such as `TASKS-ARCHIVE.md` unreported.
258
258
 
259
- Route it to `migration-superseded`. That skill resolves the standard governing
260
- `replacedBy` from the `appliesTo` the standards catalog declares, reads the
261
- destination shape with `canon standards <name>`, and proposes the split without
262
- writing. Where a folder has no governing standard, the entry earns a named
263
- refusal rather than a shape nobody stated.
259
+ No skill splits the file. The standard governing `replacedBy` is the one the
260
+ `appliesTo` the standards catalog declares, and `canon standards <name>` reads
261
+ its destination shape, so the split is the project's own to make.
264
262
 
265
263
  `unmigrated` names a domain sitting at the root layout an older toolkit installed
266
264
  to, with nothing at the path the current one reads. It carries `rootPath`,
@@ -350,7 +348,7 @@ check.
350
348
  `reverse` is the one section built by walking the target rather than the
351
349
  catalog. Every other surface enumerates toolkit-owned keys and asks whether the
352
350
  target matches, so a folder the toolkit deleted appears in none of them. It
353
- carries `unclaimed`, `migrations`, and `historyUnavailable`.
351
+ carries `unclaimed` and `historyUnavailable`.
354
352
 
355
353
  `unclaimed` names a folder the target holds at a top-level path the toolkit once
356
354
  shipped and has since deleted. The candidate roots come from the toolkit's own
@@ -381,13 +379,6 @@ colliding on a retired name costs the walk no reads. The cost is that a file the
381
379
  toolkit shipped and the target renamed goes unmatched, the same limit the
382
380
  `unmigrated` count carries.
383
381
 
384
- `migrations` names a proposal-only skill with a live case in this target. It
385
- fires on a `CLAUDE.md` past 250 lines for `migration-claude-md`, and on a `docs/` folder
386
- holding markdown with no populated `canon/context/` for `migration-context`.
387
- Each entry carries the skill name and the measurement behind it, so a consumer
388
- can check the proposal before running it. Without the field both skills are
389
- documented and unreachable from any report.
390
-
391
382
  #### What counts toward the gate
392
383
 
393
384
  `unmigrated` counts toward `--exit-code`, since running the relocation closes it.
@@ -133,7 +133,7 @@ It reports three things. `lesson` is the numbered path the lesson takes, derived
133
133
 
134
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
135
 
136
- `canon teach stylesheet <topic>` writes the one file every lesson in the workspace embeds, seeded from the design source so a workspace renders in the system every other surface does. 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.
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
137
 
138
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
139
 
@@ -172,10 +172,12 @@ canon teach nav regular-expressions --json
172
172
 
173
173
  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
174
 
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 shared stylesheet, rebuilding the breadcrumb and jump menus, the prev/next footer nav, and the behavior scripts, and leaves the authored `<h1>`, lede, body, and quiz between the header and the footnav untouched.
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 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, and leaves the authored `<h1>`, lede, body, and quiz between the header and the footnav untouched.
176
176
 
177
177
  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 embedded copy drops the seed's `@import` line, which resolved against the lesson's own folder and never loaded anything.
178
178
 
179
+ That seeding fires on the absent file alone, so a workspace already holding a `course.css` has neither half rewritten here and its lessons go on embedding the sheet they were built against. A change to the shared chrome therefore takes `canon teach stylesheet <topic>` per workspace first, which rewrites `assets/base.css`, and then this verb to embed the result. Running this one alone renders every lesson missing whatever the change added, and nothing reports it.
180
+
179
181
  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.
180
182
 
181
183
  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.
@@ -261,8 +261,6 @@ Each entry carries a verdict, since a dropped folder and one the project wrote a
261
261
 
262
262
  Nothing acts on any of them, and the verdict is what makes the list safe to read.
263
263
 
264
- The same field names a proposal-only skill with a live case here under `migrations`, which is how `canon:migration-claude-md` and `canon:migration-context` become reachable. Each entry carries the measurement behind the proposal rather than the proposal alone.
265
-
266
264
  Add `--json` for the machine-readable report, and `--exit-code` to fail a CI job when a target falls behind. Files the project authored itself never count toward that exit code, and neither do superseded artifacts, seed drift, tooling, or anything the reverse walk reports, since each names content the project is expected to edit or place itself. An unmigrated domain does count, because running the relocation closes it.
267
265
 
268
266
  Tooling reports under a section of its own, and `measured` there says whether the target ever recorded a chain. One that never ran a tooling sync reports unmeasured rather than clean, which is what separates tooling nobody has looked at from tooling that is current. A workspace root records nothing either way, since each package resolves its own chain.
@@ -285,17 +285,13 @@ This section is the corpus the coverage claim is measured against: every name `c
285
285
 
286
286
  ### Keep the project current with the toolkit
287
287
 
288
- | Skill | When to use |
289
- | -------------------------------- | -------------------------------------------------------------------------------------- |
290
- | `canon:target-check` | In a target project, to report per domain what it holds against what the toolkit ships |
291
- | `canon:seed-sync` | After a toolkit update, to reconcile installed seeds without losing customizations |
292
- | `canon:migration-claude-md` | When `CLAUDE.md` grew past what always-load context should carry |
293
- | `canon:migration-context` | When `docs/` holds agent-flavored files belonging in `canon/context/` |
294
- | `canon:migration-superseded` | When a drift report names a `.claude/` file a folder has replaced |
295
- | `canon:migration-standards-drop` | When the project still holds an installed `.claude/standards/` tree |
296
- | `canon:canon-feedback-file` | When something in the toolkit is broken, missing, or off |
297
- | `canon:canon-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
298
- | `canon:canon-rollout` | In the toolkit repo, to take one change out to every consuming project at once |
288
+ | Skill | When to use |
289
+ | ----------------------------- | -------------------------------------------------------------------------------------- |
290
+ | `canon:target-check` | In a target project, to report per domain what it holds against what the toolkit ships |
291
+ | `canon:seed-sync` | After a toolkit update, to reconcile installed seeds without losing customizations |
292
+ | `canon:canon-feedback` | When something in the toolkit is broken, missing, or off, to open an issue on it |
293
+ | `canon:canon-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
294
+ | `canon:canon-rollout` | In the toolkit repo, to take one change out to every consuming project at once |
299
295
 
300
296
  ### Generate an artifact on demand
301
297
 
@@ -309,6 +305,7 @@ This section is the corpus the coverage claim is measured against: every name `c
309
305
  | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
310
306
  | `canon:draft-context` | For a brand-new `canon/context/<domain>.md` entry, drafted against `standards/context.md` |
311
307
  | `canon:draft-wireframes` | For a brand-new `canon/wireframes/<surface>.md` file, drafted against `standards/wireframes.md` |
308
+ | `canon:draft-wiki` | For a brand-new wiki reference page on a subject owned outside the project, drafted against `standards/wiki.md` |
312
309
  | `canon:draft-figure` | For a hand-drawn figure inside an existing doc, drafted against `standards/figures.md` and rendered through Mermaid or freehand SVG |
313
310
  | `canon:draft-readme` | For a project's `README.md`, drafted against `standards/readme.md` |
314
311
  | `canon:bash-script` | For an interactive, human-facing shell tool |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.109.0",
4
+ "version": "4.111.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ # Builds the landing page, captures it, and stages the frames over
3
+ # assets/evidence/home, the baseline `canon pr evidence` compares against.
4
+ #
5
+ # Two workflows call it. pr-visual-checks.yml stages the frames into the runner
6
+ # tree to diff them against the baseline, and refresh-capture-frames.yml stages
7
+ # them to commit as the new baseline. Both need the same frames, so one script
8
+ # keeps the two from drifting apart.
9
+ #
10
+ # `tooling/web/configs/e2e/screenshot.ts` is reused as is. This checkout has no
11
+ # web/package.json, so the preview server and the cases run against the
12
+ # root-level scripts rather than through screenshot.sh, which assumes a
13
+ # per-project `bun run build`/`bun run preview` pair.
14
+ set -e
15
+ set -o pipefail
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
19
+
20
+ PORT=4321
21
+ BASE_URL="http://localhost:$PORT"
22
+ BASELINE="$PROJECT_ROOT/assets/evidence/home"
23
+
24
+ cd "$PROJECT_ROOT"
25
+
26
+ bun run web:build
27
+
28
+ bun run web:preview &
29
+ PREVIEW_PID=$!
30
+ trap 'kill "$PREVIEW_PID" 2>/dev/null || true' EXIT
31
+
32
+ for _ in $(seq 1 40); do
33
+ curl -sSf "$BASE_URL/" >/dev/null 2>&1 && break
34
+ sleep 0.25
35
+ done
36
+ curl -sSf "$BASE_URL/" >/dev/null 2>&1 || {
37
+ echo "Preview server not responding on port $PORT after 10s." >&2
38
+ exit 1
39
+ }
40
+
41
+ (cd web && SCREENSHOT_BASE_URL="$BASE_URL" bun ../tooling/web/configs/e2e/screenshot.ts)
42
+
43
+ # The committed tree is cleared first rather than copied over. A copy only adds
44
+ # and overwrites, so a section dropped from the case list would leave its
45
+ # frames in place and a diff would see no change. Clearing first shows that
46
+ # section as a deletion.
47
+ rm -rf "$BASELINE"
48
+ mkdir -p "$BASELINE"
49
+ cp -R web/screenshots/localhost/home/. "$BASELINE/"
50
+
51
+ # The sweep holds all four widths and this repository commits two, 320 and
52
+ # 1280. The case list is the place to change if that set moves.
53
+ find "$BASELINE" \( -name '768--*.png' -o -name '1536--*.png' \) -delete
54
+
55
+ # The shared golden config names the case that emulates nothing `default`, and
56
+ # this repository's baseline names that frame `light`.
57
+ find "$BASELINE" -name '*--default.png' -execdir sh -c 'mv "$1" "${1%--default.png}--light.png"' _ {} \;
58
+
59
+ echo "capture-home: staged frames in assets/evidence/home"
@@ -61,6 +61,11 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
61
61
  'Draft a wireframe for the settings panel, nothing under canon/wireframes covers that surface yet.',
62
62
  expect: 'draft-wireframes',
63
63
  },
64
+ {
65
+ prompt:
66
+ 'Write a wiki reference page for Claude Code output styles, no page covers that subject yet.',
67
+ expect: 'draft-wiki',
68
+ },
64
69
  {
65
70
  prompt: 'Say what that dense answer actually means in plain terms.',
66
71
  expect: 'restate-plainly',
@@ -1,9 +1,8 @@
1
1
  import type { SkillCase } from '@/claude/skills-rank'
2
2
 
3
3
  /**
4
- * `target-setup`, `migration-*`, `canon-*`, and `create-rule`: scaffolding,
5
- * proposal-only migrations, and the toolkit's own reference and feedback
6
- * surfaces.
4
+ * `target-setup`, `canon-*`, and `create-rule`: scaffolding and the
5
+ * toolkit's own reference and feedback surfaces.
7
6
  *
8
7
  * The five `target-setup` cases are one skill read through five entry phrasings
9
8
  * rather than five rows that collapsed to a duplicate. Each names a different
@@ -36,26 +35,6 @@ export const SETUP_CASES: readonly SkillCase[] = [
36
35
  'Check that the dev server actually starts and the end-to-end suite passes against the scaffold.',
37
36
  expect: 'target-setup',
38
37
  },
39
- {
40
- prompt:
41
- 'This CLAUDE.md file has grown huge, break it apart into the tiered context model.',
42
- expect: 'migration-claude-md',
43
- },
44
- {
45
- prompt:
46
- 'Move the agent-flavored docs out of the docs folder and into context.',
47
- expect: 'migration-context',
48
- },
49
- {
50
- prompt:
51
- 'This file was replaced by a folder, help me split its content into it.',
52
- expect: 'migration-superseded',
53
- },
54
- {
55
- prompt:
56
- 'This project still carries a copy of the standards folder, get rid of it and point everything at the CLI.',
57
- expect: 'migration-standards-drop',
58
- },
59
38
  {
60
39
  prompt:
61
40
  "Before I run this sync, tell me exactly what it's going to overwrite.",
@@ -64,7 +43,7 @@ export const SETUP_CASES: readonly SkillCase[] = [
64
43
  {
65
44
  prompt:
66
45
  'Something about the toolkit itself is broken, write it up and send it back to the maintainers.',
67
- expect: 'canon-feedback-file',
46
+ expect: 'canon-feedback',
68
47
  },
69
48
  {
70
49
  prompt:
@@ -189,7 +189,6 @@ function renderCheck(report: CheckReport): void {
189
189
  }
190
190
 
191
191
  renderUnclaimed(report)
192
- renderMigrations(report)
193
192
 
194
193
  outro()
195
194
  // Scanned domains only. Tooling renders a section on every managed target, so
@@ -334,21 +333,6 @@ function describeUnclaimed(entry: UnclaimedFolder): string {
334
333
  return `${counted}, content published at ${entry.since.slice(0, 7)}`
335
334
  }
336
335
 
337
- /**
338
- * The proposal-only skills no other field reaches.
339
- */
340
- function renderMigrations(report: CheckReport): void {
341
- const { migrations } = report.reverse
342
- if (migrations.length === 0) return
343
-
344
- logStep('Migrations with a case here')
345
-
346
- for (const candidate of migrations) {
347
- logWarn(candidate.reason)
348
- logInfo(`Run /canon:${candidate.skill} for a proposal.`)
349
- }
350
- }
351
-
352
336
  async function runSync(target: string): Promise<number> {
353
337
  intro('canon sync')
354
338