@erclx/canon 4.127.0 → 5.1.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 (95) hide show
  1. package/README.md +5 -7
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/references/verb-absent.md +0 -1
  4. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
  5. package/claude/skills/canon-feedback/SKILL.md +2 -2
  6. package/claude/skills/canon-operator/REQUIREMENT.md +2 -2
  7. package/claude/skills/canon-operator/SKILL.md +3 -3
  8. package/claude/skills/codebase-layout/REQUIREMENT.md +1 -0
  9. package/claude/skills/codebase-layout/SKILL.md +6 -0
  10. package/claude/skills/create-rule/REQUIREMENT.md +1 -1
  11. package/claude/skills/create-standard/REQUIREMENT.md +0 -2
  12. package/claude/skills/decision-escalate/REQUIREMENT.md +1 -2
  13. package/claude/skills/draft-context/REQUIREMENT.md +1 -1
  14. package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
  15. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  16. package/claude/skills/plan-intake/SKILL.md +1 -1
  17. package/claude/skills/target-setup/REQUIREMENT.md +2 -2
  18. package/claude/skills/target-setup/SKILL.md +1 -1
  19. package/claude/skills/target-setup/references/detect.md +1 -1
  20. package/claude/skills/target-setup/references/indexes.md +1 -5
  21. package/docs/agents/commands.md +1 -3
  22. package/docs/agents/counts.md +1 -2
  23. package/docs/agents/docs.md +1 -1
  24. package/docs/agents/drift-surfaces.md +6 -4
  25. package/docs/agents/index.md +1 -1
  26. package/docs/agents/indexes.md +1 -1
  27. package/docs/agents/install-and-sync.md +1 -8
  28. package/docs/agents/markdown-audit.md +2 -2
  29. package/docs/agents/restated.md +7 -5
  30. package/docs/agents/review-classification.md +1 -2
  31. package/docs/agents/scripting.md +2 -3
  32. package/docs/agents/skills-reach.md +3 -3
  33. package/docs/agents/state-scoped-risk.md +1 -1
  34. package/docs/target/index.md +4 -4
  35. package/docs/target/{target-migrations.md → migrations.md} +17 -1
  36. package/docs/target/{target-projects.md → projects.md} +5 -7
  37. package/docs/target/{target-scenarios.md → scenarios.md} +1 -1
  38. package/docs/target/{target-sync.md → sync.md} +4 -2
  39. package/docs/workflow/ai-workflow.md +3 -7
  40. package/docs/workflow/operating-model.md +4 -4
  41. package/docs/workflow/parallel-features.md +1 -1
  42. package/docs/workflow/skill-map.md +1 -2
  43. package/docs/workflow/visual-design-workflow.md +5 -5
  44. package/docs/workflow/zshrc-aliases.md +1 -1
  45. package/package.json +1 -2
  46. package/scripts/core/check-plugin-boundary.sh +1 -1
  47. package/scripts/core/install-check.sh +3 -4
  48. package/scripts/core/regen-hero.sh +2 -9
  49. package/src/autoship/paths.ts +3 -5
  50. package/src/claude/cases/authoring.ts +1 -5
  51. package/src/claude/skills-reach.ts +1 -2
  52. package/src/cli.ts +0 -2
  53. package/src/commands/feedback-format.ts +0 -1
  54. package/src/commands/gov.ts +8 -7
  55. package/src/commands/markdown.ts +2 -2
  56. package/src/counts/catalogs.ts +0 -16
  57. package/src/counts/scan.ts +1 -1
  58. package/src/docs/read.ts +25 -25
  59. package/src/gate/measures.ts +52 -0
  60. package/src/gate/stages.ts +9 -0
  61. package/src/gov/restated.ts +60 -22
  62. package/src/help.ts +0 -2
  63. package/src/init/steps.ts +1 -1
  64. package/src/migrate/skill-names.ts +4 -5
  65. package/src/secrets/shipped.ts +2 -2
  66. package/src/serve/static.ts +1 -2
  67. package/src/shipped/references.ts +7 -8
  68. package/src/sync/engine.ts +2 -2
  69. package/standards/context.md +2 -0
  70. package/standards/docs.md +1 -0
  71. package/standards/index.md +0 -1
  72. package/standards/skill.md +1 -2
  73. package/standards/wiki.md +2 -1
  74. package/tooling/base/reference.md +2 -2
  75. package/tooling/claude/reference.md +1 -1
  76. package/claude/skills/create-snippet/REQUIREMENT.md +0 -39
  77. package/claude/skills/create-snippet/SKILL.md +0 -30
  78. package/scripts/snippets/create.sh +0 -156
  79. package/snippets/align.md +0 -12
  80. package/snippets/claude/decision-memo.md +0 -39
  81. package/snippets/claude/feature-recap.md +0 -19
  82. package/snippets/claude/figma-steps.md +0 -24
  83. package/snippets/compact-summary.md +0 -5
  84. package/snippets/decision-help.md +0 -6
  85. package/snippets/meta-prompt.md +0 -14
  86. package/snippets/research-prompt.md +0 -7
  87. package/snippets/session-notes.md +0 -11
  88. package/snippets/snippets.toml +0 -5
  89. package/snippets/step-by-step.md +0 -10
  90. package/snippets/web-research.md +0 -21
  91. package/src/commands/snippets.ts +0 -90
  92. package/src/snippets/categories.ts +0 -66
  93. package/src/snippets/list.ts +0 -32
  94. package/src/snippets/presets.ts +0 -50
  95. package/standards/snippets.md +0 -76
@@ -104,8 +104,6 @@ Run `canon tooling list --json` and `canon gov list --json` to see the current c
104
104
 
105
105
  No standard is written into the project. Each governance rule's authority line names `canon standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `canon markdown audit` needs no standard at all, its ban set and checkpoints shipping with the package as data.
106
106
 
107
- No snippet is written into the project either. `claude/snippets` in the plugin cache symlinks to the toolkit's own `snippets/`, so a session reaches one at its `@` reference with no install step, the same live resolution `claude/standards` gives a rule's authority line.
108
-
109
107
  `governance` and `wiki` are skippable:
110
108
 
111
109
  - `--skip governance`: leave `.claude/rules/` empty, so no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `canon gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
@@ -121,22 +119,22 @@ When a new need appears after scaffold, install the one domain without re-runnin
121
119
  - Project-specific rule the toolkit does not ship: invoke `canon:create-rule`. It scaffolds a rule into `.claude/rules/project/` with a non-colliding number, and `canon gov sync` leaves it untouched. A rule placed under `.claude/rules/canon/` instead is deleted on the next sync, since that folder belongs to the toolkit.
122
120
  - Index.md system for a markdown-heavy folder that emerged: invoke `canon:target-setup indexes`
123
121
 
124
- Standards and snippets are not on that list, and there is nothing to add for either. Neither installs into a project, by default or by flag, so a session reads a standard with `canon standards <name>` and a snippet through its `@` reference off the live plugin symlink, both resolving against the toolkit rather than a project copy. A project holding a `.claude/standards/` or `.claude/snippets/` folder from an older toolkit is carrying a stale artifact nothing reads, and deleting it is safe.
122
+ Standards are not on that list, and there is nothing to add for them. They install into no project, by default or by flag, so a session reads one with `canon standards <name>`, resolving against the toolkit rather than a project copy. A project holding a `.claude/standards/` folder from an older toolkit is carrying a stale artifact nothing reads, and deleting it is safe. The toolkit retired snippets entirely, and [target migrations](migrations.md) covers the `.claude/snippets/` folder an older one left behind.
125
123
 
126
124
  Per-domain mechanics live in the corresponding `docs/<domain>.md`. The skill body in `claude/skills/<skill>/SKILL.md` covers detection and preview.
127
125
 
128
126
  ## Sync upstream drift
129
127
 
130
- When the toolkit updates, a target pulls changes per domain. A project scaffolded before a layout move runs the one-time moves in [target migrations](target-migrations.md) first. [Target sync](target-sync.md) then covers reading what drifted, the catch-all and targeted entry points, verifying a sync in the sandbox, and running one from an agent session.
128
+ When the toolkit updates, a target pulls changes per domain. A project scaffolded before a layout move runs the one-time moves in [target migrations](migrations.md) first. [Target sync](sync.md) then covers reading what drifted, the catch-all and targeted entry points, verifying a sync in the sandbox, and running one from an agent session.
131
129
 
132
130
  ## Scenarios
133
131
 
134
- [Target scenarios](target-scenarios.md) walks a markdown-heavy project, a web application, and a monorepo with several language roots from the first session through ongoing maintenance.
132
+ [Target scenarios](scenarios.md) walks a markdown-heavy project, a web application, and a monorepo with several language roots from the first session through ongoing maintenance.
135
133
 
136
134
  ## Related
137
135
 
138
136
  - [agents](../agents/index.md): CLI flags, exit codes, and JSON output shapes
139
- - [target migrations](target-migrations.md), [target sync](target-sync.md), [target scenarios](target-scenarios.md): the one-time moves, the recurring sync, and worked setups
137
+ - [target migrations](migrations.md), [target sync](sync.md), [target scenarios](scenarios.md): the one-time moves, the recurring sync, and worked setups
140
138
  - [AI workflow](../workflow/ai-workflow.md): feature-development loop inside a toolkit-managed project
141
- - [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
139
+ - [tooling](../../canon/context/tooling/index.md), [governance](../../canon/context/governance/index.md), [claude plugin](../../canon/context/claude-plugin/index.md), [indexes](../../canon/context/context-model/indexes.md), [standards](../../canon/context/standards/index.md): per-domain mechanics
142
140
  - [sandbox](../../canon/context/sandbox/index.md): scenario catalog for verifying domain flows
@@ -6,7 +6,7 @@ category: Agent surface
6
6
 
7
7
  # Target scenarios
8
8
 
9
- Three worked setups, each from the first session through ongoing maintenance. [Target projects](target-projects.md) carries the lifecycle they walk.
9
+ Three worked setups, each from the first session through ongoing maintenance. [Target projects](projects.md) carries the lifecycle they walk.
10
10
 
11
11
  ## Markdown-heavy project
12
12
 
@@ -6,7 +6,7 @@ category: Agent surface
6
6
 
7
7
  # Target sync
8
8
 
9
- When the toolkit updates, a target project pulls changes per domain. There is one catch-all and several targeted entry points. A project scaffolded before a layout move runs the one-time moves in [target migrations](target-migrations.md) first.
9
+ When the toolkit updates, a target project pulls changes per domain. There is one catch-all and several targeted entry points. A project scaffolded before a layout move runs the one-time moves in [target migrations](migrations.md) first.
10
10
 
11
11
  ## Check first
12
12
 
@@ -30,7 +30,7 @@ A project that has never synced under a toolkit new enough to write a stamp fall
30
30
 
31
31
  Further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `canon:seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.canon/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated`, and no command moves it either. The project moves the content itself.
32
32
 
33
- `unmigrated` currently names no domain, since standards and snippets are the two the toolkit ever installed at the project root and both closed their install channel. A target still holding a root `standards/` or `snippets/` folder from an older toolkit is carrying its own authoring surface now, not an unfinished install, and nothing proposes moving either.
33
+ `unmigrated` currently names no domain, since standards and snippets are the two the toolkit ever installed at the project root, standards closed its install channel, and snippets retired outright. A target still holding a root `standards/` folder from an older toolkit is carrying its own authoring surface now, not an unfinished install, and nothing proposes moving it. A leftover `.claude/snippets/` reports under `reverse` below instead.
34
34
 
35
35
  ### Rules you never received
36
36
 
@@ -56,6 +56,8 @@ Each entry carries a verdict, since a dropped folder and one the project wrote a
56
56
  - `unattributed` means the toolkit shipped that path and the content matches no version it published
57
57
  - `project` means the folder only shares a retired name
58
58
 
59
+ A `.claude/snippets/` folder an older toolkit installed is the case this walk exists for, since the toolkit retired the domain and deleted its `snippets/` root. It reports as `dropped`, and [target migrations](migrations.md) says what to do with it.
60
+
59
61
  Nothing acts on any of them, and the verdict is what makes the list safe to read.
60
62
 
61
63
  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.
@@ -36,7 +36,7 @@ canon/
36
36
 
37
37
  A project scaffolded before the move keeps its records under `.claude/`, and every command reads either root. `canon migrate records` moves one project across and repoints what cites it, and `canon migrate record-tree` follows it to reach the citations inside the records themselves, which the first verb passes over because it enumerates through git. A project scaffolded before the surface move keeps its context, decisions, wireframes, and loose documents under `.claude/` the same way, and `canon migrate surface-roots` moves those to `canon/` with the history following each file.
38
38
 
39
- Three tiers of context load with different cost: always-loaded (root `CLAUDE.md`, `canon/REQUIREMENTS.md`, `canon/ARCHITECTURE.md`), path-scoped lazy (`.claude/rules/<scope>.md` with `paths:` glob), and on-demand lookup (`canon/context/<domain>.md`, or `canon/context/<domain>/` once a domain outgrows one file, discovered via `canon/context/index.md`). See [the context model](../../canon/context/context-model.md) for the full picture.
39
+ Three tiers of context load with different cost: always-loaded (root `CLAUDE.md`, `canon/REQUIREMENTS.md`, `canon/ARCHITECTURE.md`), path-scoped lazy (`.claude/rules/<scope>.md` with `paths:` glob), and on-demand lookup (`canon/context/<domain>.md`, or `canon/context/<domain>/` once a domain outgrows one file, discovered via `canon/context/index.md`). See [the context model](../../canon/context/context-model/overview.md) for the full picture.
40
40
 
41
41
  Run `canon init` to seed the `.claude/` directory, a root `CLAUDE.md` file, and `.claude/rules/` in one pass. `canon init` chains claude init and governance install. Claude Code auto-loads every file in `.claude/rules/` at session start, applying always-on rules unconditionally and path-scoped rules to files matching their `paths:` glob.
42
42
 
@@ -44,7 +44,7 @@ Run `canon init` to seed the `.claude/` directory, a root `CLAUDE.md` file, and
44
44
 
45
45
  ### Bootstrap a new project
46
46
 
47
- See [target projects](../target/target-projects.md) for the scaffold decision, core domains and skips, and the full lifecycle across scaffold, add-a-domain-later, and upstream sync.
47
+ See [target projects](../target/projects.md) for the scaffold decision, core domains and skips, and the full lifecycle across scaffold, add-a-domain-later, and upstream sync.
48
48
 
49
49
  ### New feature
50
50
 
@@ -95,7 +95,7 @@ For features on a mature stack, chain the post-plan pipeline in one session. App
95
95
 
96
96
  Review findings split by origin before severity is read. One the branch inherited stops the chain, and one the run itself caused is repaired in place at any severity, bounded at a single pass. Origin is causation rather than authorship, so staleness the run induced in a file it never opened counts as its own and the plan's file list bounds what it builds rather than what it may repair.
97
97
 
98
- Review is skipped when the diff is prose that only informs: every changed file matches `*.md` or `*.txt`, and none sits under a behavior path. Behavior paths cover skills and rules in both the authoring and the installed spelling, so the list matches whether a repository authors those surfaces or consumed them from the toolkit. Standards, snippets, `internal/`, and `tooling/` carry the authoring spelling alone, since none of the four reaches a session through a `.claude/` copy, and root `CLAUDE.md` is named as a file because a path prefix reaches nothing sitting in no folder.
98
+ Review is skipped when the diff is prose that only informs: every changed file matches `*.md` or `*.txt`, and none sits under a behavior path. Behavior paths cover skills and rules in both the authoring and the installed spelling, so the list matches whether a repository authors those surfaces or consumed them from the toolkit. Standards, `internal/`, and `tooling/` carry the authoring spelling alone, since none of the three reaches a session through a `.claude/` copy, and root `CLAUDE.md` is named as a file because a path prefix reaches nothing sitting in no folder.
99
99
 
100
100
  Markdown under one states what an agent does, so a branch touching it reaches review while `docs/` and `wiki/` still skip and stay gated by `docs-sync`, `standards-audit`, and pre-push hooks.
101
101
 
@@ -146,7 +146,3 @@ verify fails → Session 1 (it has implementation context)
146
146
  design fails → new Claude chat session (planning problem)
147
147
  review finds → Session 2 (fix alongside review, before ship)
148
148
  ```
149
-
150
- ## Snippets
151
-
152
- For the full list of snippets that complement this workflow, see `canon/context/snippets.md`.
@@ -12,7 +12,7 @@ cross-feature call and reviews. Planning runs there or in a session of its own.
12
12
  Cold worker sessions build. The human launches workers and merges.
13
13
 
14
14
  This page covers the roles and the loop. For the worktree mechanism (isolation, merge
15
- order, port collisions), see [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md).
15
+ order, port collisions), see [Claude Code and git worktrees](../../wiki/claude/worktrees.md).
16
16
 
17
17
  ## Four roles
18
18
 
@@ -226,11 +226,11 @@ A dev server, an end-to-end run, and a screenshot run alongside each other on a
226
226
  web stack, since every worktree derives its own port. Singleton resources (one
227
227
  local model server, one GPU) still serialize, as does any port a stack fixes by
228
228
  hand. See
229
- [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md) for merge order and the
229
+ [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for merge order and the
230
230
  port-collision detail.
231
231
 
232
232
  ## Related
233
233
 
234
- - [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md) for the isolation and fan-out mechanics
235
- - [Claude Code subagents](../../wiki/claude/claude-subagents.md) for in-session parallelism without worktrees
234
+ - [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the isolation and fan-out mechanics
235
+ - [Claude Code subagents](../../wiki/claude/subagents.md) for in-session parallelism without worktrees
236
236
  - `canon/context/claude-plugin/skill-strategy/overview.md` for how the skills in the loop are categorized
@@ -32,7 +32,7 @@ A constraint naming a track in flight carries the same problem past the handoff,
32
32
 
33
33
  ## Records at the main root
34
34
 
35
- `.canon/plans/`, `.canon/review/`, and `.canon/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. A session inside a worktree reads them directly, since the file-editing tools refuse a main-root path but `Read` resolves normally. It writes a whole file through the shell and makes a change inside an existing file through a `canon` verb, which resolves the main root in-process. See [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md) for the full rule and the domain-level fan-out guidance.
35
+ `.canon/plans/`, `.canon/review/`, and `.canon/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. A session inside a worktree reads them directly, since the file-editing tools refuse a main-root path but `Read` resolves normally. It writes a whole file through the shell and makes a change inside an existing file through a `canon` verb, which resolves the main root in-process. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the full rule and the domain-level fan-out guidance.
36
36
 
37
37
  The plan's shape is fixed by `standards/plan.md`: the section list, the filename, the lifecycle, and the contract its questions keep. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
38
38
 
@@ -6,7 +6,7 @@ category: Workflow
6
6
 
7
7
  # Skill map
8
8
 
9
- Groups run in the order a project meets them, so a reader at a known point scans to that group and reads across. The set reconciles the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/target-projects.md) describes, rather than inventing a third vocabulary beside those two, so a group name matches neither source exactly and every moment either one names has a group. Each row says when to reach for the skill. What it does is the skill's own description.
9
+ Groups run in the order a project meets them, so a reader at a known point scans to that group and reads across. The set reconciles the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, rather than inventing a third vocabulary beside those two, so a group name matches neither source exactly and every moment either one names has a group. Each row says when to reach for the skill. What it does is the skill's own description.
10
10
 
11
11
  This page is the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and a mention on any other page is prose rather than routing.
12
12
 
@@ -115,7 +115,6 @@ This page is the corpus the coverage claim is measured against: every name `cano
115
115
  | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
116
116
  | `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
117
117
  | `canon:create-skill` | For a new `SKILL.md` |
118
- | `canon:create-snippet` | For a reusable prompt |
119
118
  | `canon:create-standard` | For a new authoring convention |
120
119
  | `canon:draft-docs` | For a brand-new `docs/*.md` page, drafted against `standards/docs.md` |
121
120
  | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
@@ -8,9 +8,9 @@ category: Workflow
8
8
 
9
9
  Three tiers cover the range from prose-only design docs to a fully graphical design source of truth. Pick one per project based on how UI-heavy the work is, whether stakeholders review visuals, and whether a designer is involved. Tiers stack, so moving up does not invalidate work done at a lower tier.
10
10
 
11
- The tier framework sits alongside [Claude Design](../../wiki/claude/claude-design.md), the one catalog page in this range. Stitch, Excalidraw, and the surrounding MCP and skill ecosystem are named inline below rather than catalogued separately. This page decides when to reach for what.
11
+ The tier framework sits alongside [Claude Design](../../wiki/claude/design.md), the one catalog page in this range. Stitch, Excalidraw, and the surrounding MCP and skill ecosystem are named inline below rather than catalogued separately. This page decides when to reach for what.
12
12
 
13
- Two tools anchor tier 1 and tier 2. Stitch is the agent-addressable default through its MCP server at `stitch.googleapis.com/mcp`, with a free tier of 400 daily credits that covers daily iteration. [Claude Design](../../wiki/claude/claude-design.md), released 2026-04-17 and priced inside Claude subscriptions, is the ceiling tool reserved for codebase extraction and the richly annotated handoff bundle. Each covers a different job, they are not swappable.
13
+ Two tools anchor tier 1 and tier 2. Stitch is the agent-addressable default through its MCP server at `stitch.googleapis.com/mcp`, with a free tier of 400 daily credits that covers daily iteration. [Claude Design](../../wiki/claude/design.md), released 2026-04-17 and priced inside Claude subscriptions, is the ceiling tool reserved for codebase extraction and the richly annotated handoff bundle. Each covers a different job, they are not swappable.
14
14
 
15
15
  ## Tier 0: prose only
16
16
 
@@ -74,7 +74,7 @@ Impeccable, if installed, keeps its own root `DESIGN.md` and `PRODUCT.md` in the
74
74
  - Excalidraw canvas server on localhost plus the community [`yctimlin/mcp_excalidraw`](https://github.com/yctimlin/mcp_excalidraw) MCP shim, for projects that need an agent to draw, read back, and revise a canvas. MIT-licensed, plaintext JSON scene format. `describe_scene` and `get_canvas_screenshot` let the agent verify its own layout before claiming it is correct.
75
75
  - Playwright MCP ([`microsoft/playwright-mcp`](https://github.com/microsoft/playwright-mcp)) for browser-side verification, driven off the accessibility tree rather than screenshots.
76
76
  - Chrome DevTools MCP ([`ChromeDevTools/chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp)) for live frontend debugging: performance traces, network inspection, and DOM and CSS inspection.
77
- - Claude Design as the ceiling option for codebase extraction or polished handoff bundles. See [Claude Design](../../wiki/claude/claude-design.md).
77
+ - Claude Design as the ceiling option for codebase extraction or polished handoff bundles. See [Claude Design](../../wiki/claude/design.md).
78
78
 
79
79
  ### Skills
80
80
 
@@ -111,7 +111,7 @@ Design happens in a graphical tool. `canon/DESIGN.md` either regenerates from th
111
111
  ### Tools
112
112
 
113
113
  - Figma desktop app with the official [Figma Dev Mode MCP](https://www.figma.com/blog/introducing-claude-code-to-figma/) for teams with a dedicated designer already on Figma. Bidirectional sync and Code to Canvas capture of a running Claude Code UI into editable Figma frames.
114
- - Claude Design with its Claude Code handoff bundle for teams without an existing Figma investment and for solo founders or PMs driving design themselves. One-way handoff, no bidirectional sync. See [Claude Design](../../wiki/claude/claude-design.md).
114
+ - Claude Design with its Claude Code handoff bundle for teams without an existing Figma investment and for solo founders or PMs driving design themselves. One-way handoff, no bidirectional sync. See [Claude Design](../../wiki/claude/design.md).
115
115
  - Stitch via MCP as a low-cost complement to either, used for bulk screen generation driven by Claude Code.
116
116
  - Playwright and Chrome DevTools MCPs as in tier 1
117
117
 
@@ -149,7 +149,7 @@ Resist over-tiering early. Moving up is cheap because tiers stack. Moving down m
149
149
 
150
150
  ## References
151
151
 
152
- - [Claude Design](../../wiki/claude/claude-design.md): first-party hosted design product and handoff bundle
152
+ - [Claude Design](../../wiki/claude/design.md): first-party hosted design product and handoff bundle
153
153
  - `canon/context/claude-plugin/skill-strategy/overview.md`: how to decide between workflow and domain-knowledge skills
154
154
  - [`microsoft/playwright-mcp`](https://github.com/microsoft/playwright-mcp): browser automation MCP used in tier 1 and tier 2
155
155
  - [`ChromeDevTools/chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp): live frontend debugging MCP used in tier 1 and tier 2
@@ -68,7 +68,7 @@ Use `clp` in any other repository where you want the toolkit skills available. W
68
68
 
69
69
  Use `cls` or `clps` to save Opus usage on routine sessions. Switch mid-session with `/model` to avoid restarting.
70
70
 
71
- Use `clw <name>` for features that will take more than one session. A worktree isolates the branch, the transcripts, and the `/resume` history. See [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md) for fan-out rules.
71
+ Use `clw <name>` for features that will take more than one session. A worktree isolates the branch, the transcripts, and the `/resume` history. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for fan-out rules.
72
72
 
73
73
  Use `clc` to resume the last session without a picker. Use `clr` when you have several sessions and need to pick by name or recency. Reach for `clpc` over `clc` wherever the resumed session needs the plugin, inside the toolkit repository as well as outside it, since neither resume re-runs discovery.
74
74
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.127.0",
4
+ "version": "5.1.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -11,7 +11,6 @@
11
11
  "src",
12
12
  "scripts",
13
13
  "standards",
14
- "snippets",
15
14
  "governance",
16
15
  "tooling",
17
16
  "claude",
@@ -24,7 +24,7 @@ if ! command -v realpath >/dev/null 2>&1; then
24
24
  exit 1
25
25
  fi
26
26
 
27
- # The plugin reaches `standards/` and `snippets/` through symlinks, which an
27
+ # The plugin reaches `standards/` through a symlink, which an
28
28
  # installer dereferences with no code in the path to filter. Walking the plugin
29
29
  # tree with symlinks followed is what an install actually copies, so resolving
30
30
  # each file and rejecting anything under `internal/` asserts the boundary at the
@@ -88,10 +88,9 @@ log_step "Assert scaffold"
88
88
  # catches a failed domain, and the gate stays green while the target is
89
89
  # missing everything that domain provides.
90
90
  #
91
- # Standards and snippets name no path because neither corpus installs into a
92
- # target. A scaffold reads a standard through `canon standards <name>` and a
93
- # snippet through the plugin's live `claude/snippets` symlink, both resolving
94
- # against the toolkit rather than a copy this gate could assert on. The two
91
+ # Standards name no path because that corpus never installs into a target. A
92
+ # scaffold reads a standard through `canon standards <name>`, resolving against
93
+ # the toolkit rather than a copy this gate could assert on. The two
95
94
  # rule paths stand for the `claude` and `canon` groups `base` takes whole.
96
95
  for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" "canon/wireframes/index.md" ".canon/diagrams/index.md" \
97
96
  ".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
@@ -94,10 +94,9 @@ COUNTS_JSON="$(catalog gov counts || true)"
94
94
  SKILLS_JSON="$(catalog claude skills list)"
95
95
  GOV_JSON="$(catalog gov list)"
96
96
  STANDARDS_JSON="$(catalog standards list)"
97
- SNIPPETS_JSON="$(catalog snippets list)"
98
97
  TOOLING_JSON="$(catalog tooling list)"
99
98
 
100
- for payload in "$COUNTS_JSON" "$SKILLS_JSON" "$GOV_JSON" "$STANDARDS_JSON" "$SNIPPETS_JSON" "$TOOLING_JSON"; do
99
+ for payload in "$COUNTS_JSON" "$SKILLS_JSON" "$GOV_JSON" "$STANDARDS_JSON" "$TOOLING_JSON"; do
101
100
  if [ -z "$payload" ]; then
102
101
  echo "regen-hero: a catalog returned nothing, refusing to write a zeroed hero" >&2
103
102
  exit 1
@@ -115,7 +114,6 @@ printf '%s' "$COUNTS_JSON" >"$PAYLOAD_DIR/counts.json"
115
114
  printf '%s' "$SKILLS_JSON" >"$PAYLOAD_DIR/skills.json"
116
115
  printf '%s' "$GOV_JSON" >"$PAYLOAD_DIR/gov.json"
117
116
  printf '%s' "$STANDARDS_JSON" >"$PAYLOAD_DIR/standards.json"
118
- printf '%s' "$SNIPPETS_JSON" >"$PAYLOAD_DIR/snippets.json"
119
117
  printf '%s' "$TOOLING_JSON" >"$PAYLOAD_DIR/tooling.json"
120
118
  printf '%s' "$TOKEN_CSS" >"$PAYLOAD_DIR/tokens.css"
121
119
 
@@ -141,7 +139,6 @@ const COUNTS_JSON = payload("counts")
141
139
  const SKILLS_JSON = payload("skills")
142
140
  const GOV_JSON = payload("gov")
143
141
  const STANDARDS_JSON = payload("standards")
144
- const SNIPPETS_JSON = payload("snippets")
145
142
  const TOOLING_JSON = payload("tooling")
146
143
 
147
144
  const listed = Number(LISTED)
@@ -157,9 +154,6 @@ const rules = gov.rules.map(slug)
157
154
  const stacked = new Set(gov.stacks.flatMap((stack) => stack.rules))
158
155
  const deliveredRules = gov.rules.filter((entry) => stacked.has(entry.name)).map(slug)
159
156
  const standards = JSON.parse(STANDARDS_JSON).standards.map((entry) => entry.name)
160
- const snippets = new Set(
161
- JSON.parse(SNIPPETS_JSON).categories.flatMap((category) => category.entries),
162
- )
163
157
  const toolingStacks = JSON.parse(TOOLING_JSON).stacks
164
158
  const commandCount = JSON.parse(COUNTS_JSON).catalogs.commands
165
159
  if (!commandCount) {
@@ -247,7 +241,7 @@ const remaining = (names) => String(Math.max(0, names.length - listed))
247
241
  // frame claiming the domain has nothing in it.
248
242
  for (const [label, list] of [
249
243
  ["skills", skills], ["rules", rules], ["standards", standards],
250
- ["snippets", [...snippets]], ["tooling stacks", toolingStacks], ["gov stacks", gov.stacks],
244
+ ["tooling stacks", toolingStacks], ["gov stacks", gov.stacks],
251
245
  ["stack-delivered rules", deliveredRules],
252
246
  ]) {
253
247
  if (list.length === 0) {
@@ -354,7 +348,6 @@ const values = {
354
348
  SKILL_COUNT: String(skills.length),
355
349
  RULE_COUNT: String(rules.length),
356
350
  STANDARD_COUNT: String(standards.length),
357
- SNIPPET_COUNT: String(snippets.size),
358
351
  GOV_STACK_COUNT: String(gov.stacks.length),
359
352
  TOOLING_STACK_COUNT: String(toolingStacks.length),
360
353
  COMMAND_COUNT: String(commandCount),
@@ -16,10 +16,9 @@
16
16
  *
17
17
  * Most carry two spellings, the one a surface authors at and the one it reaches
18
18
  * a session at, so the rule reads the same in this toolkit and in a project
19
- * that consumed one. `standards/` and `snippets/` carry one apiece: neither
20
- * installs into a project, reaching a reader by resolution and through the
21
- * `claude/snippets` symlink respectively, so no `.claude/` spelling exists to
22
- * name.
19
+ * that consumed one. `standards/` carries one: it never installs into a
20
+ * project, reaching a reader by resolution, so no `.claude/` spelling exists
21
+ * to name.
23
22
  */
24
23
  export const BEHAVIOR_PREFIXES = [
25
24
  'claude/skills/',
@@ -27,7 +26,6 @@ export const BEHAVIOR_PREFIXES = [
27
26
  'governance/rules/',
28
27
  '.claude/rules/',
29
28
  'standards/',
30
- 'snippets/',
31
29
  'internal/',
32
30
  'tooling/',
33
31
  ] as const
@@ -1,7 +1,7 @@
1
1
  import type { SkillCase } from '@/claude/skills-rank'
2
2
 
3
3
  /**
4
- * Script, skill, snippet, standard, and doc authoring, plus the two prose
4
+ * Script, skill, standard, and doc authoring, plus the two prose
5
5
  * skills that revise rather than generate.
6
6
  */
7
7
  export const AUTHORING_CASES: readonly SkillCase[] = [
@@ -24,10 +24,6 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
24
24
  prompt: 'Scaffold a brand-new SKILL.md for this capability.',
25
25
  expect: 'create-skill',
26
26
  },
27
- {
28
- prompt: 'I need a new reusable prompt snippet added to the corpus.',
29
- expect: 'create-snippet',
30
- },
31
27
  {
32
28
  prompt: 'Write a fresh authoring convention as a new standard file.',
33
29
  expect: 'create-standard',
@@ -7,7 +7,7 @@ import { SURFACE_ROOTS } from '@/surface-root'
7
7
  * The authoring roots this repository owns and no install channel delivers.
8
8
  *
9
9
  * Every entry is a folder a target never holds under that spelling. Standards
10
- * and snippets install nowhere and are reached through the plugin corpus,
10
+ * install nowhere and are reached through the plugin corpus,
11
11
  * rules install under `.claude/rules/`, and the rest are this repository's
12
12
  * own source, docs, and catalogs.
13
13
  *
@@ -28,7 +28,6 @@ const AUTHORING_ROOTS = [
28
28
  'docs/agents/',
29
29
  'governance/',
30
30
  'internal/',
31
- 'snippets/',
32
31
  'standards/',
33
32
  'tooling/',
34
33
  'wiki/',
package/src/cli.ts CHANGED
@@ -6,7 +6,6 @@ import { register as sandbox } from '@/commands/sandbox'
6
6
  import { register as sync } from '@/commands/sync'
7
7
  import { register as gov } from '@/commands/gov'
8
8
  import { register as standards } from '@/commands/standards'
9
- import { register as snippets } from '@/commands/snippets'
10
9
  import { register as tooling } from '@/commands/tooling'
11
10
  import { register as claude } from '@/commands/claude'
12
11
  import { register as wiki } from '@/commands/wiki'
@@ -67,7 +66,6 @@ sandbox(program)
67
66
  sync(program)
68
67
  gov(program)
69
68
  standards(program)
70
- snippets(program)
71
69
  tooling(program)
72
70
  claude(program)
73
71
  wiki(program)
@@ -98,7 +98,6 @@ const DOMAIN_LABELS: readonly { pattern: RegExp; label: string }[] = [
98
98
  { pattern: /\b(?:cli)s?\b/, label: 'cli' },
99
99
  { pattern: /\b(?:tooling|seeds?)\b/, label: 'tooling' },
100
100
  { pattern: /\b(?:governance)s?\b/, label: 'governance' },
101
- { pattern: /\bsnippets?\b/, label: 'snippets' },
102
101
  ]
103
102
 
104
103
  /**
@@ -27,7 +27,7 @@ import {
27
27
  type RestatedRefusal,
28
28
  type RestatedReport,
29
29
  readRestated,
30
- RULES_REL as RESTATED_RULES_REL,
30
+ PROJECT_SKILLS_REL,
31
31
  SEED_REL,
32
32
  SHIPPED_SKILLS_REL,
33
33
  } from '@/gov/restated'
@@ -123,7 +123,7 @@ interface CountsOptions {
123
123
  /** What a reader does about each way the sweep produced no reading. */
124
124
  const RESTATED_REFUSALS: Record<RestatedRefusal, string> = {
125
125
  'no-instructions': `No ${INSTRUCTIONS_REL} here, or it carries no bullet, so there is no instruction corpus to sweep.`,
126
- 'no-surfaces': `None of ${SEED_REL}, ${SHIPPED_SKILLS_REL}/, or ${RESTATED_RULES_REL}/ is here, so no further surface exists to match against.`,
126
+ 'no-surfaces': `None of ${SEED_REL}, ${SHIPPED_SKILLS_REL}/, ${PROJECT_SKILLS_REL}/, ${RULE_DIRS.join('/, or ')}/ is here, so no further surface exists to match against.`,
127
127
  }
128
128
 
129
129
  const COUNTS_REFUSALS: Record<CountsRefusal, string> = {
@@ -275,8 +275,8 @@ export function register(program: Command): void {
275
275
  'members a closed catalog holds, then compares the stated figure',
276
276
  'against what the tree actually counts.',
277
277
  '',
278
- 'Catalogs read: skills, governance rules, standards, snippets, CLI',
279
- 'commands, and audit ids. Closed rather than derived, so widening the',
278
+ 'Catalogs read: skills, governance rules, standards, CLI commands,',
279
+ 'and audit ids. Closed rather than derived, so widening the',
280
280
  'set is a deliberate change rather than a side effect of a new list',
281
281
  'command shipping elsewhere.',
282
282
  '',
@@ -469,8 +469,9 @@ export function register(program: Command): void {
469
469
  [
470
470
  '',
471
471
  `Matches every bullet in ${INSTRUCTIONS_REL} and every rule under`,
472
- `${RESTATED_RULES_REL}/ against ${SEED_REL}, every`,
473
- `${SHIPPED_SKILLS_REL}/*/SKILL.md body, and every other rule. Matching is`,
472
+ `${RULE_DIRS.join('/ and ')}/ against ${SEED_REL},`,
473
+ `every ${SHIPPED_SKILLS_REL}/*/SKILL.md and ${PROJECT_SKILLS_REL}/*/SKILL.md body,`,
474
+ 'and every other rule. Matching is',
474
475
  'recall-first, keyed on distinctive tokens two statements share rather',
475
476
  'than on a phrase they spell the same way, because the case this exists',
476
477
  'for was one rule written three different ways.',
@@ -564,7 +565,7 @@ function reportRestated(
564
565
  // also says how wide the corpus behind it was.
565
566
  logStep('Corpus')
566
567
  logInfo(
567
- `${report.corpus.instructions} always-loaded instruction(s) and ${report.corpus.rules} rule file(s) against ${report.corpus.candidates} statement(s) from the seed, ${report.corpus.bodies} shipped body/bodies, and every rule again, in ${root}`,
568
+ `${report.corpus.instructions} always-loaded instruction(s) and ${report.corpus.rules} rule file(s) against ${report.corpus.candidates} statement(s) from the seed, ${report.corpus.bodies} skill body/bodies, and every rule again, in ${root}`,
568
569
  )
569
570
  logInfo(
570
571
  `matched on ${report.matcher.anchors} weighted anchor(s), dropping any token in more than ${report.matcher.common} statements`,
@@ -108,14 +108,14 @@ export function register(program: Command): void {
108
108
  'out of a standards file, so a project that installed no standards is',
109
109
  'measured the same as one that did. markdown.md still states every rule',
110
110
  'for a reader. No folder has to resolve and no index.md has',
111
- 'to exist, so .claude/rules/, governance/, and snippets/ are in reach.',
111
+ 'to exist, so .claude/rules/ and governance/ are in reach.',
112
112
  '',
113
113
  'Examples:',
114
114
  ' canon markdown audit',
115
115
  ' canon markdown audit --json',
116
116
  ' canon markdown audit .claude/rules governance',
117
117
  ' canon markdown audit docs/agents/commands.md',
118
- " canon markdown audit 'snippets/**/*.md'",
118
+ " canon markdown audit 'standards/**/*.md'",
119
119
  '',
120
120
  ].join('\n'),
121
121
  )
@@ -3,7 +3,6 @@ import { join } from 'node:path'
3
3
  import { AUDITS } from '@/audits/catalog'
4
4
  import { listSkills } from '@/claude/skills-list'
5
5
  import { buildGovCatalog } from '@/gov/list'
6
- import { buildSnippetsCatalog } from '@/snippets/list'
7
6
  import { listStandards } from '@/standards/read'
8
7
 
9
8
  export interface Catalog {
@@ -39,16 +38,6 @@ function countCommands(root: string): number | undefined {
39
38
  return [...readFileSync(cli, 'utf8').matchAll(REGISTER_IMPORT)].length
40
39
  }
41
40
 
42
- /**
43
- * The distinct entries across every category, matching what `regen-hero.sh`
44
- * counts. A snippet reachable from two categories is one snippet, not two.
45
- */
46
- function countSnippets(root: string): number {
47
- const catalog = buildSnippetsCatalog(root)
48
- return new Set(catalog.categories.flatMap((category) => category.entries))
49
- .size
50
- }
51
-
52
41
  /**
53
42
  * The closed set of catalogs this sweep counts a document against.
54
43
  *
@@ -75,11 +64,6 @@ export const CATALOGS: readonly Catalog[] = [
75
64
  nouns: ['standard', 'standards'],
76
65
  count: (root) => listStandards(root).length,
77
66
  },
78
- {
79
- id: 'snippets',
80
- nouns: ['snippet', 'snippets'],
81
- count: countSnippets,
82
- },
83
67
  {
84
68
  id: 'commands',
85
69
  nouns: ['command', 'commands'],
@@ -153,7 +153,7 @@ const ARTICLES = ['the', 'a', 'an']
153
153
  * either number.
154
154
  *
155
155
  * The optional word between the number and the noun is what reaches a form
156
- * like `installs 11 shipped snippets` without also reaching past an
156
+ * like `installs 11 shipped rules` without also reaching past an
157
157
  * intervening clause, since a wider gap would start pairing a number in one
158
158
  * clause with a noun in the next. Requiring the noun's own plural form to
159
159
  * double as its match narrows the corpus this reads without hand-listing
package/src/docs/read.ts CHANGED
@@ -56,22 +56,22 @@ export function resolveTopic(
56
56
  }
57
57
 
58
58
  /**
59
- * Names every sub-area file called `<topic>.md`, across both roots. A name
60
- * carried by more than one folder resolves to none of them, since answering a
61
- * bare `overview` with whichever folder sorts first is a confident wrong answer
62
- * where the miss is a listing of what the caller could have typed.
59
+ * Names every sub-area file called `<topic>.md` in the first root whose folders
60
+ * carry one, so the root precedence above holds one folder down as well. A name
61
+ * carried by more than one folder of that root resolves to none of them, since
62
+ * answering a bare `overview` with whichever folder sorts first is a confident
63
+ * wrong answer where the miss is a listing of what the caller could have typed.
63
64
  */
64
65
  function findLeaves(root: string, topic: string): string[] {
65
- const matches: string[] = []
66
-
67
66
  for (const dir of ROOTS) {
68
- for (const folder of catalogedFolders(join(root, dir))) {
69
- const rel = join(dir, folder, `${topic}.md`)
70
- if (existsSync(join(root, rel))) matches.push(rel)
71
- }
67
+ const matches = catalogedFolders(join(root, dir))
68
+ .map((folder) => join(dir, folder, `${topic}.md`))
69
+ .filter((rel) => existsSync(join(root, rel)))
70
+
71
+ if (matches.length > 0) return matches
72
72
  }
73
73
 
74
- return matches
74
+ return []
75
75
  }
76
76
 
77
77
  /**
@@ -100,20 +100,20 @@ export function listTopics(root: string): string[] {
100
100
  const leaves = ROOTS.map((dir) => collectLeaves(root, dir))
101
101
 
102
102
  const taken = new Set(named.flat())
103
- const counts = new Map<string, number>()
104
- for (const name of leaves.flat()) {
105
- counts.set(name, (counts.get(name) ?? 0) + 1)
106
- }
107
-
108
- const reachable = (name: string): boolean =>
109
- !taken.has(name) && counts.get(name) === 1
110
-
111
- return ROOTS.flatMap((_, position) =>
112
- [
113
- ...(named[position] as string[]),
114
- ...(leaves[position] as string[]).filter(reachable),
115
- ].sort(),
116
- )
103
+ const claimed = new Set<string>()
104
+
105
+ return ROOTS.flatMap((_, position) => {
106
+ const here = leaves[position] as string[]
107
+ const reachable = [...new Set(here)].filter(
108
+ (name) =>
109
+ !taken.has(name) &&
110
+ !claimed.has(name) &&
111
+ here.indexOf(name) === here.lastIndexOf(name),
112
+ )
113
+ for (const name of here) claimed.add(name)
114
+
115
+ return [...(named[position] as string[]), ...reachable].sort()
116
+ })
117
117
  }
118
118
 
119
119
  /** The sibling files and split folders of one root, which shadow every leaf. */