@erclx/canon 4.122.0 → 4.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +7 -8
- package/claude/skills/auto-ship/references/tier-failures.md +7 -4
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/codebase-layout/REQUIREMENT.md +1 -1
- package/claude/skills/codebase-layout/SKILL.md +1 -1
- package/claude/skills/create-rule/SKILL.md +5 -2
- package/claude/skills/design-taste/SKILL.md +1 -1
- package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/SKILL.md +1 -1
- package/claude/skills/git-ship/SKILL.md +4 -3
- package/claude/skills/memory-capture/SKILL.md +14 -4
- package/claude/skills/memory-review/REQUIREMENT.md +4 -2
- package/claude/skills/memory-review/SKILL.md +18 -7
- package/claude/skills/memory-review/references/later-phases.md +7 -4
- package/claude/skills/memory-review/references/receipt-format.md +19 -3
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/session-resume/SKILL.md +4 -3
- package/claude/skills/task-board/SKILL.md +1 -1
- package/claude/skills/task-board/references/archive.md +1 -1
- package/claude/skills/task-board/references/decline.md +1 -1
- package/claude/skills/test-first/REQUIREMENT.md +1 -1
- package/docs/agents/counts.md +3 -3
- package/docs/agents/drift-surfaces.md +2 -2
- package/docs/agents/rule-citations.md +4 -4
- package/docs/agents/tasks-archive.md +3 -3
- package/docs/agents/tasks-plans.md +5 -3
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/tasks.md +1 -1
- package/docs/agents/test-order.md +1 -1
- package/docs/index.md +3 -0
- package/docs/target-migrations.md +93 -0
- package/docs/target-projects.md +4 -216
- package/docs/target-scenarios.md +56 -0
- package/docs/target-sync.md +93 -0
- package/docs/workflow/ai-workflow.md +4 -203
- package/docs/workflow/index.md +2 -0
- package/docs/workflow/operating-model.md +18 -47
- package/docs/workflow/parallel-features.md +77 -0
- package/docs/workflow/skill-map.md +148 -0
- package/governance/renames.toml +34 -0
- package/governance/rules/{claude/555-tasks.md → canon/655-tasks.md} +2 -1
- package/governance/rules/{claude/560-diagrams.md → canon/660-diagrams.md} +1 -1
- package/governance/rules/{claude/562-session.md → canon/662-session.md} +1 -1
- package/governance/rules/code/000-code.md +21 -0
- package/governance/rules/lib/300-testing-ts.md +0 -4
- package/governance/rules/lib/330-testing-py.md +0 -4
- package/governance/rules/{claude/500-prose.md → writing/800-prose.md} +1 -1
- package/governance/rules/{claude/501-markdown.md → writing/801-markdown.md} +1 -1
- package/governance/rules/{claude/502-mermaid.md → writing/802-mermaid.md} +1 -1
- package/governance/stacks/base.toml +7 -6
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +3 -5
- package/src/claude/routing.ts +1 -1
- package/src/commands/gov.ts +1 -1
- package/src/commands/tasks.ts +39 -29
- package/src/gate/measures.ts +1 -6
- package/src/gate/stages.ts +1 -0
- package/src/gov/citations.ts +1 -1
- package/src/gov/list.ts +1 -1
- package/src/gov/restated.ts +2 -2
- package/src/init/steps.ts +1 -1
- package/src/labels/spelling.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/shipped/references.ts +1 -1
- package/src/tasks/archive.ts +212 -105
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/record.ts +59 -16
- package/src/tasks/validate.ts +1 -1
- package/standards/board.md +136 -0
- package/standards/commit.md +5 -0
- package/standards/groundwork.md +25 -36
- package/standards/index.md +2 -1
- package/standards/plan.md +20 -34
- package/standards/readme.md +8 -77
- package/standards/requirements.md +2 -2
- package/standards/rule.md +4 -6
- package/standards/tasks.md +18 -125
- package/standards/versioning.md +1 -1
- package/tooling/base/configs/commitlint.config.js +27 -1
- package/tooling/base/reference.md +2 -2
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +1 -1
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +1 -1
- package/tooling/web/reference.md +1 -1
- package/governance/rules/core/000-constitution.md +0 -30
- package/governance/rules/core/020-concurrency.md +0 -22
- package/governance/rules/core/030-error-handling.md +0 -31
- package/governance/rules/core/040-performance.md +0 -20
- package/governance/rules/core/050-logging.md +0 -20
- package/governance/rules/core/060-naming.md +0 -19
- package/governance/rules/snippets/600-at-references.md +0 -9
- /package/governance/rules/{core/025-indexes.md → canon/601-indexes.md} +0 -0
- /package/governance/rules/{core/035-tasks.md → canon/602-tasks.md} +0 -0
- /package/governance/rules/{core/045-memory.md → canon/603-memory.md} +0 -0
- /package/governance/rules/{core/055-scratch.md → canon/604-scratch.md} +0 -0
- /package/governance/rules/{core/085-worktrees.md → canon/605-worktrees.md} +0 -0
- /package/governance/rules/{core/087-git.md → canon/606-git.md} +0 -0
- /package/governance/rules/{claude/510-context.md → canon/610-context.md} +0 -0
- /package/governance/rules/{claude/511-indexes.md → canon/611-indexes.md} +0 -0
- /package/governance/rules/{claude/520-wireframes.md → canon/620-wireframes.md} +0 -0
- /package/governance/rules/{claude/530-requirements.md → canon/630-requirements.md} +0 -0
- /package/governance/rules/{claude/540-architecture.md → canon/640-architecture.md} +0 -0
- /package/governance/rules/{claude/545-decisions.md → canon/645-decisions.md} +0 -0
- /package/governance/rules/{claude/550-design.md → canon/650-design.md} +0 -0
- /package/governance/rules/{claude/556-groundwork.md → canon/656-groundwork.md} +0 -0
- /package/governance/rules/{claude/557-intake.md → canon/657-intake.md} +0 -0
- /package/governance/rules/{claude/558-plan.md → canon/658-plan.md} +0 -0
- /package/governance/rules/{claude/559-memory.md → canon/659-memory.md} +0 -0
- /package/governance/rules/{claude/561-teach.md → canon/661-teach.md} +0 -0
- /package/governance/rules/{claude/563-ready.md → canon/663-ready.md} +0 -0
- /package/governance/rules/{core/005-behavior.md → claude/565-behavior.md} +0 -0
- /package/governance/rules/{core/015-output.md → claude/566-output.md} +0 -0
- /package/governance/rules/{core/070-planning.md → claude/567-planning.md} +0 -0
- /package/governance/rules/{core → code}/010-testing.md +0 -0
- /package/governance/rules/{ci → tooling}/700-ci-workflow.md +0 -0
- /package/governance/rules/{core/065-spelling.md → tooling/765-spelling.md} +0 -0
- /package/governance/rules/{core/075-dependencies.md → tooling/775-dependencies.md} +0 -0
- /package/governance/rules/{core/080-config-comments.md → tooling/780-config-comments.md} +0 -0
- /package/governance/rules/{core/090-code-comments.md → tooling/790-code-comments.md} +0 -0
- /package/governance/rules/{claude/580-readme.md → writing/880-readme.md} +0 -0
- /package/governance/rules/{claude/591-standard-authoring.md → writing/891-standard-authoring.md} +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Skill map
|
|
3
|
+
description: When to reach for each plugin skill, grouped by the moment a project meets it
|
|
4
|
+
category: Workflow
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill map
|
|
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-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
|
+
|
|
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
|
+
|
|
13
|
+
## Set up a project
|
|
14
|
+
|
|
15
|
+
| Skill | When to use |
|
|
16
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
17
|
+
| `canon:target-setup` | On a fresh scaffold, to detect the stack and run the install chain, or to reach one phase of it alone |
|
|
18
|
+
| `canon:canon-operator` | On a project that already exists, to read what it carries before an install is picked |
|
|
19
|
+
| `canon:sketch-design` | Before `design-extract`'s greenfield path, to trace a design direction from reference images or URLs |
|
|
20
|
+
| `canon:design-extract` | Before the first UI feature, to draft `canon/DESIGN.md` |
|
|
21
|
+
| `canon:draft-diagram` | Once the architecture is written, to draft per-kind entries under `.canon/diagrams/` |
|
|
22
|
+
| `canon:repo-metadata` | When the GitHub About text, homepage, or topics may have drifted, to reconcile them against the README |
|
|
23
|
+
|
|
24
|
+
## Decide what to build
|
|
25
|
+
|
|
26
|
+
| Skill | When to use |
|
|
27
|
+
| -------------------------- | --------------------------------------------------------------------------------- |
|
|
28
|
+
| `canon:plan-intake` | When the input is a pile of findings rather than one feature |
|
|
29
|
+
| `canon:plan-intake-answer` | When an intake folder holds unread slots waiting on your decision |
|
|
30
|
+
| `canon:backlog-triage` | When the backlog needs pruning, to file a verdict per row and apply approved ones |
|
|
31
|
+
| `canon:plan-groundwork` | When the state is unmeasured and more than one approach is live |
|
|
32
|
+
| `canon:decision-escalate` | When open decisions turn on your preference and want batching into one set |
|
|
33
|
+
| `canon:draft-and-pick` | When the call is taste and wants several candidates rendered side by side |
|
|
34
|
+
| `canon:task-board` | When a decided item needs a file on the board, or a shipped one needs archiving |
|
|
35
|
+
| `canon:plan-feature` | When the approach is settled and the next step is a plan |
|
|
36
|
+
| `canon:codebase-layout` | When a plan or change adds a file and you need to decide which folder holds it |
|
|
37
|
+
|
|
38
|
+
## Build the feature
|
|
39
|
+
|
|
40
|
+
| Skill | When to use |
|
|
41
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `canon:session-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch, or to route a refused main-root write |
|
|
43
|
+
| `canon:auto-ship` | After plan approval, to chain implement, verify, review, draft PR |
|
|
44
|
+
| `canon:project-commands` | When the project's own command needs running |
|
|
45
|
+
| `canon:test-first` | Before implementing a planned change, to run its test red, green, then refactor |
|
|
46
|
+
| `canon:test-craft` | When writing or changing any test, to pick its layer and filter what it asserts |
|
|
47
|
+
| `canon:review-craft` | When reviewing a change, for what to look for and how much evidence a finding needs |
|
|
48
|
+
| `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
|
|
49
|
+
| `canon:ui-checklist` | After a UI change, to write what to look at and name what ships untested |
|
|
50
|
+
|
|
51
|
+
## Check the work before it leaves the branch
|
|
52
|
+
|
|
53
|
+
| Skill | When to use |
|
|
54
|
+
| ------------------------ | --------------------------------------------------------------------------------------- |
|
|
55
|
+
| `canon:review-branch` | On the local branch diff, before anything is pushed |
|
|
56
|
+
| `canon:standards-audit` | When changed markdown has to answer to the authoring standards |
|
|
57
|
+
| `canon:document-health` | When the documents themselves have to answer for length, placement, and staleness |
|
|
58
|
+
| `canon:markdown-propose` | When a markdown claim needs rewriting and the change should wait for an answer per file |
|
|
59
|
+
| `canon:ux-audit` | To read UI source for missing states, edge cases, and inconsistencies |
|
|
60
|
+
| `canon:ux-measure` | To start the interface and measure paint, processor, and layout cost |
|
|
61
|
+
| `canon:ux-walkthrough` | To run a multi-finding inspection pass over a running app with the operator |
|
|
62
|
+
|
|
63
|
+
## Ship it
|
|
64
|
+
|
|
65
|
+
| Skill | When to use |
|
|
66
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `canon:git-ship` | To run the whole post-feature chain from the verify gate through open PR |
|
|
68
|
+
| `canon:memory-capture` | First skill in that chain, to route what the session learned to the surface owning it |
|
|
69
|
+
| `canon:context-fold` | When decisions diverged from the plan, or a shipped task needs its outcomes marked |
|
|
70
|
+
| `canon:docs-fold` | Never on purpose. A pointer at the old name that names `context-fold` <!-- canon-keep-retired --> |
|
|
71
|
+
| `canon:docs-sync` | When a change since main left `README.md` or `docs/` stale |
|
|
72
|
+
| `canon:git-stage` | When the staged set spans several concerns and wants one commit each |
|
|
73
|
+
| `canon:git-commit` | When the staged set is one concern, or was staged hunk by hand |
|
|
74
|
+
| `canon:git-branch` | When a branch name needs generating or renaming to conventional form |
|
|
75
|
+
| `canon:git-pr` | When a pull request needs a title and body written from the diff |
|
|
76
|
+
| `canon:memory-review` | When the pen has grown, to propose where each entry belongs |
|
|
77
|
+
|
|
78
|
+
## After the pull request opens
|
|
79
|
+
|
|
80
|
+
| Skill | When to use |
|
|
81
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
82
|
+
| `canon:review-pr` | From an independent session, to post findings on the PR itself |
|
|
83
|
+
| `canon:review-address` | On the worker's side, to fix posted findings and push a follow-up |
|
|
84
|
+
| `canon:git-followup` | For a small self-review edit on a branch whose PR is already open |
|
|
85
|
+
| `canon:git-split` | When a branch turns out to carry unrelated commits |
|
|
86
|
+
| `canon:git-issue` | When something surfaced that belongs on the tracker rather than here |
|
|
87
|
+
| `canon:git-worktree` | After a PR merges, to list worktrees and reclaim the slot |
|
|
88
|
+
|
|
89
|
+
## Run several tracks at once
|
|
90
|
+
|
|
91
|
+
| Skill | When to use |
|
|
92
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `canon:role-orchestrator` | To assert the control session that owns the queue and reviews each worker's PR |
|
|
94
|
+
| `canon:role-planner` | To assert the thinking role for a cold session writing a plan, a groundwork track, or an intake pass |
|
|
95
|
+
| `canon:role-worker` | To assert the worker role for a cold session building one branch under one plan |
|
|
96
|
+
| `canon:session-relay` | When this session owes another session a message, with or without a role |
|
|
97
|
+
| `canon:session-resume` | At the start of a session, to pick up what a previous one left |
|
|
98
|
+
| `canon:session-compact` | Before a compaction or a move to another machine, to write a plain session's handoff note outside the board |
|
|
99
|
+
| `canon:session-map` | At the close of an orchestrating session, to write its board-side handoff |
|
|
100
|
+
|
|
101
|
+
## Keep the project current with the toolkit
|
|
102
|
+
|
|
103
|
+
| Skill | When to use |
|
|
104
|
+
| ----------------------------- | -------------------------------------------------------------------------------------- |
|
|
105
|
+
| `canon:target-check` | In a target project, to report per domain what it holds against what the toolkit ships |
|
|
106
|
+
| `canon:seed-sync` | After a toolkit update, to reconcile installed seeds without losing customizations |
|
|
107
|
+
| `canon:canon-feedback` | When something in the toolkit is broken, missing, or off, to open an issue on it |
|
|
108
|
+
| `canon:canon-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
|
|
109
|
+
| `canon:canon-rollout` | In the toolkit repo, to take one change out to every consuming project at once |
|
|
110
|
+
|
|
111
|
+
## Generate an artifact on demand
|
|
112
|
+
|
|
113
|
+
| Skill | When to use |
|
|
114
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
|
|
116
|
+
| `canon:create-skill` | For a new `SKILL.md` |
|
|
117
|
+
| `canon:create-snippet` | For a reusable prompt |
|
|
118
|
+
| `canon:create-standard` | For a new authoring convention |
|
|
119
|
+
| `canon:draft-docs` | For a brand-new `docs/*.md` page, drafted against `standards/docs.md` |
|
|
120
|
+
| `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
|
|
121
|
+
| `canon:draft-context` | For a brand-new `canon/context/<domain>.md` entry, drafted against `standards/context.md` |
|
|
122
|
+
| `canon:draft-wireframes` | For a brand-new `canon/wireframes/<surface>.md` file, drafted against `standards/wireframes.md` |
|
|
123
|
+
| `canon:draft-wiki` | For a brand-new wiki reference page on a subject owned outside the project, drafted against `standards/wiki.md` |
|
|
124
|
+
| `canon:draft-figure` | For a hand-drawn figure inside an existing doc, drafted against `standards/figures.md` in Mermaid or freehand SVG |
|
|
125
|
+
| `canon:draft-readme` | For a project's `README.md`, drafted against `standards/readme.md` |
|
|
126
|
+
| `canon:bash-script` | For an interactive, human-facing shell tool |
|
|
127
|
+
| `canon:bash-cli-script` | For a non-interactive automation, CI, or pipeline script |
|
|
128
|
+
| `canon:ci-workflow` | For a GitHub Actions workflow file |
|
|
129
|
+
| `canon:draft-slides` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
|
|
130
|
+
| `canon:draft-screencast` | For a recording script with beats and defaults already seeded |
|
|
131
|
+
| `canon:record-screencast` | For compiling and running a screencast draft into a recording and a still |
|
|
132
|
+
| `canon:draft-identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
|
|
133
|
+
|
|
134
|
+
## Answer a question at any point
|
|
135
|
+
|
|
136
|
+
| Skill | When to use |
|
|
137
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
138
|
+
| `canon:canon-cli` | Before running an unfamiliar verb, a sync, or an install, to learn which command to run, which reference doc covers it, or what it overwrites, merges, or leaves alone |
|
|
139
|
+
| `canon:index-lookup` | To find where a topic is documented across the tracked `index.md` catalogs |
|
|
140
|
+
| `canon:youtube-transcripts` | When a video transcript is wanted in the repo as context |
|
|
141
|
+
| `canon:read-frames` | To read a recorded demo back frame by frame and report what each one shows, with no verdict on whether the recording looks right |
|
|
142
|
+
| `canon:teach-workspace` | To learn a subject across sessions, in a workspace that holds the progress |
|
|
143
|
+
| `canon:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
|
|
144
|
+
| `canon:restate-plainly` | When an answer or a document has to be read again in plain words |
|
|
145
|
+
|
|
146
|
+
Every row answers a question rather than marking a point in a project's life, so a phase above would send a reader to the wrong group.
|
|
147
|
+
|
|
148
|
+
A learning workspace produces two halves and only one of them leaves. A lesson is worked through once and stays in the workspace, and a reference page or a glossary carries no learner, so it belongs wherever the project already keeps prose on that subject. Asking `canon:teach-workspace` to promote sorts each durable page by who owns its subject, sending an Anthropic-owned subject to the wiki, an internal one to the matching context entry, and everything else, subject-neutral material included, to the public docs. It proposes and waits, because a promoted page is public prose that needs a line naming who owns the subject, and it writes nothing to a destination: each page the operator confirms goes to a handoff file that `canon:context-fold` folds in from a branch. A project with no wiki folder gets a refusal naming `canon wiki init` rather than a folder it never asked for.
|
package/governance/renames.toml
CHANGED
|
@@ -11,3 +11,37 @@
|
|
|
11
11
|
# stay. A key must name no live rule and every chain must end on one, which
|
|
12
12
|
# `src/gov/renames.test.ts` checks against this file.
|
|
13
13
|
[renamed]
|
|
14
|
+
"005-behavior" = "565-behavior"
|
|
15
|
+
"015-output" = "566-output"
|
|
16
|
+
"070-planning" = "567-planning"
|
|
17
|
+
"025-indexes" = "601-indexes"
|
|
18
|
+
"035-tasks" = "602-tasks"
|
|
19
|
+
"045-memory" = "603-memory"
|
|
20
|
+
"055-scratch" = "604-scratch"
|
|
21
|
+
"085-worktrees" = "605-worktrees"
|
|
22
|
+
"087-git" = "606-git"
|
|
23
|
+
"510-context" = "610-context"
|
|
24
|
+
"511-indexes" = "611-indexes"
|
|
25
|
+
"520-wireframes" = "620-wireframes"
|
|
26
|
+
"530-requirements" = "630-requirements"
|
|
27
|
+
"540-architecture" = "640-architecture"
|
|
28
|
+
"545-decisions" = "645-decisions"
|
|
29
|
+
"550-design" = "650-design"
|
|
30
|
+
"555-tasks" = "655-tasks"
|
|
31
|
+
"556-groundwork" = "656-groundwork"
|
|
32
|
+
"557-intake" = "657-intake"
|
|
33
|
+
"558-plan" = "658-plan"
|
|
34
|
+
"559-memory" = "659-memory"
|
|
35
|
+
"560-diagrams" = "660-diagrams"
|
|
36
|
+
"561-teach" = "661-teach"
|
|
37
|
+
"562-session" = "662-session"
|
|
38
|
+
"563-ready" = "663-ready"
|
|
39
|
+
"500-prose" = "800-prose"
|
|
40
|
+
"501-markdown" = "801-markdown"
|
|
41
|
+
"502-mermaid" = "802-mermaid"
|
|
42
|
+
"580-readme" = "880-readme"
|
|
43
|
+
"591-standard-authoring" = "891-standard-authoring"
|
|
44
|
+
"065-spelling" = "765-spelling"
|
|
45
|
+
"075-dependencies" = "775-dependencies"
|
|
46
|
+
"080-config-comments" = "780-config-comments"
|
|
47
|
+
"090-code-comments" = "790-code-comments"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Route .canon/tasks/ edits to the tasks standard for
|
|
2
|
+
description: Route .canon/tasks/ edits to the tasks standard for task files and the board standard for priority.md and backlog.md
|
|
3
3
|
paths:
|
|
4
4
|
- '.canon/tasks/**'
|
|
5
5
|
---
|
|
@@ -9,4 +9,5 @@ paths:
|
|
|
9
9
|
## Authority
|
|
10
10
|
|
|
11
11
|
- Follow the tasks standard for filenames, frontmatter, what belongs, and the task file format. It is the single source. Read it with `canon standards tasks`.
|
|
12
|
+
- Follow the board standard for `priority.md` and `backlog.md`, their readiness groups, and row order. Read it with `canon standards board`.
|
|
12
13
|
- Never hand-edit `.canon/tasks/index.md`. A hook regenerates it from sibling frontmatter.
|
|
@@ -10,7 +10,7 @@ paths:
|
|
|
10
10
|
## Authority
|
|
11
11
|
|
|
12
12
|
- Follow the diagrams standard for which question an entry answers, the entry kinds and their source signals, the frontmatter, and the explanation prose beneath the fence. It is the single source. Read it with `canon standards diagrams`.
|
|
13
|
-
- Layout, budgets, accessibility, labels, and render verification inside the fence are a separate topic. `
|
|
13
|
+
- Layout, budgets, accessibility, labels, and render verification inside the fence are a separate topic. `802-mermaid` routes them to `mermaid.md`.
|
|
14
14
|
- A diagram entry carries structure and flow, not implementation. Read the standard before adding or revising a kind.
|
|
15
15
|
|
|
16
16
|
## Scope
|
|
@@ -8,7 +8,7 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
## What a handoff carries
|
|
10
10
|
|
|
11
|
-
- Follow this rule rather than `
|
|
11
|
+
- Follow this rule rather than `655-tasks.md` for a `session-` file. A handoff is not a task and carries neither `## Outcomes` nor `## Findings`.
|
|
12
12
|
|
|
13
13
|
## Authority
|
|
14
14
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Code placement, dependency, error, and naming choices a model does not make by default
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Code standards
|
|
6
|
+
|
|
7
|
+
## Placement
|
|
8
|
+
|
|
9
|
+
- Load the `canon:codebase-layout` skill before placing a new file, and report it rather than proceeding silently when it does not resolve.
|
|
10
|
+
- Prioritize native platform capabilities over third-party libraries.
|
|
11
|
+
- Ensure data and configuration reside in designated Single Source of Truth locations.
|
|
12
|
+
|
|
13
|
+
## Errors
|
|
14
|
+
|
|
15
|
+
- Return structured error types for recoverable failures.
|
|
16
|
+
- Never expose internal implementation details in error messages.
|
|
17
|
+
|
|
18
|
+
## Naming
|
|
19
|
+
|
|
20
|
+
- Prefix booleans with `is`, `has`, `should`, or `can`: `isLoading`, `hasAccess`.
|
|
21
|
+
- Prefix event handlers with `handle`: `handleClick`, `handleSubmit`.
|
|
@@ -9,10 +9,6 @@ paths:
|
|
|
9
9
|
|
|
10
10
|
# TypeScript/JavaScript testing tooling
|
|
11
11
|
|
|
12
|
-
## Layer
|
|
13
|
-
|
|
14
|
-
- Load the `canon:test-craft` skill to pick the layer a test belongs at before choosing the tooling below, and report it rather than proceeding silently when the skill does not resolve.
|
|
15
|
-
|
|
16
12
|
## Unit and integration
|
|
17
13
|
|
|
18
14
|
- Use Vitest for unit and integration tests.
|
|
@@ -8,10 +8,6 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
# Python testing tooling
|
|
10
10
|
|
|
11
|
-
## Layer
|
|
12
|
-
|
|
13
|
-
- Load the `canon:test-craft` skill to pick the layer a test belongs at before choosing the tooling below, and report it rather than proceeding silently when the skill does not resolve.
|
|
14
|
-
|
|
15
11
|
## Framework
|
|
16
12
|
|
|
17
13
|
- Use `pytest` for all tests. Do not use `unittest.TestCase`.
|
|
@@ -12,4 +12,4 @@ paths:
|
|
|
12
12
|
- Load it before drafting a passage, not after revising one.
|
|
13
13
|
- Report it rather than proceeding silently when the skill does not resolve.
|
|
14
14
|
- Do not work these rules from memory.
|
|
15
|
-
- Punctuation, formatting, and file references are a separate topic. `
|
|
15
|
+
- Punctuation, formatting, and file references are a separate topic. `801-markdown` routes them.
|
|
@@ -11,4 +11,4 @@ paths:
|
|
|
11
11
|
- Follow the markdown standard for headings, paragraph and list structure, code spans, punctuation, emphasis, file references, and the wording of a `title` or `description`. It is the single source. Read it with `canon standards markdown`.
|
|
12
12
|
- Read it before a substantial markdown edit. Do not work the banned characters from memory.
|
|
13
13
|
- Run `canon markdown audit <path>` after the edit, and rewrite the sentence carrying a hit rather than swapping the token for a near-synonym.
|
|
14
|
-
- Voice, rhythm, and sentence construction are a separate topic. `
|
|
14
|
+
- Voice, rhythm, and sentence construction are a separate topic. `800-prose` routes them.
|
|
@@ -11,4 +11,4 @@ paths:
|
|
|
11
11
|
- Apply this rule only when the edit drafts or revises a Mermaid fence. Skip it on a markdown edit carrying no diagram.
|
|
12
12
|
- Follow the mermaid standard for direction and layout, node and edge budgets, the accessibility fields, punctuation inside labels, and how a render is verified. It is the single source. Read it with `canon standards mermaid`.
|
|
13
13
|
- Read it before drafting the fence. Do not work the layout or the budgets from memory.
|
|
14
|
-
- Which diagram a document carries and what question it answers is a separate topic. `
|
|
14
|
+
- Which diagram a document carries and what question it answers is a separate topic. `660-diagrams` routes it.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
extends = ""
|
|
2
|
-
#
|
|
2
|
+
# Four audience groups whole. Every rule authored under them ships to a base
|
|
3
3
|
# consumer, so adding one is a single edit rather than a file and a name here.
|
|
4
|
-
# `
|
|
5
|
-
#
|
|
6
|
-
# workflow
|
|
7
|
-
#
|
|
8
|
-
|
|
4
|
+
# `canon` carries the toolkit workflow and canonical-doc rules, `claude` the
|
|
5
|
+
# session conduct and `.claude/` authoring rules, `tooling` the dev setup and
|
|
6
|
+
# CI workflow rules, and `writing` the prose and document-shape rules. The
|
|
7
|
+
# `code` group is left to the stacks that write code, so a docs or writing
|
|
8
|
+
# project loads no rule it can never apply.
|
|
9
|
+
rules = ["canon", "claude", "tooling", "writing", "120-bash"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["100-typescript"]
|
|
2
|
+
rules = ["code", "100-typescript"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["110-python", "340-pydantic", "330-testing-py", "360-security-server", "370-database"]
|
|
2
|
+
rules = ["code", "110-python", "340-pydantic", "330-testing-py", "360-security-server", "370-database"]
|
package/package.json
CHANGED
|
@@ -91,13 +91,11 @@ log_step "Assert scaffold"
|
|
|
91
91
|
# Standards and snippets name no path because neither corpus installs into a
|
|
92
92
|
# target. A scaffold reads a standard through `canon standards <name>` and a
|
|
93
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
|
|
95
|
-
#
|
|
96
|
-
# installs, since `base` carries `governance/rules/snippets/` as a
|
|
97
|
-
# folder-whole entry the same way it does `core` and `claude`.
|
|
94
|
+
# against the toolkit rather than a copy this gate could assert on. The two
|
|
95
|
+
# rule paths stand for the `claude` and `canon` groups `base` takes whole.
|
|
98
96
|
for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" "canon/wireframes/index.md" ".canon/diagrams/index.md" \
|
|
99
97
|
".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
|
|
100
|
-
".claude/rules/canon/
|
|
98
|
+
".claude/rules/canon/claude/565-behavior.md" ".claude/rules/canon/canon/604-scratch.md"; do
|
|
101
99
|
if [ ! -e "$TARGET_DIR/$path" ]; then
|
|
102
100
|
log_error "Missing after canon init: $path"
|
|
103
101
|
fi
|
package/src/claude/routing.ts
CHANGED
|
@@ -196,7 +196,7 @@ function frontmatter(text: string): string {
|
|
|
196
196
|
*
|
|
197
197
|
* A glob opening `**` reaches every folder in the tree, so it answers that a
|
|
198
198
|
* file type is governed and never that a named path is. Counting one would
|
|
199
|
-
* report every markdown path covered by `
|
|
199
|
+
* report every markdown path covered by `801-markdown` and leave the column
|
|
200
200
|
* saying nothing a reader could act on.
|
|
201
201
|
*/
|
|
202
202
|
function isAnchored(glob: string): boolean {
|
package/src/commands/gov.ts
CHANGED
|
@@ -234,7 +234,7 @@ export function register(program: Command): void {
|
|
|
234
234
|
'after',
|
|
235
235
|
[
|
|
236
236
|
'',
|
|
237
|
-
'Measures the rule in .claude/rules/canon/
|
|
237
|
+
'Measures the rule in .claude/rules/canon/claude/567-planning.md that asks for',
|
|
238
238
|
'the test before the code. It reports and never gates, because pairing',
|
|
239
239
|
'a test to an implementation is a judgment.',
|
|
240
240
|
'',
|
package/src/commands/tasks.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type DeclineOutcome,
|
|
12
12
|
declineTask,
|
|
13
13
|
type PlanCitations,
|
|
14
|
+
type PlanMove,
|
|
14
15
|
planCitations,
|
|
15
16
|
} from '@/tasks/archive'
|
|
16
17
|
import {
|
|
@@ -154,9 +155,9 @@ export function register(program: Command): void {
|
|
|
154
155
|
'after',
|
|
155
156
|
[
|
|
156
157
|
'',
|
|
157
|
-
'The task carries
|
|
158
|
-
'plan, and the archived task keeps a working
|
|
159
|
-
'path. A plan several tasks share stays where it is.',
|
|
158
|
+
'The task carries each plan its Plan: line links when no other live',
|
|
159
|
+
'task cites that plan, and the archived task keeps a working pointer',
|
|
160
|
+
'at each new path. A plan several tasks share stays where it is.',
|
|
160
161
|
'',
|
|
161
162
|
'The ready folder the task names moves to .canon/ready/archive/ with its',
|
|
162
163
|
'plan, and the Ready: line and the plan path to it are retargeted. A',
|
|
@@ -260,6 +261,7 @@ export function register(program: Command): void {
|
|
|
260
261
|
'',
|
|
261
262
|
'Locations:',
|
|
262
263
|
' unstated the task carries no Plan: line',
|
|
264
|
+
' several the Plan: line links more than one plan, named in targets',
|
|
263
265
|
' live the target resolves inside .canon/plans/',
|
|
264
266
|
' archived the target resolves inside .canon/plans/archive/',
|
|
265
267
|
' outside the target resolves somewhere else',
|
|
@@ -448,12 +450,14 @@ export function register(program: Command): void {
|
|
|
448
450
|
'',
|
|
449
451
|
'Exit codes:',
|
|
450
452
|
' 0 the line was added, corrected, or already correct',
|
|
451
|
-
' 1 refused,
|
|
453
|
+
' 1 refused, including several-plans for a line linking more than one',
|
|
454
|
+
' plan, with the reason on stderr or in the JSON record',
|
|
452
455
|
'',
|
|
453
456
|
'It writes Plan: [<label>](<target>) right after the H1, corrects the',
|
|
454
|
-
'target in place when the line exists
|
|
455
|
-
'and a board-relative path the way canon
|
|
456
|
-
'from a linked worktree, since it
|
|
457
|
+
'target in place when the line exists and names the one it replaced,',
|
|
458
|
+
'and resolves both a bare slug and a board-relative path the way canon',
|
|
459
|
+
'tasks plan-answers does. Safe from a linked worktree, since it',
|
|
460
|
+
'resolves the board root in-process.',
|
|
457
461
|
'',
|
|
458
462
|
'Examples:',
|
|
459
463
|
' canon tasks plan-link v28.1-trigger-escalation dispatch-answer-gate',
|
|
@@ -793,6 +797,7 @@ function reportPlanLink(
|
|
|
793
797
|
path: relative(root, outcome.path),
|
|
794
798
|
plan: outcome.plan,
|
|
795
799
|
action: outcome.action,
|
|
800
|
+
replaced: outcome.replaced,
|
|
796
801
|
})}\n`,
|
|
797
802
|
)
|
|
798
803
|
return 0
|
|
@@ -801,6 +806,7 @@ function reportPlanLink(
|
|
|
801
806
|
intro('canon tasks plan-link')
|
|
802
807
|
logStep(outcome.action === 'unchanged' ? 'Already recorded' : 'Recorded')
|
|
803
808
|
logInfo(`${outcome.stem} names plan ${outcome.plan}`)
|
|
809
|
+
if (outcome.replaced) logInfo(`replaced ${outcome.replaced}`)
|
|
804
810
|
if (outcome.action !== 'unchanged') logAdd(relative(root, outcome.path))
|
|
805
811
|
outro()
|
|
806
812
|
|
|
@@ -975,6 +981,10 @@ function reportCitations(
|
|
|
975
981
|
function describeCitations(outcome: PlanCitations): string {
|
|
976
982
|
if (outcome.location === 'unstated') return 'carries no Plan: line.'
|
|
977
983
|
|
|
984
|
+
if (outcome.location === 'several') {
|
|
985
|
+
return `links ${outcome.targets.length} plans (${outcome.targets.join(', ')}), so no single plan is the sweep's to settle.`
|
|
986
|
+
}
|
|
987
|
+
|
|
978
988
|
if (outcome.location === 'archived') {
|
|
979
989
|
return `points at ${outcome.target}, which an earlier sweep already archived.`
|
|
980
990
|
}
|
|
@@ -1396,11 +1406,7 @@ function report(
|
|
|
1396
1406
|
logStep('Archived')
|
|
1397
1407
|
logRemove(relative(root, outcome.from))
|
|
1398
1408
|
logAdd(relative(root, outcome.to))
|
|
1399
|
-
|
|
1400
|
-
logRemove(relative(root, outcome.plan.from))
|
|
1401
|
-
logAdd(relative(root, outcome.plan.to))
|
|
1402
|
-
logInfo('retargeted the Plan: line')
|
|
1403
|
-
}
|
|
1409
|
+
logPlanMoves(outcome.plans, root)
|
|
1404
1410
|
if (outcome.ready) {
|
|
1405
1411
|
logRemove(relative(root, outcome.ready.from))
|
|
1406
1412
|
logAdd(relative(root, outcome.ready.to))
|
|
@@ -1414,6 +1420,24 @@ function report(
|
|
|
1414
1420
|
return 0
|
|
1415
1421
|
}
|
|
1416
1422
|
|
|
1423
|
+
function logPlanMoves(plans: readonly PlanMove[], root: string): void {
|
|
1424
|
+
for (const plan of plans) {
|
|
1425
|
+
logRemove(relative(root, plan.from))
|
|
1426
|
+
logAdd(relative(root, plan.to))
|
|
1427
|
+
}
|
|
1428
|
+
if (plans.length > 0) logInfo('retargeted the Plan: line')
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function planMovesFor(
|
|
1432
|
+
plans: readonly PlanMove[],
|
|
1433
|
+
root: string,
|
|
1434
|
+
): readonly { readonly from: string; readonly to: string }[] {
|
|
1435
|
+
return plans.map((plan) => ({
|
|
1436
|
+
from: relative(root, plan.from),
|
|
1437
|
+
to: relative(root, plan.to),
|
|
1438
|
+
}))
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1417
1441
|
function recordFor(
|
|
1418
1442
|
outcome: ArchiveOutcome,
|
|
1419
1443
|
root: string,
|
|
@@ -1434,12 +1458,7 @@ function recordFor(
|
|
|
1434
1458
|
to: relative(root, outcome.to),
|
|
1435
1459
|
priorityRowRemoved: outcome.priorityRowRemoved,
|
|
1436
1460
|
indexRegenerated: outcome.indexRegenerated,
|
|
1437
|
-
|
|
1438
|
-
? {
|
|
1439
|
-
from: relative(root, outcome.plan.from),
|
|
1440
|
-
to: relative(root, outcome.plan.to),
|
|
1441
|
-
}
|
|
1442
|
-
: null,
|
|
1461
|
+
plans: planMovesFor(outcome.plans, root),
|
|
1443
1462
|
ready: outcome.ready
|
|
1444
1463
|
? {
|
|
1445
1464
|
from: relative(root, outcome.ready.from),
|
|
@@ -1537,11 +1556,7 @@ function reportDecline(
|
|
|
1537
1556
|
logStep('Declined')
|
|
1538
1557
|
logRemove(relative(root, outcome.from))
|
|
1539
1558
|
logAdd(relative(root, outcome.to))
|
|
1540
|
-
|
|
1541
|
-
logRemove(relative(root, outcome.plan.from))
|
|
1542
|
-
logAdd(relative(root, outcome.plan.to))
|
|
1543
|
-
logInfo('retargeted the Plan: line')
|
|
1544
|
-
}
|
|
1559
|
+
logPlanMoves(outcome.plans, root)
|
|
1545
1560
|
if (outcome.priorityRowRemoved) logInfo('cleared the ordering row')
|
|
1546
1561
|
if (outcome.backlogRowRemoved) logInfo('cleared the backlog row')
|
|
1547
1562
|
if (outcome.indexRegenerated) logInfo('regenerated index.md')
|
|
@@ -1571,11 +1586,6 @@ function declineRecordFor(
|
|
|
1571
1586
|
priorityRowRemoved: outcome.priorityRowRemoved,
|
|
1572
1587
|
backlogRowRemoved: outcome.backlogRowRemoved,
|
|
1573
1588
|
indexRegenerated: outcome.indexRegenerated,
|
|
1574
|
-
|
|
1575
|
-
? {
|
|
1576
|
-
from: relative(root, outcome.plan.from),
|
|
1577
|
-
to: relative(root, outcome.plan.to),
|
|
1578
|
-
}
|
|
1579
|
-
: null,
|
|
1589
|
+
plans: planMovesFor(outcome.plans, root),
|
|
1580
1590
|
}
|
|
1581
1591
|
}
|
package/src/gate/measures.ts
CHANGED
|
@@ -101,12 +101,7 @@ const output = (text: string): Emission => ({ kind: 'output', text })
|
|
|
101
101
|
/**
|
|
102
102
|
* Rules no stack reaches, sorted the way `canon gov list` emits them.
|
|
103
103
|
* `260-shadcn` and `320-tanstack-query` are opt-in libraries a project may not
|
|
104
|
-
* want.
|
|
105
|
-
* purpose since a rule under `claude/` would reach every base consumer through
|
|
106
|
-
* the folder-whole entry there. Its own install channel, `canon snippets
|
|
107
|
-
* install`, retired with nothing left to deliver it, so `base` now carries
|
|
108
|
-
* `snippets` as a folder-whole entry of its own and the rule reaches every base
|
|
109
|
-
* consumer through that instead.
|
|
104
|
+
* want.
|
|
110
105
|
*
|
|
111
106
|
* `130-go`, `140-php`, `335-testing-go`, and `336-testing-php` are opt-in by
|
|
112
107
|
* `--add` until a Go or PHP stack ships, which `target-setup` reaches by
|
package/src/gate/stages.ts
CHANGED
package/src/gov/citations.ts
CHANGED
|
@@ -266,7 +266,7 @@ export function collectCitations(text: string): RawCitation[] {
|
|
|
266
266
|
*
|
|
267
267
|
* A rule spelling a whole path there is declaring that exact artifact, and a
|
|
268
268
|
* body line naming it again is naming what the rule governs rather than
|
|
269
|
-
* pointing a reader somewhere. `governance/rules/
|
|
269
|
+
* pointing a reader somewhere. `governance/rules/canon/660-diagrams.md` tells
|
|
270
270
|
* its reader to convert a `.claude/DIAGRAMS.md` left by an older install, which
|
|
271
271
|
* is correctly absent from this tree and correctly named in the rule.
|
|
272
272
|
*/
|
package/src/gov/list.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface GovCatalog {
|
|
|
31
31
|
/**
|
|
32
32
|
* Reports each stack's own entries expanded, so a folder entry reaches a
|
|
33
33
|
* consumer as the rules it stands for. the `setup` skill dedupes `--add` extras
|
|
34
|
-
* against this list, and a stack answering `
|
|
34
|
+
* against this list, and a stack answering `canon` there would re-add every
|
|
35
35
|
* rule that folder already carries.
|
|
36
36
|
*
|
|
37
37
|
* The `extends` chain is deliberately not resolved, which is what the bash
|
package/src/gov/restated.ts
CHANGED
|
@@ -698,8 +698,8 @@ export function readRestated(root: string): RestatedReport {
|
|
|
698
698
|
|
|
699
699
|
// Every candidate's rare set is invariant across the subject loop, so it is
|
|
700
700
|
// built once here rather than per pair. The corpora multiply out to hundreds
|
|
701
|
-
// of thousands of pairings, and rebuilding a set inside that
|
|
702
|
-
//
|
|
701
|
+
// of thousands of pairings, and rebuilding a set inside that would dominate
|
|
702
|
+
// the run.
|
|
703
703
|
const rareCandidates = candidates.map((candidate) => ({
|
|
704
704
|
...candidate,
|
|
705
705
|
rare: distinctive(candidate.analysis),
|
package/src/init/steps.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { DomainStep } from '@/init/run'
|
|
|
5
5
|
export type RunFactory = (args: readonly string[]) => () => Promise<boolean>
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* States the same fact as `docs/target-
|
|
8
|
+
* States the same fact as `docs/target-migrations.md`'s backup section in its
|
|
9
9
|
* own words. Keep the two in step by hand; nothing compares them.
|
|
10
10
|
*/
|
|
11
11
|
const RECORDS_SETUP_NOTICE =
|
package/src/labels/spelling.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { execa } from 'execa'
|
|
|
8
8
|
*
|
|
9
9
|
* `no-binary` is the binary being absent anywhere from the root upward.
|
|
10
10
|
* `cspell` is a devDependency of this repository alone, per
|
|
11
|
-
* `
|
|
11
|
+
* `775-dependencies.md`'s ban on importing a transitive-only package, so a
|
|
12
12
|
* target project that never adopted it gets no coverage from this check
|
|
13
13
|
* rather than a network fetch or a forced new dependency.
|
|
14
14
|
*
|
package/src/records/prune.ts
CHANGED
|
@@ -35,7 +35,7 @@ const LEGACY_MOVES: ReadonlyMap<string, string> = new Map([
|
|
|
35
35
|
* Scratch-root names a different migration moves out of scratch for good,
|
|
36
36
|
* derived from that migration's own table rather than duplicated in a second
|
|
37
37
|
* hand-kept list. `RECORD_LAYOUT_MOVES` names `memory-archive`, the
|
|
38
|
-
* retired-entry archive `
|
|
38
|
+
* retired-entry archive `603-memory` says never to delete, and `PROMOTED_FOLDERS`
|
|
39
39
|
* names an evidence folder a durable record cites by name. A project that has
|
|
40
40
|
* not run either migration still carries these at the scratch root, where an
|
|
41
41
|
* ordinary slug's own age test would eventually offer them.
|
|
@@ -212,7 +212,7 @@ const PHASE_LABEL = /\bv\d+\.\d+(?!\.\d)\b/g
|
|
|
212
212
|
* number, such as `create-rule`, `memory-review`, and `target-setup` already
|
|
213
213
|
* write correctly, from matching. The segment group between `rules/` and the
|
|
214
214
|
* numbered file admits both a governance-namespace path
|
|
215
|
-
* (`canon/
|
|
215
|
+
* (`canon/canon/604-scratch.md`) and a project-namespace one
|
|
216
216
|
* (`project/<subdir>/<n>-<slug>.md`) without distinguishing them, since
|
|
217
217
|
* either shape is the same broken citation.
|
|
218
218
|
*/
|