@erclx/canon 4.19.2 → 4.20.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.19.2",
4
+ "version": "4.20.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -35,6 +35,15 @@ Every element competes with the material for attention.
35
35
  - Do not animate anything the learner did not trigger
36
36
  - Prefer whitespace over rules and boxes to separate sections
37
37
 
38
+ ## Diagrams
39
+
40
+ A diagram earns its place when a relationship, a boundary, a path, a before-and-after, is the point of the passage rather than decoration for it. Most passages do not clear that bar, and a lesson reaching for a diagram on every section is the failure the neighboring `## Restraint` section already guards against. When prose or a list already carries that point, leave it there.
41
+
42
+ - Wrap it in a `<figure>` holding an inline `<svg>` and a `<figcaption>` that names what to take from the diagram rather than what it shows. Let the figure run wider than the prose column, since a diagram cramped to the reading measure loses the labels it needs.
43
+ - Draw it by hand, as plain shapes and lines authored directly in the markup, not exported from a diagramming tool.
44
+ - Color every fill and stroke through a custom property the workspace's own stylesheet defines, never a literal hex value, which is the same rule that stylesheet's opening comment already states for every rule added under it. The diagram then re-colors itself on the same switch that re-colors the page.
45
+ - Give the `<svg>` an accessible name: `role="img"` with `aria-label` for one short line, or `aria-labelledby` pointing at a `<title>` element inside it for a longer one.
46
+
38
47
  ## Quiz construction
39
48
 
40
49
  The quiz is the retrieval, so a leak in its construction turns it into a reading test.
@@ -10,6 +10,7 @@ Read these files in parallel:
10
10
  - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
11
11
  - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
12
12
  - `${CLAUDE_SKILL_DIR}/../../standards/readme.md`: README structure, required sections, and content rules
13
+ - `${CLAUDE_SKILL_DIR}/../../standards/docs.md`: the reader a page under `docs/` serves, its frontmatter, structure, and what it links out rather than carrying
13
14
 
14
15
  ## Diff baseline
15
16
 
@@ -51,7 +51,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
51
51
  | `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`) |
52
52
  | `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
53
53
  | `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
54
- | `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` |
54
+ | `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
55
55
  | `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
56
56
  | `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
57
57
  | `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
@@ -45,4 +45,4 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
45
45
  - [Tasks](tasks.md): Selecting a shipped task by stem or pull request, recording a number and closing an outcome, the refusal reasons, the board and backlog checks validate runs, and why the board root defaults to the main worktree
46
46
  - [Teach](teach.md): Listing learning workspaces and the ordinal a new one takes, opening one with its required files, recording sources and glossary terms, resolving what the next lesson needs before it is written, the refusal reasons, and why every write here runs through a verb
47
47
  - [Test order](test-order.md): Reading where an implementation reached history ahead of its test, how a pair is decided, the three verdicts, the coverage the pairing cannot reach, and why the check reports rather than gates
48
- - [Worktrees](worktrees.md): Reporting which worktrees are reclaimable, removing the ones that are, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
48
+ - [Worktrees](worktrees.md): Reporting which worktrees are reclaimable, removing the ones that are, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Worktrees
3
- description: Reporting which worktrees are reclaimable, removing the ones that are, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
3
+ description: Reporting which worktrees are reclaimable, removing the ones that are, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
4
4
  ---
5
5
 
6
6
  # Worktrees
@@ -31,11 +31,13 @@ An exit code says nothing about a call made from a session, since a shell profil
31
31
  ```bash
32
32
  canon worktrees reclaim --dry-run
33
33
  canon worktrees reclaim
34
+ canon worktrees reclaim --json
34
35
  ```
35
36
 
36
37
  | Option | Behavior |
37
38
  | ----------- | ------------------------------------------- |
38
39
  | `--dry-run` | Report what would be removed without acting |
40
+ | `--json` | Add a machine-readable record on stdout |
39
41
 
40
42
  Removal sits on the default path rather than behind an apply flag. Report-only is what the `list` verb already did, and what it produced was a hand cleanup: eight directories deleted outside git in one afternoon, each leaving the registration and the branch that `git worktree remove` would have taken with it. A flag a reader has to remember is one nobody passes the first time, and the first time is when the directories are still there.
41
43
 
@@ -43,6 +45,18 @@ Each entry unlocks, removes, then deletes its branch, and the sequence is the sa
43
45
 
44
46
  Exit codes: `0` every reclaimable worktree was removed or there were none, `1` refused or a removal failed. A reading that could not reach the merge state refuses every entry rather than falling back to a default, since that state is the one input deciding whether a branch is safe to delete.
45
47
 
48
+ ### The record
49
+
50
+ `--json` writes `{reason, detail, dryRun, reclaimable, removed, failed, pruned, outcomes}` on stdout. `reason` is null unless the whole reading refused, `reclaimable` counts what the verdicts named, `removed` and `failed` split the outcomes, and `pruned` says whether the stale-registration sweep ran. A dry run and a reading that found nothing both report `removed` as 0 and differ on `reclaimable`, which separates a quiet repository from a run asked not to act.
51
+
52
+ Field order is part of the contract. A shell reads this record with a pattern rather than a parser, so `reason` sits ahead of the free-text `detail`, and the scalar counts sit ahead of `outcomes`, whose per-entry `failedAt` and boolean `removed` match neither digit pattern. Read the record's fields rather than the exit when a skill or a hook consumes this, for the reason the `list` section above gives.
53
+
54
+ ## What calls it
55
+
56
+ `.husky/post-merge` runs `canon worktrees reclaim --json` on every merge, between the records push and the upgrade block, so a merged branch's worktree goes without a person remembering. `CANON_SKIP_RECLAIM=1` turns that step off.
57
+
58
+ The hook's call carries no `--root` and no `cd`, unlike the two steps above it. Git runs a hook from the top level of the worktree the pull happened in, and the verb reads its own working directory to refuse that worktree as `current-worktree`, so a root argument would turn the running worktree into an ordinary candidate and let a pull inside a linked worktree delete the ground under itself.
59
+
46
60
  ## What makes a worktree reclaimable
47
61
 
48
62
  All three hold: its branch has a merged pull request, its working tree is clean, and no live session holds the directory. Each alone has a case where removal loses something, so `refusals` names every failing condition rather than the first.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.19.2",
4
+ "version": "4.20.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -28,7 +28,7 @@
28
28
  "check:spell": "cspell '**' '.*/**' '.*' --no-progress --color --show-context",
29
29
  "format": "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --write --indent 2 scripts/ tooling/ claude/",
30
30
  "check:format": "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --diff --indent 2 scripts/ tooling/ claude/",
31
- "check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} +",
31
+ "check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
32
32
  "check:types": "tsc --noEmit",
33
33
  "check:install": "./scripts/core/install-check.sh",
34
34
  "test": "bun --bun vitest run",
@@ -17,7 +17,11 @@ import {
17
17
  type WorktreeVerdict,
18
18
  } from '@/worktrees/reclaim'
19
19
  import { mainWorktreeRoot } from '@/worktree'
20
- import { removeReclaimable, type RemovalOutcome } from '@/worktrees/remove'
20
+ import {
21
+ removeReclaimable,
22
+ type RemovalOutcome,
23
+ type RemovalReport,
24
+ } from '@/worktrees/remove'
21
25
 
22
26
  interface ListCommandOptions {
23
27
  readonly json?: boolean
@@ -25,8 +29,51 @@ interface ListCommandOptions {
25
29
 
26
30
  interface ReclaimCommandOptions {
27
31
  readonly dryRun?: boolean
32
+ readonly json?: boolean
33
+ }
34
+
35
+ /**
36
+ * What a caller reads instead of the frame.
37
+ *
38
+ * `.husky/post-merge` is the first caller that is not a person, and it branches
39
+ * on a parsed field rather than on the exit code, since a shell profile
40
+ * wrapping `canon` in a function flattens every status to zero. It reads the
41
+ * record with a pattern rather than a parser, so every field it branches on is
42
+ * a number or a fixed word, and `outcomes` sits last so a greedy pattern
43
+ * anchored on a scalar never reaches into it.
44
+ */
45
+ export interface ReclaimRecord {
46
+ readonly reason: Unreadable | null
47
+ readonly detail: string | null
48
+ readonly dryRun: boolean
49
+ readonly reclaimable: number
50
+ readonly removed: number
51
+ readonly failed: number
52
+ readonly pruned: boolean
53
+ readonly outcomes: readonly RemovalOutcome[]
28
54
  }
29
55
 
56
+ /** Every value the record carries, in the two shapes a run can end in. */
57
+ export type ReclaimSource =
58
+ | {
59
+ readonly kind: 'unreadable'
60
+ readonly reason: Unreadable
61
+ readonly detail: string
62
+ readonly dryRun: boolean
63
+ }
64
+ | {
65
+ readonly kind: 'read'
66
+ readonly dryRun: boolean
67
+ readonly reclaimable: number
68
+ /**
69
+ * Null when no removal ran at all, which covers a reading that found
70
+ * nothing reclaimable and a dry run alike. The two differ on
71
+ * `reclaimable` rather than here, so a caller can tell a quiet repository
72
+ * from a run that was asked not to act.
73
+ */
74
+ readonly removal: RemovalReport | null
75
+ }
76
+
30
77
  const REFUSALS: Record<Refusal, string> = {
31
78
  'main-worktree': 'the main worktree, which is never reclaimable',
32
79
  'current-worktree':
@@ -120,6 +167,7 @@ export function register(program: Command): void {
120
167
  )
121
168
  .helpOption('-h, --help', 'Show this help message')
122
169
  .option('--dry-run', 'Report what would be removed without removing it')
170
+ .option('--json', 'Add a machine-readable record on stdout')
123
171
  .addHelpText(
124
172
  'after',
125
173
  [
@@ -148,6 +196,7 @@ export function register(program: Command): void {
148
196
  'Examples:',
149
197
  ' canon worktrees reclaim --dry-run',
150
198
  ' canon worktrees reclaim',
199
+ ' canon worktrees reclaim --json',
151
200
  '',
152
201
  ].join('\n'),
153
202
  )
@@ -210,6 +259,47 @@ async function runList(opts: ListCommandOptions): Promise<number> {
210
259
  return 0
211
260
  }
212
261
 
262
+ /**
263
+ * Builds the record from the values a run already holds.
264
+ *
265
+ * Separate from the run so the record is testable without a repository, a `gh`
266
+ * credential, or a directory the test would then have to remove. The counts are
267
+ * derived from `outcomes` rather than passed in, since two numbers a caller
268
+ * computes are two numbers a caller can get wrong.
269
+ */
270
+ export function reclaimRecord(source: ReclaimSource): ReclaimRecord {
271
+ if (source.kind === 'unreadable') {
272
+ return {
273
+ reason: source.reason,
274
+ detail: source.detail,
275
+ dryRun: source.dryRun,
276
+ reclaimable: 0,
277
+ removed: 0,
278
+ failed: 0,
279
+ pruned: false,
280
+ outcomes: [],
281
+ }
282
+ }
283
+
284
+ const outcomes = source.removal?.outcomes ?? []
285
+
286
+ return {
287
+ reason: null,
288
+ detail: null,
289
+ dryRun: source.dryRun,
290
+ reclaimable: source.reclaimable,
291
+ removed: outcomes.filter((outcome) => outcome.removed).length,
292
+ failed: outcomes.filter((outcome) => !outcome.removed).length,
293
+ pruned: source.removal?.pruned ?? false,
294
+ outcomes,
295
+ }
296
+ }
297
+
298
+ function emitReclaim(opts: ReclaimCommandOptions, source: ReclaimSource): void {
299
+ if (opts.json !== true) return
300
+ process.stdout.write(`${JSON.stringify(reclaimRecord(source))}\n`)
301
+ }
302
+
213
303
  async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
214
304
  const dryRun = opts.dryRun ?? false
215
305
  const cwd = process.cwd()
@@ -222,6 +312,12 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
222
312
  if (report.kind === 'unreadable') {
223
313
  reportUnreadable(report)
224
314
  outro()
315
+ emitReclaim(opts, {
316
+ kind: 'unreadable',
317
+ reason: report.reason,
318
+ detail: report.detail,
319
+ dryRun,
320
+ })
225
321
  return 1
226
322
  }
227
323
 
@@ -231,6 +327,7 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
231
327
  if (reclaimable.length === 0) {
232
328
  logInfo('None. Every worktree fails at least one condition.')
233
329
  outro()
330
+ emitReclaim(opts, { kind: 'read', dryRun, reclaimable: 0, removal: null })
234
331
  return 0
235
332
  }
236
333
 
@@ -241,6 +338,12 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
241
338
  logStep('Dry run')
242
339
  logWarn('Nothing was removed. Run this again without --dry-run to remove.')
243
340
  outro()
341
+ emitReclaim(opts, {
342
+ kind: 'read',
343
+ dryRun,
344
+ reclaimable: reclaimable.length,
345
+ removal: null,
346
+ })
244
347
  return 0
245
348
  }
246
349
 
@@ -265,12 +368,16 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
265
368
  logWarn(
266
369
  `${plural(failed.length, 'worktree')} could not be removed. Read the reason above and clear it by hand.`,
267
370
  )
268
- outro()
269
- return 1
270
371
  }
271
372
 
272
373
  outro()
273
- return 0
374
+ emitReclaim(opts, {
375
+ kind: 'read',
376
+ dryRun,
377
+ reclaimable: reclaimable.length,
378
+ removal,
379
+ })
380
+ return failed.length > 0 ? 1 : 0
274
381
  }
275
382
 
276
383
  /** Names what happened to one entry, with the failing step where it did not close. */
@@ -410,7 +410,12 @@ export const STAGES: readonly Stage[] = [
410
410
  {
411
411
  id: 'shell',
412
412
  label: 'Shell',
413
- scope: /\.sh$|^package\.json$/,
413
+ // `.husky/` is matched by prefix rather than by extension, since every hook
414
+ // there carries no extension and the `.sh` half reaches none. Without
415
+ // it `check:shell` lints those files only on a branch that happened to
416
+ // touch a `.sh` file or this manifest, which is the gap that let fifty
417
+ // lines of shell land in a hook with no stage firing on it.
418
+ scope: /\.sh$|^\.husky\/|^package\.json$/,
414
419
  skipped: 'Skipped, no shell changes',
415
420
  checks: [
416
421
  {
@@ -17,6 +17,7 @@ Does not govern:
17
17
  - Product scope, goals, and non-goals: `requirements.md`
18
18
  - Path-scoped coding rules: `rule.md`
19
19
  - Diagrams and wireframes, which answer structure and layout questions an entry hands off: `diagrams.md` and `wireframes.md`
20
+ - The consumer-facing reference under `docs/`, whose reader is operating the project without the source open: `docs.md`
20
21
 
21
22
  ## Organizing principle
22
23
 
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Docs reference
3
+ description: Reader and jurisdiction, frontmatter, page structure, what a page links out to, the diagram permission, and when a category earns a subfolder
4
+ ---
5
+
6
+ # Docs reference
7
+
8
+ Applies to each authored reference page under `docs/`. Skip for `index.md` at any depth, which is generated rather than authored.
9
+
10
+ ## Scope
11
+
12
+ Governs each authored reference page under `docs/`, whose reader is an operator running what the project ships rather than developing it, and an agent reading the page cold from a project that consumes it: the frontmatter a page carries, its structure, what it links out to rather than restating, whether it holds a diagram, and when a category earns a subfolder of its own.
13
+
14
+ Neither reader has the source open. That is the whole of the distinction from internal narrative, whose reader does, and from the page a project puts at its front door, whose reader has not decided to install anything yet.
15
+
16
+ Does not govern:
17
+
18
+ - The page a project puts at its front door, whose reader is deciding whether to commit at all: `readme.md`
19
+ - Per-domain internal narrative written for a reader with the source open: `context.md`
20
+ - The drawing inside a Mermaid fence: `mermaid.md`
21
+ - Voice, rhythm, and sentence construction: the `write-human` skill
22
+ - Spelling, banned words, punctuation, and formatting: `markdown.md`
23
+
24
+ ## What a working docs page looks like
25
+
26
+ A page works when a reader who has never opened the source answers all four from the page alone.
27
+
28
+ - What is this surface, and when would I reach for it?
29
+ - What do I run or write to use it, spelled exactly enough to copy?
30
+ - What does it refuse, and what does a refusal look like when I hit one?
31
+ - Where do I go for the adjacent surface this page stops at?
32
+
33
+ A page failing these is non-conforming even when it satisfies every shape rule below. The shape rules are the means. These four questions are the test.
34
+
35
+ ## Frontmatter
36
+
37
+ - `title` (required): sentence case, naming the surface the page covers rather than the action a reader takes on it
38
+ - `description` (required): one line naming what the page covers, which is what the folder's catalog shows beside the link
39
+ - `category` (conditional): the shelf the page sits on in that catalog
40
+ - Declare `category` on every page in a folder where any page declares one. A grouped catalog writes the declared shelves and nothing else, so a page omitting the field beside a sibling that carries one is absent from the catalog with nothing reporting it.
41
+ - Omit it on every page or on none. A folder where no page declares one lists flat, which is the right shape for a folder whose pages need no shelves.
42
+ - Spell one shelf's value identically on every page sitting there. The grouping reads the literal string, so a second spelling opens a second shelf holding one page.
43
+
44
+ ## Structure
45
+
46
+ - Open with an H1 matching `title`, then one or two lines saying what the surface is, before any heading
47
+ - Use `##` for major sections and `###` for subsections. Do not nest deeper.
48
+ - Use sentence case for every heading, proper nouns aside
49
+ - Lead with the invocation a reader came for. Background, rationale, and edge cases sit below it.
50
+ - Write a command, a flag, or a field as something copyable, in a code span or a fence, never paraphrased into prose
51
+
52
+ ## Content
53
+
54
+ - Write for a reader with no access to the source. A sentence that resolves only by opening a file belongs in internal narrative instead.
55
+ - Name the failures a surface produces alongside the path that works. A page documenting success alone sends its reader to the source at the first refusal.
56
+ - Link to the adjacent page rather than restating it. One page owns each surface, and a second copy drifts with nothing comparing the two.
57
+ - Do not restate output a command already prints, such as a full flag table. Name the command that prints it.
58
+ - Do not carry the history of how a surface reached its shape. A page describes the surface as it stands, and the change that moved it is recorded wherever the project tracks work.
59
+ - Do not carry contributor setup or the argument for adopting the project. Both are addressed to a reader who has not installed anything yet.
60
+
61
+ ## Diagrams
62
+
63
+ - Carry a diagram where the page describes a structure or a path the reader has to hold in their head at once. One per page at most, and none is a correct answer for most pages.
64
+ - Draw it as a Mermaid fence or as a committed image the page references. Neither form is preferred, and the pick belongs to whoever knows which one the page's reader reads it in.
65
+ - Follow `mermaid.md` for the drawing inside a fence. It reaches the drawing and stops at the fence markers, so everything around it stays this file's subject.
66
+ - Do not write the drawing as raw inline markup. A rendering host strips markup outside its own allowlist, so an inline drawing survives a local preview and vanishes on the surface the page is read from.
67
+ - Say beneath the diagram, in prose, what it shows. A reader who cannot see the image gets the same answer from the sentence.
68
+
69
+ ## Splitting the folder
70
+
71
+ - Move a `category` into a subfolder once its pages stop reading as a list in the catalog and start reading as a section of one. The shelf is the signal, not a file count, and a count invites an argument about the count instead.
72
+ - Move every page carrying that value. A shelf split across two depths is harder to scan than either arrangement taken whole.
73
+ - Give the subfolder a catalog page of its own, so the parent lists one entry where it listed several. The parent lists a subfolder as a folder rather than as a member of the shelf it replaced, so nothing on that page restores the grouping.
74
+ - Retarget every link into a moved page in the same change. A link resolving nowhere is what the move costs, and nothing else reports it.
75
+
76
+ ## Template
77
+
78
+ ```markdown
79
+ ---
80
+ title: <Surface>
81
+ description: <one line naming what this page covers>
82
+ category: <the shelf this page sits on, carried by every sibling or by none>
83
+ ---
84
+
85
+ # <Surface>
86
+
87
+ <One or two lines on what the surface is and when a reader reaches for it.>
88
+
89
+ ## <The invocation>
90
+
91
+ `<the command or field, spelled to copy>`
92
+
93
+ <What it does and what it produces.>
94
+
95
+ ## <Refusals>
96
+
97
+ - `<the condition>`: <what the reader sees, and what to do about it>
98
+
99
+ ## <Adjacent surface>
100
+
101
+ <Where the reader goes for the thing this page stops at.>
102
+ ```
@@ -13,6 +13,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
13
13
  - [Context entry reference](context.md): Shape and content rules for .claude/context/<domain>.md entries
14
14
  - [Design reference](design.md): Shape and content rules for .claude/DESIGN.md
15
15
  - [Diagram reference](diagrams.md): Shape and content rules for .canon/diagrams/<kind>.md files
16
+ - [Docs reference](docs.md): Reader and jurisdiction, frontmatter, page structure, what a page links out to, the diagram permission, and when a category earns a subfolder
16
17
  - [Glossary reference](glossary.md): Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
17
18
  - [Groundwork reference](groundwork.md): Folder layout, ordinal naming, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
18
19
  - [Intake reference](intake.md): Folder layout, ordinal naming, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
@@ -18,6 +18,7 @@ Does not govern:
18
18
  - Rhythm and sentence construction in README prose: the `write-human` skill, which yields the voice and keeps the rest
19
19
  - Spelling, banned words, punctuation, and formatting in README prose: `markdown.md`, which yields nothing
20
20
  - Product scope and goals: `requirements.md`
21
+ - The consumer-facing reference under `docs/`, whose reader has already committed and is operating the project: `docs.md`
21
22
 
22
23
  ## Voice
23
24