@erclx/canon 4.17.0 → 4.19.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.17.0",
4
+ "version": "4.19.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -24,7 +24,7 @@ Read these from the project root in parallel, skipping any that do not exist:
24
24
 
25
25
  Do not recurse speculatively.
26
26
 
27
- Follow `${CLAUDE_SKILL_DIR}/../../standards/diagrams.md` for frontmatter, entry kinds, layout, budgets, accessibility, verification, and explanation rules.
27
+ Follow `${CLAUDE_SKILL_DIR}/../../standards/diagrams.md` for frontmatter, entry kinds, and explanation rules. Follow `${CLAUDE_SKILL_DIR}/../../standards/mermaid.md` for what goes inside the fence: layout, budgets, accessibility, labels, and verification.
28
28
 
29
29
  ### Migrating a pre-split flat file
30
30
 
@@ -124,7 +124,7 @@ When the render fails for any reason (no browser engine, no network, no package
124
124
 
125
125
  ## Step 6: read the renders back
126
126
 
127
- Read each PNG and judge the picture against what the entry means to say. Apply the verification properties in the standard.
127
+ Read each PNG and judge the picture against what the entry means to say. Apply the verification properties in `${CLAUDE_SKILL_DIR}/../../standards/mermaid.md`.
128
128
 
129
129
  Fix the source and re-render. Stop after two correction passes on an entry.
130
130
 
@@ -68,7 +68,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
68
68
  | `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
69
69
  | `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
70
70
  | `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
71
- | `canon labels scan` | Fail a pull request whose title or body names the board, by a phase label, a label a code span quotes, or a gitignored record path (`--event`, `--json`) |
71
+ | `canon labels scan` | Fail a pull request whose title or body carries a phase label, a label a code span quotes, a gitignored record path, or a session link (`--event`, `--json`) |
72
72
  | `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
73
73
  | `canon pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`) |
74
74
  | `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
@@ -38,7 +38,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
38
38
  - [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, and what each session surface can see
39
39
  - [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
40
40
  - [Citation reach](skills-reach.md): Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
41
- - [Standard success criteria](standards-audit.md): Reading the corpus against the Success criterion gate, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
41
+ - [Standard success criteria](standards-audit.md): Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
42
42
  - [State-scoped risk](state-scoped-risk.md): Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
43
43
  - [Superseded values](superseded.md): Reading where the tree still asserts a value a changed convention no longer produces, why the sweep keys on the value rather than the file, the family stem behind a templated citation, the exemption marker, the blind spots it cannot reach, and why it reports rather than gates
44
44
  - [Targets](targets.md): The projects this toolkit installed into, the record the install writes against the sweep that backs it, what bounds each answer, and the cross-target pull request read
@@ -1,11 +1,13 @@
1
1
  ---
2
2
  title: Standard success criteria
3
- description: Reading the corpus against the Success criterion gate, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
3
+ description: Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
4
4
  ---
5
5
 
6
6
  # Standard success criteria
7
7
 
8
- `canon standards audit` reads the corpus at `standards/` and reports which files carry a `## Success criterion` section against which do not, per `standards/standard.md`. It fails only on a standard new to the current branch, never on one already short the section.
8
+ `canon standards audit` reads the corpus at `standards/` and reports which files state a success criterion against which do not, per `standards/standard.md`. It fails only on a standard new to the current branch, never on one already short the section.
9
+
10
+ Two headings count, at any casing. `## Success criterion` is the wording that standard states the rule under, and `## What a working <document type> looks like` is the wording its template prescribes for the section itself. Most of the corpus writes the templated form, so accepting the rule's wording alone would refuse a conforming standard.
9
11
 
10
12
  ```bash
11
13
  canon standards audit
@@ -25,11 +27,11 @@ canon standards audit --arrivals-only
25
27
 
26
28
  ## Exit codes and refusals
27
29
 
28
- | Code | Meaning |
29
- | ---- | --------------------------------------------------------------- |
30
- | `0` | every arriving standard carries the section |
31
- | `1` | refused, with `reason` naming the cause |
32
- | `2` | a standard new to this branch carries no `## Success criterion` |
30
+ | Code | Meaning |
31
+ | ---- | ---------------------------------------------------------------------- |
32
+ | `0` | every arriving standard carries the section |
33
+ | `1` | refused, with `reason` naming the cause |
34
+ | `2` | a standard new to this branch states no criterion under either heading |
33
35
 
34
36
  A project authoring no standards refuses with `no-corpus`, the ordinary state of most targets, the same absence `canon claude skills audit` reads as its own `no-corpus`.
35
37
 
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Route a drafted Mermaid fence to the mermaid standard for layout, budgets, accessibility, labels, and render verification
3
+ paths:
4
+ - '**/*.md'
5
+ ---
6
+
7
+ # Mermaid diagram standards
8
+
9
+ ## Authority
10
+
11
+ - Apply this rule only when the edit drafts or revises a Mermaid fence. Skip it on a markdown edit carrying no diagram.
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
+ - 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. `560-diagrams` routes it.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Route .canon/diagrams edits to the diagrams standard for Mermaid conventions
2
+ description: Route .canon/diagrams edits to the diagrams standard for entry kinds, frontmatter, and explanation prose
3
3
  paths:
4
4
  - '.canon/diagrams/**'
5
5
  - '.claude/DIAGRAMS.md'
@@ -9,7 +9,8 @@ paths:
9
9
 
10
10
  ## Authority
11
11
 
12
- - Follow the diagrams standard for Mermaid diagram layout, budgets, accessibility, verification, and explanation prose. It is the single source. Read it with `canon standards diagrams`.
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. `502-mermaid` routes them to `mermaid.md`.
13
14
  - A diagram entry carries structure and flow, not implementation. Read the standard before adding or revising a kind.
14
15
 
15
16
  ## Scope
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.17.0",
4
+ "version": "4.19.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -90,7 +90,7 @@ export function register(program: Command): void {
90
90
  labels
91
91
  .command('scan')
92
92
  .description(
93
- 'Fail a pull request whose title or body carries a phase label or a board identifier',
93
+ 'Fail a pull request whose title or body carries a phase label, a board identifier, or a session link',
94
94
  )
95
95
  .helpOption('-h, --help', 'Show this help message')
96
96
  .option(
@@ -118,10 +118,16 @@ export function register(program: Command): void {
118
118
  'copy of. A path under a tracked folder is left alone, so a rule or a',
119
119
  'skill any clone resolves is not reported.',
120
120
  '',
121
+ 'It reports a session link as the third category, being a link to one',
122
+ 'Claude Code session, which the harness appends to text it tells a',
123
+ 'session to publish. That link resolves for the one account holding the',
124
+ 'session and for no other reader, which no clone repairs, so it is read',
125
+ 'on a release pull request too, where the board identifier is not.',
126
+ '',
121
127
  'Exit codes:',
122
- ' 0 no phase label and no board identifier found',
128
+ ' 0 no phase label, no board identifier, and no session link found',
123
129
  ' 1 refused, with the reason on stderr or in the JSON record',
124
- ' 2 the title or body carries a phase label or a board identifier',
130
+ ' 2 the title or body carries one of the three',
125
131
  '',
126
132
  'Examples:',
127
133
  ' canon labels scan --event "$GITHUB_EVENT_PATH"',
@@ -363,6 +369,16 @@ async function runScan(opts: ScanOptions): Promise<number> {
363
369
  for (const reference of result.boardReferences) logWarn(reference)
364
370
  }
365
371
 
372
+ logStep(result.sessionLinks.length === 0 ? 'Clean' : 'Session link found')
373
+ if (result.sessionLinks.length === 0) {
374
+ logInfo('no session link in the title or body')
375
+ } else {
376
+ logWarn(
377
+ `${plural(result.sessionLinks.length, 'session link')} in the title or body. Delete every hit rather than rewriting it, since a session resolves for the one account that started it and for no other reader.`,
378
+ )
379
+ for (const link of result.sessionLinks) logWarn(link)
380
+ }
381
+
366
382
  outro()
367
383
 
368
384
  if (emitJson) {
@@ -372,11 +388,14 @@ async function runScan(opts: ScanOptions): Promise<number> {
372
388
  phaseLabels: result.phaseLabels,
373
389
  semverTags: result.semverTags,
374
390
  boardReferences: result.boardReferences,
391
+ sessionLinks: result.sessionLinks,
375
392
  })}\n`,
376
393
  )
377
394
  }
378
395
 
379
- return result.phaseLabels.length === 0 && result.boardReferences.length === 0
396
+ return result.phaseLabels.length === 0 &&
397
+ result.boardReferences.length === 0 &&
398
+ result.sessionLinks.length === 0
380
399
  ? 0
381
400
  : 2
382
401
  }
@@ -34,6 +34,19 @@ export interface PhaseScanResult {
34
34
  * nor the gitignored folder a path names.
35
35
  */
36
36
  readonly boardReferences: readonly string[]
37
+ /**
38
+ * A link to one Claude Code session, which the harness appends to text it
39
+ * tells a session to publish.
40
+ *
41
+ * It sits beside the board references rather than inside them because the
42
+ * two are unresolvable for different reasons. A record path fails for a
43
+ * reader holding no copy of this checkout, and a session link fails for
44
+ * everyone outside the one account that holds the session, which no clone
45
+ * and no checkout repairs. The report line for a board reference names a
46
+ * record path and a quoted label, so a session link folded in would be
47
+ * reported under a sentence that does not describe it.
48
+ */
49
+ readonly sessionLinks: readonly string[]
37
50
  }
38
51
 
39
52
  const VERSION_TOKEN = /\bv\d+(?:\.\d+){1,2}\b/g
@@ -98,6 +111,26 @@ const RECORD_PATH = new RegExp(
98
111
  'g',
99
112
  )
100
113
 
114
+ /**
115
+ * A link to one Claude Code session, matched on the host and the path segment
116
+ * rather than on the identifier alphabet.
117
+ *
118
+ * The two instances on the trunk carry a 24-character identifier after
119
+ * `session_`, and reading that shape into the pattern would empty this check
120
+ * the moment the harness changed it, with nothing left to report and no
121
+ * failure to notice. The host and the path segment are what the harness has to
122
+ * keep for the link to resolve at all. A host change still gets past this and
123
+ * nothing detects that.
124
+ *
125
+ * The scheme is optional because it is incidental to the two parts being
126
+ * matched, so a link written without it is the same unresolvable reference.
127
+ * The tail runs to the first whitespace or closing delimiter, which is how a
128
+ * record path is read, so a report names the whole link rather than the prefix
129
+ * that matched.
130
+ */
131
+ const SESSION_LINK =
132
+ /(?<![\w./-])(?:https?:\/\/)?claude\.ai\/code\/session_[^\s`)\]]+/g
133
+
101
134
  /**
102
135
  * The head branch release-please opens every release pull request under.
103
136
  *
@@ -180,6 +213,16 @@ function recordPaths(text: string): string[] {
180
213
  ]
181
214
  }
182
215
 
216
+ function sessionLinks(text: string): string[] {
217
+ return [
218
+ ...new Set(
219
+ (text.match(SESSION_LINK) ?? []).map((link) =>
220
+ link.replace(TRAILING_PUNCTUATION, ''),
221
+ ),
222
+ ),
223
+ ]
224
+ }
225
+
183
226
  /**
184
227
  * Reads a title and a body for version-shaped tokens and sorts every one
185
228
  * found into the namespace this pull request is allowed to carry.
@@ -197,11 +240,22 @@ function recordPaths(text: string): string[] {
197
240
  * body without passing the gate on its own. That its author has nothing to
198
241
  * rewrite is true as well and is the weaker half, since it would leave the
199
242
  * reference standing and unresolvable.
243
+ *
244
+ * A session link is reported on both paths, the release one included. Nothing
245
+ * appends one to a release body, which release-please generates with no
246
+ * session in the loop, so populating the field there costs nothing on every
247
+ * run this repository has seen. What it buys is that the coverage argument
248
+ * above never has to hold for this category: that argument reasons from every
249
+ * commit in the generated history having passed this gate, and a link the gate
250
+ * did not yet scan for would reach a release body under it. Reading the whole
251
+ * body on both paths leaves no hole if the premise ever slips.
200
252
  */
201
253
  export function scanPhaseLabels(input: PhaseScanInput): PhaseScanResult {
202
254
  const source = `${input.title}\n${input.body}`
255
+ const outsideFences = linesOutsideFences(source).join('\n')
203
256
  const tokens = versionTokens(readable(source))
204
257
  const cutsRelease = isReleasePullRequest(input)
258
+ const links = sessionLinks(outsideFences)
205
259
 
206
260
  if (cutsRelease) {
207
261
  return {
@@ -209,6 +263,7 @@ export function scanPhaseLabels(input: PhaseScanInput): PhaseScanResult {
209
263
  phaseLabels: [],
210
264
  semverTags: tokens,
211
265
  boardReferences: [],
266
+ sessionLinks: links,
212
267
  }
213
268
  }
214
269
 
@@ -222,9 +277,7 @@ export function scanPhaseLabels(input: PhaseScanInput): PhaseScanResult {
222
277
  cutsRelease,
223
278
  phaseLabels: tokens,
224
279
  semverTags: [],
225
- boardReferences: [
226
- ...quoted,
227
- ...recordPaths(linesOutsideFences(source).join('\n')),
228
- ],
280
+ boardReferences: [...quoted, ...recordPaths(outsideFences)],
281
+ sessionLinks: links,
229
282
  }
230
283
  }
@@ -5,11 +5,25 @@ import { gitEnv } from '@/git-env'
5
5
  import { resolveBaseRef } from '@/git-files'
6
6
  import { INDEX_FILE, standardsSourceDir } from '@/standards/read'
7
7
 
8
- /** Returned when a standard new to this branch carries no `## Success criterion` section, the gating check. */
8
+ /** Returned when a standard new to this branch states no success criterion under either heading `CRITERION_HEADING` accepts, the gating check. */
9
9
  export const EXIT_MISSING_CRITERION = 2
10
10
 
11
- /** Matched at any casing, level-2 only, per the heading `standards/standard.md` itself uses. */
12
- const CRITERION_HEADING = /^##\s+success criterion\s*$/im
11
+ /**
12
+ * Matched at any casing, level-2 only, in either heading the corpus writes a
13
+ * criterion under.
14
+ *
15
+ * `standards/standard.md` states the rule under `## Success criterion` and
16
+ * templates the section itself as `## What a working <document type> looks
17
+ * like`, and the template wins where the two disagree, which that file says in
18
+ * as many words. Reading the rule group's own heading as the one to match is
19
+ * what the first spelling here did, and no standard in the corpus carries a
20
+ * criterion under it: 12 of 28 use the templated form and `standard.md` alone
21
+ * carries the literal words, over its rules about criteria rather than over its
22
+ * own. Nothing caught that, because the gate reads a standard new since the
23
+ * merge base and none had arrived since it shipped.
24
+ */
25
+ const CRITERION_HEADING =
26
+ /^##\s+(?:success criterion|what a working .+ looks like)\s*$/im
13
27
 
14
28
  /**
15
29
  * The reasons an audit produces no reading. `no-corpus` is the ordinary state
@@ -11,10 +11,11 @@ A diagram entry answers one question about the system with one or more Mermaid d
11
11
 
12
12
  ## Scope
13
13
 
14
- Governs per-kind diagram entries under `.canon/diagrams/`: which question each answers, the Mermaid source, the accessibility fields, and the explanation prose beneath. It states the voice for that prose, which is the yield the `write-human` skill grants a surface whose own standard sets one.
14
+ Governs per-kind diagram entries under `.canon/diagrams/`: which question each answers, which kinds exist and what each is drawn from, the frontmatter, and the explanation prose beneath the fence. It states the voice for that prose, which is the yield the `write-human` skill grants a surface whose own standard sets one.
15
15
 
16
16
  Does not govern:
17
17
 
18
+ - How the drawing inside the fence is laid out, budgeted, made accessible, and verified against its render: `mermaid.md`
18
19
  - Rhythm and sentence construction in explanation prose: the `write-human` skill, whose rules the yield does not lift beyond voice
19
20
  - Language, word choice, punctuation, and formatting in explanation prose and node labels: `markdown.md`, which the yield does not reach
20
21
  - The mechanism behind any component a diagram draws: `context.md`
@@ -63,28 +64,9 @@ The kinds drift at rates spanning roughly an order of magnitude, which is why th
63
64
 
64
65
  A second entry for one kind takes a suffixed name (`request-flow-admin.md`) and repeats the kind's `category` verbatim, which is what the grouping field is for. One entry per kind is the ordinary case, so most catalogs show one entry under each heading.
65
66
 
66
- ## Layout
67
-
68
- - Declare `flowchart TB` by default. Mermaid ignores a subgraph's direction whenever that subgraph links outward, and an architecture diagram links across its subgraphs as the normal case, so top-bottom is a declaration rather than a guarantee.
69
- - Restructure a diagram that renders diagonal or left-to-right. Repeating the direction keyword does not fix it.
70
- - Render a context, component, or pipeline diagram taller than wide. A `sequenceDiagram` is wide by construction and is exempt.
71
- - Do not let independent nodes render in a row. A row of siblings reads as a sequential chain and asserts a pipeline the system does not have.
72
- - Do not converge many edges on one node from one side. A crossing bundle is unreadable whatever it encodes.
73
- - Keep node labels short. Three or four words max. Detail goes in the paragraph below the diagram.
74
- - Use `<br/>` for a second short line on a node when the label is two ideas, never for a sentence.
75
- - Subgraphs are for grouping unrelated lanes such as offline versus online or browser versus server. Do not subgraph a single linear flow.
76
-
77
67
  ## Budgets
78
68
 
79
- - Hold a diagram to roughly 5 to 10 nodes. Split it past 15.
80
- - Watch edge count harder than node count. It binds first, and a diagram whose edges outnumber its nodes is already too dense to read.
81
- - Treat a diagram that cannot be described in one sentence as two diagrams.
82
69
  - Keep an entry to one diagram by default. A second fence in the same file needs its own H2 naming what it adds, and a third is a sign the entry covers two kinds.
83
- - Warn rather than refuse on a budget, and name the split that would fix it. These numbers come from published Mermaid practice rather than from a measurement in this repository, so a hard refusal on them will be wrong sometimes and unarguable when it is.
84
-
85
- ## Accessibility
86
-
87
- - Give every diagram `accTitle` and `accDescr`. `accTitle` names what the diagram answers. `accDescr` states the structure in one sentence for a reader who cannot see the render.
88
70
 
89
71
  ## Explanation
90
72
 
@@ -96,13 +78,6 @@ A second entry for one kind takes a suffixed name (`request-flow-admin.md`) and
96
78
 
97
79
  This section states the voice for the surface, which is what claims the yield the `write-human` skill grants to a surface whose own standard sets it. Explanation prose is pedagogical here and the default developer-facing voice does not apply. The yield covers voice alone. The rhythm and density rules that skill carries stay in force, as do the language bans, punctuation, and formatting in `markdown.md`, which grants no yield at all.
98
80
 
99
- ## Verification
100
-
101
- - Judge a diagram from its rendered image, not from its source. Direction, sibling rows, and edge bundles are visible only in the output.
102
- - Render to PNG. An SVG export reads back as markup with no recoverable spatial meaning.
103
- - Apply four tests as a reviewer, the same ones the author applied: direction held, no sibling row reading as a chain, no crossing edge bundle, taller than wide outside a sequence diagram.
104
- - State which verification was skipped when no renderer is available. A diagram written without a render is still shippable, and one reported as verified without a render is not.
105
-
106
81
  ## What moves to .claude/context/
107
82
 
108
83
  Implementation detail that answers how a component is built belongs in a `.claude/context/` entry, not a diagram:
@@ -120,13 +95,13 @@ Reference the context entry by path when a reader needs the mechanism. The diagr
120
95
  - A diagram showing a defunct host or library is worse than no diagram. Audit the affected entry in the same PR.
121
96
  - `System context` has no named source signal beyond `.claude/REQUIREMENTS.md`, so nothing tells a session it went stale. Re-read it when the boundary or the set of external dependencies moves.
122
97
  - Nothing watches the folder for you. The entries are redrawn on demand rather than swept on every ship, so `verified` carries the whole signal: an entry whose date sits far behind the branch is due a read, and no pass is going to name which one.
123
- - The explanation paragraphs around a Mermaid block are prose and follow `markdown.md` and the `write-human` skill. The fenced block itself is not, which is why a check scoped to prose is the wrong thing to rely on for what sits inside it.
124
- - The punctuation bans still apply to node and subgraph labels, and nothing checks them there. An em dash in a label passes every gate the repository has, so read the labels before shipping the entry.
125
98
 
126
99
  ## Template
127
100
 
128
101
  The filename and the `category` value both come from Entry kinds and are fixed per kind. A stub nobody has drawn yet carries `TODO: never verified` in place of the SHA and date. The node names and labels inside the fence are placeholders, written bare because Mermaid reads an angle bracket as markup.
129
102
 
103
+ What goes inside the fence, and how the render is checked once it is drawn, is `mermaid.md`. This template fixes the frame around it.
104
+
130
105
  ````markdown
131
106
  ---
132
107
  title: <what the entry answers>
@@ -19,6 +19,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
19
19
  - [Issue reference](issue.md): GitHub issue title, labels, and body conventions
20
20
  - [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, banned words, and frontmatter wording
21
21
  - [Memory reference](memory.md): Filename and type prefix, frontmatter, the body shape per type, links between entries, and the lifecycle from write to retire
22
+ - [Mermaid reference](mermaid.md): Direction and layout, node and edge budgets, accessibility fields, label punctuation, and render verification for a Mermaid diagram
22
23
  - [Plan reference](plan.md): Filename and slug, required sections, the suggested-and-answer contract, and the lifecycle from the live folder to the archive
23
24
  - [Pull request reference](pr.md): Pull request title and body conventions
24
25
  - [Publish reference](publish.md): Scan an author runs against finished text, the cross-reference form each destination takes, and the response to an unreadable source
@@ -19,13 +19,14 @@ Does not govern:
19
19
  ## Title
20
20
 
21
21
  - Format: `<type>: <subject>`
22
- - Type is `bug` or `task`. Lowercase the type and the first word of the subject.
22
+ - Type is `bug`, `task`, or `feedback`. Lowercase the type and the first word of the subject for `bug` and `task`, both hand-composed. `feedback` is always lowercase. Its subject is derived by the CLI from the report's `### Surface` field verbatim, so the subject alone carries no casing rule.
23
23
  - Length: 72 characters maximum, no trailing period.
24
24
 
25
25
  ## Labels
26
26
 
27
- - Apply `bug` for a defect and `enhancement` for a task or improvement.
28
- - Both are GitHub default labels. A label that does not exist makes `gh` reject the issue. Create it once with `gh label create`.
27
+ - Apply `bug` for a defect and `enhancement` for a task or improvement. Both are GitHub default labels.
28
+ - Apply `feedback` to a report filed through `canon feedback --github`, which sets the label unconditionally rather than a session choosing it. It is not a GitHub default. Create it once per project with `gh label create feedback`.
29
+ - A label that does not exist makes `gh` reject the issue. Create each one once with `gh label create`.
29
30
  - One label per issue unless a second genuinely applies.
30
31
 
31
32
  ## Content
@@ -37,16 +38,35 @@ Does not govern:
37
38
 
38
39
  ## Sections
39
40
 
41
+ ### Bug and task
42
+
40
43
  - `## Summary`: one line naming what and why.
41
44
  - `## Details`: for a bug, what happens versus what is expected. For a task, what to build.
42
45
  - `## Context`: for a bug, repro steps or commands. For a task, the driving reason, or `none`.
43
46
  - `## Proposed` (optional): one line naming a fix or approach. Omit when open.
44
47
 
48
+ ### Feedback
49
+
50
+ - One `## Toolkit feedback` wrapper holds every field as a nested `###` heading. A flat `##` field is not recognized.
51
+ - `### From project` (optional): the reporting project's name or kind.
52
+ - `### Surface` (required): the toolkit surface and its type, and the specific file or name when known.
53
+ - `### Observed` (required): the observed behavior.
54
+ - `### Expected` (optional): the expected behavior, or `unclear`.
55
+ - `### Repro` (optional): repro details, or `none`.
56
+ - `### Proposed fix` (required): a proposed fix, or `open` when the session settled on no direction.
57
+ - A required field is refused when present but empty. Write the fallback literal for an optional field rather than leaving it blank.
58
+
45
59
  ## Formatting
46
60
 
61
+ ### Bug and task
62
+
47
63
  - End every bullet with a period.
48
64
  - Keep each section to one or two lines.
49
65
 
66
+ ### Feedback
67
+
68
+ - A field may run to multiple paragraphs and may hold a fenced code block. The one-or-two-line rule and the trailing-period rule do not apply.
69
+
50
70
  ## Examples
51
71
 
52
72
  ### Correct (bug)
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: Mermaid reference
3
+ description: Direction and layout, node and edge budgets, accessibility fields, label punctuation, and render verification for a Mermaid diagram
4
+ ---
5
+
6
+ # Mermaid reference
7
+
8
+ Applies to every Mermaid fence, wherever it is written. A fence sits inside a document some other standard shapes, so this file reaches the drawing and stops at the fence markers.
9
+
10
+ ## Scope
11
+
12
+ Governs the Mermaid attribute wherever a fence is written, naming no path because an attribute standard governs a drawing rather than a document type: direction and layout, node and edge budgets, the accessibility fields, how punctuation reaches a label, and how a rendered diagram is verified. It carries no template, since a fence has no document shape of its own and sits inside one another standard sets.
13
+
14
+ Does not govern:
15
+
16
+ - Which diagram a document carries, what question it answers, its frontmatter, and the explanation prose beneath it: `diagrams.md`
17
+ - Language, word choice, punctuation, and formatting in the prose around a fence: `markdown.md`
18
+ - Voice, rhythm, and sentence construction in that prose: the `write-human` skill
19
+ - The mechanism behind any component a diagram draws: `context.md`
20
+
21
+ ## What a working diagram looks like
22
+
23
+ A diagram works when a reviewer holding the rendered image, and nothing else, answers yes four times:
24
+
25
+ - Did the declared direction hold?
26
+ - Is every row of siblings free of a sequential reading the system does not have?
27
+ - Is every convergence free of a crossing edge bundle?
28
+ - Is it taller than wide, outside a sequence diagram?
29
+
30
+ A diagram failing these is non-conforming even when it satisfies every shape rule below. The rules are the means. These four questions are the test.
31
+
32
+ ## Layout
33
+
34
+ - Declare `flowchart TB` by default. Mermaid ignores a subgraph's direction whenever that subgraph links outward, and an architecture diagram links across its subgraphs as the normal case, so top-bottom is a declaration rather than a guarantee.
35
+ - Restructure a diagram that renders diagonal or left-to-right. Repeating the direction keyword does not fix it.
36
+ - Render a context, component, or pipeline diagram taller than wide. A `sequenceDiagram` is wide by construction and is exempt.
37
+ - Do not let independent nodes render in a row. A row of siblings reads as a sequential chain and asserts a pipeline the system does not have.
38
+ - Do not converge many edges on one node from one side. A crossing bundle is unreadable whatever it encodes.
39
+ - Keep node labels short. Three or four words max. Detail goes in the paragraph below the diagram.
40
+ - Use `<br/>` for a second short line on a node when the label is two ideas, never for a sentence.
41
+ - Subgraphs are for grouping unrelated lanes such as offline versus online or browser versus server. Do not subgraph a single linear flow.
42
+
43
+ ## Budgets
44
+
45
+ - Hold a diagram to roughly 5 to 10 nodes. Split it past 15.
46
+ - Watch edge count harder than node count. It binds first, and a diagram whose edges outnumber its nodes is already too dense to read.
47
+ - Treat a diagram that cannot be described in one sentence as two diagrams.
48
+ - Warn rather than refuse on a budget, and name the split that would fix it. These numbers come from published Mermaid practice rather than from a measurement against a corpus, so a hard refusal on them will be wrong sometimes and unarguable when it is.
49
+
50
+ ## Labels
51
+
52
+ - Apply the punctuation bans in `markdown.md` to node and subgraph labels. A label is not prose, so a check scoped to prose does not reach it, and reading the labels before shipping is the only gate they have.
53
+ - Treat the prose around a fence and the text inside it as two surfaces. The paragraphs are prose and follow `markdown.md` and the `write-human` skill. The fenced block is not, which is why a check scoped to prose is the wrong thing to rely on for what sits inside it.
54
+
55
+ ## Accessibility
56
+
57
+ - Give every diagram `accTitle` and `accDescr`. `accTitle` names what the diagram answers. `accDescr` states the structure in one sentence for a reader who cannot see the render.
58
+
59
+ ## Verification
60
+
61
+ - Judge a diagram from its rendered image, not from its source. Direction, sibling rows, and edge bundles are visible only in the output.
62
+ - Render to PNG. An SVG export reads back as markup with no recoverable spatial meaning.
63
+ - Apply the four questions above as a reviewer, against the same render the author judged.
64
+ - State which verification was skipped when no renderer is available. A diagram written without a render is still shippable, and one reported as verified without a render is not.
package/standards/pr.md CHANGED
@@ -14,7 +14,7 @@ Does not govern:
14
14
  - Commit subject format, which shares the title form: `commit.md`
15
15
  - Branch naming: `branch.md`
16
16
  - Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
17
- - Whether a quoted label or a gitignored record path may appear in a title or body, which `canon labels scan` fails on: `publish.md`
17
+ - Whether a quoted label, a gitignored record path, or a link to one Claude Code session may appear in a title or body, which `canon labels scan` fails on: `publish.md`
18
18
  - Voice, rhythm, and sentence construction in pull request prose: the `write-human` skill
19
19
  - Punctuation, formatting, and banned words in pull request prose: `markdown.md`
20
20
 
@@ -46,6 +46,7 @@ Does not govern:
46
46
  - Use checkboxes, never prose. See Testing discipline for which box gets ticked.
47
47
  - `## For the reviewer` (optional): what the reviewing session should confirm, one bullet per request
48
48
  - Visuals: include only when they clarify architecture, UI, or complex logic flows
49
+ - The list is closed. A body carries the sections above and nothing after the last of them, which covers a trailer a harness appends once the body is composed. `publish.md` states the rule, and `canon labels scan` fails on the one such trailer measured so far.
49
50
 
50
51
  ## Testing discipline
51
52
 
@@ -46,6 +46,16 @@ Rewrite a hit to name what the reader can reach rather than deleting it. A row's
46
46
 
47
47
  `canon labels scan` runs this check and the phase-label one over a pull request title and body. It reads that pair alone, so every other channel is the author's own scan.
48
48
 
49
+ ## Session links
50
+
51
+ A link to one Claude Code session names the session that wrote the text rather than the change the text describes. It enters because the harness tells the composing session to end its published text with one, so the author writes it deliberately and it reads as required rather than as a slip. That is what carries it past a scan the same author runs, and it is why a rule stating the ban does not reach the case: the session is following an instruction it was given, and reads its own compliance as correct.
52
+
53
+ This check depends on the reader holding the account rather than the checkout, which is narrower than the dependency the destination rule above scopes. A reader inside this checkout opens a record path and still cannot open a session, since the session resolves for the one account that started it and for nobody else. The scoping still sits at the invocation site, because `canon labels scan` reads a pull request title and body and reaches no other text.
54
+
55
+ Delete a hit rather than rewriting it. The other two checks replace a hit with what the reader can reach instead, and a session has no such form, so what stands in its place is the body already written above it.
56
+
57
+ Removing the link before a merge does not substitute for never writing it. One branch removed it, read the live body back clean, and the squashed commit landed carrying it anyway off an earlier snapshot, which is permanent on the trunk.
58
+
49
59
  ## Cross-reference form
50
60
 
51
61
  A number referring to a pull request or an issue takes the form its destination renders. Write it bare where the destination auto-links it, and in backticks where it does not. Both spellings are correct, each in one place, so a reference moved from one destination to the other is rewritten rather than copied.