@erclx/canon 4.81.0 → 4.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/docs-fold/REQUIREMENT.md +13 -0
  3. package/claude/skills/docs-fold/SKILL.md +20 -0
  4. package/claude/skills/docs-fold/references/classify.md +78 -0
  5. package/claude/skills/draft-slides/SKILL.md +1 -1
  6. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  7. package/claude/skills/draft-wireframes/SKILL.md +7 -4
  8. package/docs/agents/commands.md +8 -4
  9. package/docs/agents/context-audit-checks.md +22 -2
  10. package/docs/agents/context-audit.md +13 -3
  11. package/docs/agents/context-classify.md +99 -0
  12. package/docs/agents/index.md +1 -0
  13. package/docs/target-projects.md +15 -0
  14. package/docs/workflow/ai-workflow.md +4 -3
  15. package/docs/workflow/visual-design-workflow.md +1 -1
  16. package/governance/rules/claude/520-wireframes.md +1 -1
  17. package/governance/rules/claude/545-decisions.md +12 -0
  18. package/package.json +1 -1
  19. package/src/claude/seeds.ts +1 -0
  20. package/src/commands/claude.ts +26 -5
  21. package/src/commands/context.ts +496 -2
  22. package/src/context/architecture.ts +73 -0
  23. package/src/context/audit.ts +18 -2
  24. package/src/context/classify/extract.ts +450 -0
  25. package/src/context/classify/ollama.ts +172 -0
  26. package/src/context/classify/patterns.ts +114 -0
  27. package/src/context/classify/prompts.ts +73 -0
  28. package/src/context/classify/run.ts +348 -0
  29. package/src/context/classify/settings.ts +196 -0
  30. package/src/context/folders.ts +1 -0
  31. package/src/context/gate.ts +44 -6
  32. package/src/context/wireframe-states.ts +238 -0
  33. package/src/surface-root.ts +1 -0
  34. package/standards/architecture.md +11 -1
  35. package/standards/context.md +4 -1
  36. package/standards/decisions.md +100 -0
  37. package/standards/design.md +6 -0
  38. package/standards/index.md +1 -0
  39. package/standards/requirements.md +2 -1
  40. package/standards/wireframes.md +45 -29
  41. package/tooling/claude/reference.md +2 -1
  42. package/tooling/claude/seeds/CLAUDE.md +2 -1
  43. package/tooling/claude/seeds/canon/decisions/index.md +8 -0
  44. package/tooling/claude/seeds/canon/wireframes/index.md +2 -2
@@ -0,0 +1,238 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises'
2
+ import { basename, dirname, join, relative } from 'node:path'
3
+ import type { AuditedFolder } from '@/context/folders'
4
+ import { type BodyLine, bodyLines } from '@/markdown/scan'
5
+
6
+ /** `standards/wireframes.md`'s `## States` heading, matched by text alone. */
7
+ const STATES_HEADING = /^##\s+States\s*$/i
8
+ const SECTION_HEADING = /^##\s+\S/
9
+ const TABLE_ROW = /^\s*\|/
10
+ const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
11
+
12
+ /** The one exception the standard admits to the states/evidence bijection. */
13
+ const NOT_CAPTURED = 'not captured'
14
+
15
+ /** A fenced block's opening delimiter naming the `plaintext` info string. */
16
+ const PLAINTEXT_FENCE = /^`{3,}\s*plaintext\s*$/i
17
+
18
+ export interface StateRow {
19
+ readonly line: number
20
+ readonly state: string
21
+ /** The evidence cell as written, backticks and all. */
22
+ readonly evidence: string
23
+ }
24
+
25
+ export interface MissingFolderFinding {
26
+ readonly line: number
27
+ readonly state: string
28
+ /** The cited path, so a report names what to go create or fix. */
29
+ readonly path: string
30
+ }
31
+
32
+ export interface UnlistedFolderFinding {
33
+ /** The evidence root the folder sits under, cited by at least one row. */
34
+ readonly root: string
35
+ readonly folder: string
36
+ }
37
+
38
+ export interface WireframeStatesReport {
39
+ readonly rel: string
40
+ readonly rows: readonly StateRow[]
41
+ /** A row whose evidence cell names a path with no folder at it. */
42
+ readonly missingFolders: readonly MissingFolderFinding[]
43
+ /** A folder under a cited root that no row names. */
44
+ readonly unlistedFolders: readonly UnlistedFolderFinding[]
45
+ /** Line of the `plaintext` fence, absent from an entry carrying no sketch. */
46
+ readonly sketchLine?: number
47
+ /** Whether that sketch sits beside a state whose evidence already exists. */
48
+ readonly sketchWithEvidence: boolean
49
+ }
50
+
51
+ function cells(row: string): string[] {
52
+ const parts = row.split('|')
53
+ if ((parts[0] ?? '').trim() === '') parts.shift()
54
+ if ((parts[parts.length - 1] ?? '').trim() === '') parts.pop()
55
+ return parts.map((cell) => cell.trim())
56
+ }
57
+
58
+ function columnIndex(headers: readonly string[], name: string): number {
59
+ return headers.findIndex(
60
+ (header) => header.toLowerCase() === name.toLowerCase(),
61
+ )
62
+ }
63
+
64
+ /**
65
+ * Reads the States table under the `## States` heading, matching its `State`
66
+ * and `Evidence` columns by header text rather than position.
67
+ *
68
+ * Column order is the one part of the standard's own table the standard could
69
+ * still move during its own review, per the plan this ships under. Matching
70
+ * position would break silently on a reorder, where matching text breaks the
71
+ * same way a renamed header would: visibly, by finding nothing.
72
+ */
73
+ export function parseStatesTable(lines: readonly BodyLine[]): StateRow[] {
74
+ let index = 0
75
+ while (index < lines.length && !STATES_HEADING.test(lines[index].text)) {
76
+ index++
77
+ }
78
+ if (index >= lines.length) return []
79
+ index++
80
+
81
+ while (index < lines.length && !SECTION_HEADING.test(lines[index].text)) {
82
+ const separator = lines[index + 1]
83
+ if (
84
+ TABLE_ROW.test(lines[index].text) &&
85
+ separator !== undefined &&
86
+ TABLE_SEPARATOR.test(separator.text)
87
+ ) {
88
+ break
89
+ }
90
+ index++
91
+ }
92
+ if (
93
+ index >= lines.length ||
94
+ SECTION_HEADING.test(lines[index].text) ||
95
+ !TABLE_ROW.test(lines[index].text)
96
+ ) {
97
+ return []
98
+ }
99
+
100
+ const headers = cells(lines[index].text)
101
+ const stateCol = columnIndex(headers, 'State')
102
+ const evidenceCol = columnIndex(headers, 'Evidence')
103
+ if (stateCol === -1 || evidenceCol === -1) return []
104
+
105
+ index += 2
106
+ const rows: StateRow[] = []
107
+ while (index < lines.length && TABLE_ROW.test(lines[index].text)) {
108
+ const row = cells(lines[index].text)
109
+ rows.push({
110
+ line: lines[index].number,
111
+ state: row[stateCol] ?? '',
112
+ evidence: row[evidenceCol] ?? '',
113
+ })
114
+ index++
115
+ }
116
+
117
+ return rows
118
+ }
119
+
120
+ /** Line of the first `plaintext`-fenced sketch, or nothing. */
121
+ export function findSketchLine(lines: readonly BodyLine[]): number | undefined {
122
+ for (const line of lines) {
123
+ if (PLAINTEXT_FENCE.test(line.text.trim())) return line.number
124
+ }
125
+ return undefined
126
+ }
127
+
128
+ /**
129
+ * Reads an evidence cell into the path it cites, or nothing for the one cell
130
+ * value the standard exempts from having one.
131
+ */
132
+ function evidencePath(cell: string): string | undefined {
133
+ const stripped = cell.replace(/^`+|`+$/g, '').trim()
134
+ if (stripped.toLowerCase() === NOT_CAPTURED) return undefined
135
+ return stripped.replace(/\/+$/, '')
136
+ }
137
+
138
+ async function isDirectory(path: string): Promise<boolean> {
139
+ try {
140
+ return (await stat(path)).isDirectory()
141
+ } catch {
142
+ return false
143
+ }
144
+ }
145
+
146
+ async function subfolders(path: string): Promise<string[]> {
147
+ try {
148
+ const entries = await readdir(path, { withFileTypes: true })
149
+ return entries
150
+ .filter((entry) => entry.isDirectory())
151
+ .map((entry) => entry.name)
152
+ } catch {
153
+ return []
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Measures one wireframe entry against its own States table.
159
+ *
160
+ * The evidence cell resolves as the literal path it names against `root`,
161
+ * per the plan's operator answer, since the standard puts the path there and a
162
+ * root guessed from convention reads wrong in a monorepo carrying more than
163
+ * one evidence tree.
164
+ *
165
+ * An unlisted folder is reported only under a root at least one row already
166
+ * cites, per the same operator answer, so a project holding evidence with no
167
+ * states table at all reports nothing. `roots` is built from the rows this
168
+ * loop already resolved rather than scanned separately, which is what keeps
169
+ * the two readings in the same pass.
170
+ */
171
+ export async function measureWireframeStates(
172
+ root: string,
173
+ rel: string,
174
+ source: string,
175
+ ): Promise<WireframeStatesReport> {
176
+ const lines = bodyLines(source)
177
+ const rows = parseStatesTable(lines)
178
+ const sketchLine = findSketchLine(lines)
179
+
180
+ const missingFolders: MissingFolderFinding[] = []
181
+ const cited = new Map<string, Set<string>>()
182
+ let anyCaptured = false
183
+
184
+ for (const row of rows) {
185
+ const path = evidencePath(row.evidence)
186
+ if (path === undefined) continue
187
+
188
+ if (!(await isDirectory(join(root, path)))) {
189
+ missingFolders.push({ line: row.line, state: row.state, path })
190
+ continue
191
+ }
192
+
193
+ anyCaptured = true
194
+ const evidenceRoot = dirname(path)
195
+ const folder = basename(path)
196
+ const named = cited.get(evidenceRoot) ?? new Set<string>()
197
+ named.add(folder)
198
+ cited.set(evidenceRoot, named)
199
+ }
200
+
201
+ const unlistedFolders: UnlistedFolderFinding[] = []
202
+ for (const [evidenceRoot, named] of cited) {
203
+ for (const folder of await subfolders(join(root, evidenceRoot))) {
204
+ if (!named.has(folder)) {
205
+ unlistedFolders.push({ root: evidenceRoot, folder })
206
+ }
207
+ }
208
+ }
209
+
210
+ return {
211
+ rel,
212
+ rows,
213
+ missingFolders,
214
+ unlistedFolders,
215
+ ...(sketchLine !== undefined && { sketchLine }),
216
+ sketchWithEvidence: sketchLine !== undefined && anyCaptured,
217
+ }
218
+ }
219
+
220
+ /** Measures every wireframe entry in a resolved `wireframes` folder. */
221
+ export async function measureWireframeFolder(
222
+ root: string,
223
+ folder: AuditedFolder,
224
+ ): Promise<WireframeStatesReport[]> {
225
+ const reports: WireframeStatesReport[] = []
226
+
227
+ for (const path of folder.entries) {
228
+ reports.push(
229
+ await measureWireframeStates(
230
+ root,
231
+ relative(root, path),
232
+ await readFile(path, 'utf8'),
233
+ ),
234
+ )
235
+ }
236
+
237
+ return reports
238
+ }
@@ -43,6 +43,7 @@ export const SURFACE_ENTRIES: readonly string[] = [
43
43
  'DESIGN.md',
44
44
  'context',
45
45
  'wireframes',
46
+ 'decisions',
46
47
  'canon',
47
48
  ]
48
49
 
@@ -28,16 +28,26 @@ Does not govern:
28
28
  - How individual functions work line by line. The code carries its own behavior.
29
29
  - Full type definitions. They live in code. Reference the shape conceptually if needed.
30
30
  - A measurement paragraph specific to one domain's own mechanism. Route it to that domain's `canon/context/<domain>.md` entry instead. The choice and its rejected alternative stay here whatever their reach, since reach is what makes a decision cross-domain, not how many domains its supporting measurement happens to touch.
31
+ - A decision that constrains one domain alone. It lives in that domain's context entry, and this file carries at most one line pointing at it.
32
+ - The history of how a decision was reached or revised: rounds of candidates, a figure followed by its correction, a branch or change that moved a number. That trail goes to the decision log or the change that introduced it.
31
33
 
32
34
  ## Sections
33
35
 
34
36
  Use `## Overview`, `## Key technical decisions` with one named H3 per decision, and `## Risks / open questions`. Name each decision and give the reasoning, especially for non-obvious choices. Skip entries where the rationale is self-evident.
35
37
 
38
+ ## Keeping it current
39
+
40
+ - Rewrite a decision a later one changed rather than appending the change beside it. A reader should find the design that stands in one place, with the alternative that lost stated once.
41
+ - Hold only what is open under `## Risks / open questions`. An entry leaves the section in the change that settles it, becoming a decision here when it constrains more than one domain and moving to that domain's context entry when it does not.
42
+
36
43
  ## Verification anchors
37
44
 
38
45
  A decision's reasoning stays correct while the numbers it cites move. The anchor records what a measured claim was read against, so a reader can tell a number that was checked and held from one nobody has looked at since.
39
46
 
40
47
  - Close a decision entry whose reasoning cites a measured number with a trailing sentence naming the short commit SHA and the ISO date that number was read: `Measured at <short-sha> on <YYYY-MM-DD>.`
48
+ - Name a commit, never a pull request number or a branch. A branch is gone after merge, and a pull request number resolves only on the forge.
49
+ - Carry one anchor per figure. When a number is re-measured, rewrite the number and its anchor in place, and leave the old value to the change that moved it.
50
+ - Point at a generated file that already records a figure and its commit, rather than copying the figure and anchoring the copy.
41
51
  - Anchor on the number alone. A decision citing none takes no anchor whatever its reasoning rests on, because a marker over a claim nobody can re-measure is one no reader can falsify.
42
52
  - Anchor a decision when writing it or when amending its reasoning. Leave an entry written before the rule unanchored rather than dating it by blame, which is archaeology for a marker nothing reads back.
43
53
  - Read an absent anchor as unchecked rather than as current. On an entry citing no number there is nothing to check. On one citing a number the number is due a read.
@@ -46,7 +56,7 @@ A decision's reasoning stays correct while the numbers it cites move. The anchor
46
56
 
47
57
  ## Length
48
58
 
49
- Every session pays for this file before any work starts, so a heavy read is a real cost. Judge weight by reading the file rather than by counting it: a file that reads heavy is carrying too many decisions, not decisions written too long.
59
+ Every session pays for this file before any work starts, so a heavy read is a real cost. Judge weight by reading the file rather than by counting it: a file that reads heavy is carrying too many decisions, not decisions written too long. A word count, for the file and for each decision, is read alongside that judgment when one is available, and it never gates.
50
60
 
51
61
  - Bring a heavy file back by merging two decisions or retiring one, never by compressing a decision's prose.
52
62
  - Yield to the paragraph weight checkpoint in `markdown.md`. A paragraph past the checkpoint is a defect no length guideline licenses.
@@ -70,6 +70,7 @@ Pick by what the domain is. Add domain-specific headings as needed.
70
70
  ## Ordering
71
71
 
72
72
  - Order sections `Overview`, `Layout`, `Decisions`, `Gotchas`, then everything else.
73
+ - Name a heading for its subject as it stands (`## Cache`), never for the event that shaped it (`## The cache was the untested part, and the test moved it`). A heading is what a scan reads, so a narrating heading turns the table of contents into a changelog.
73
74
  - Entries get read top-down and often partially, so irreducible content sits above recoverable content.
74
75
 
75
76
  ## The development entry
@@ -84,6 +85,7 @@ Only the `development` entry carries this section. It is not a general-purpose h
84
85
  - Decisions specific to the domain. Broader cross-domain decisions belong in `canon/ARCHITECTURE.md`.
85
86
  - Constraints, gotchas, things tried and rejected
86
87
  - Domain-specific conventions that do not fit a `paths:`-scoped rule
88
+ - A measured figure, anchored the way `architecture.md` states under `## Verification anchors`, so an entry and the architecture record date a number the same way.
87
89
  - A reference to another entry, spelled as the path that entry sits at rather than as its bare filename. A bare name resolves against whichever folder the reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A reference to a seed, a standard, or a file the project owns elsewhere keeps the form its own surface uses.
88
90
 
89
91
  ## What does not go in
@@ -98,12 +100,13 @@ Only the `development` entry carries this section. It is not a general-purpose h
98
100
  - Anything already in `canon/REQUIREMENTS.md` or `canon/ARCHITECTURE.md`.
99
101
  - The history of how the domain reached its current shape. An entry describes the repository as it stands, so a change number, release label, or date attached to a change goes wherever the project tracks work.
100
102
  - A rejected alternative's provenance, which is the same rule at the one place the section above admits history. Keep what was tried and why it lost. Cut who tried it and when.
103
+ - The route to a decision: the rounds of candidates, the tuning steps a threshold passed through, the review pass that caught a miss. State the decision, what lost, and why, once and in the present tense. The route goes to the decision log or the change that introduced it.
104
+ - Measured results a generated file already carries. Point at the file instead of copying the figure, so a re-run cannot leave the entry behind.
101
105
 
102
106
  ## Length
103
107
 
104
108
  - Aim for one entry per domain. There is no hard cap. Length is a symptom, not the defect.
105
109
  - Past roughly 150 rendered lines, check three things before adding more: whether the entry still covers a single domain, whether it has filled with content `ls` or `--help` reproduces, and whether it has accumulated the history of its own changes. Fix whichever is true rather than trimming to hit a number. Rendered lines count as `markdown.md` defines them.
106
- - Where a bullet sits past the weight checkpoint `markdown.md` states, the overflow to move is the incident that motivated the decision, which specializes that rule's instruction to send the overflow to prose. Keep the current design and the alternative that lost, and send the incident to the change that introduced it, the issue that tracked it, or the research record behind it.
107
110
  - Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
108
111
  - Retire a decision or gotcha once its subject is gone, rewriting the bullet to state the current design rather than leaving the narration of what it replaced beside it. A rejected alternative is not a retired one, so what was tried and why it lost stays whatever its age. The rule above protects content whose subject is live, and this one releases content whose subject is not.
109
112
  - Rewrite a decision a later one replaced rather than appending the replacement beside it. The subject is still live, so the rule above does not reach it, and two bullets on one subject leave a reader to work out which of them is current. State the design that stands and keep the superseded reasoning only where it is the alternative that lost.
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Decisions reference
3
+ description: Folder layout, ordinal filename, frontmatter, record sections, and the append-only lifecycle for canon/decisions/
4
+ ---
5
+
6
+ # Decisions reference
7
+
8
+ Applies to `canon/decisions/`. Holds the history a canonical doc used to carry itself: pick rounds, superseded figures, and a decision's rejected alternatives, in a tracked folder nothing loads eagerly. A canonical doc points at a record from its own history section rather than restating it.
9
+
10
+ ## Scope
11
+
12
+ Governs `canon/decisions/`: folder layout, the ordinal filename, frontmatter, record sections, and the append-only lifecycle.
13
+
14
+ Does not govern:
15
+
16
+ - Which canonical doc points here and when, and what stays behind in that doc's own body: `architecture.md`, `context.md`, `wireframes.md`, `design.md`, `requirements.md`, once each states its own retirement rule
17
+ - Voice, rhythm, and sentence construction: the `write-human` skill
18
+ - Headings, punctuation, word choice, and file references: `markdown.md`
19
+
20
+ ## What a working record looks like
21
+
22
+ A record works when a reader who has never opened the project can follow it from the file alone:
23
+
24
+ - What was decided, stated once, without needing the canonical doc that points here
25
+ - What else was considered, and why each alternative lost
26
+ - Which claim rests on a measurement, and what commit that measurement was read against
27
+
28
+ A record failing these is non-conforming even when it satisfies every shape rule below.
29
+
30
+ ## Folder name
31
+
32
+ - `canon/decisions/`, resolved the way every tracked surface is: at `canon/decisions/` in a project that has moved, at `.claude/decisions/` in one that has not.
33
+ - Never add `canon/decisions/index.md` to a `CLAUDE.md` `@` import. A log that loads eagerly rebuilds the bloat it exists to absorb. A canonical doc's own pointer is how a reader reaches a record, one file at a time.
34
+
35
+ ## Record filename
36
+
37
+ - Name each record `<nn>-<slug>.md`, a two-digit zero-padded ordinal followed by a kebab-case slug, the same shape a groundwork track's folder takes.
38
+ - The ordinal is the order the record was written in, which is what lets a listing sort by when a decision landed rather than alphabetically by subject.
39
+ - Never renumber an existing record. A later reader cites it by that name, and a record whose number moved is a record a stale citation can no longer find.
40
+
41
+ ## Frontmatter
42
+
43
+ - `title` (required): the decision in sentence case
44
+ - `description` (required): one line naming what was decided
45
+
46
+ ## Sections
47
+
48
+ Use `## Context`, `## Decision`, `## Alternatives`, and `## Measurements`.
49
+
50
+ - `## Context`: the problem as it stood, stated so a reader needs nothing else open. Restate a fact rather than pointing at where it was found.
51
+ - `## Decision`: what was chosen, and why, in enough detail that a reader can tell it apart from an alternative that sounds similar.
52
+ - `## Alternatives`: each one considered and dropped, with the reason it lost. An alternative with no stated reason reads as a claim nobody checked.
53
+ - `## Measurements`: skip when the decision cites no number. When it does, state the number and close with the commit it was read against, per Verification anchors below.
54
+
55
+ ## Verification anchors
56
+
57
+ A record's reasoning stays correct while the numbers it cites move. The anchor records what a measured claim was read against, so a reader can tell a number that was checked and held from one nobody has looked at since.
58
+
59
+ - Close a `## Measurements` section with a trailing sentence naming the short commit SHA and the ISO date that number was read: `Measured at <short-sha> on <YYYY-MM-DD>.`
60
+ - Anchor on the number alone. A record citing none carries no `## Measurements` section at all.
61
+ - Read an absent section as unchecked rather than as current. A record with no measurements has nothing due a re-read.
62
+
63
+ ## Lifecycle
64
+
65
+ - Append-only. A written record is never edited to reflect a later reversal.
66
+ - Write a new record when a later decision supersedes an earlier one, naming the record it supersedes. The old record stays as it was written, since it is history rather than a live statement of the current shape.
67
+ - Never auto-loaded. A canonical doc's own history section links to a record by relative path, and a reader reaches it by following that link, not by the folder loading with the session.
68
+
69
+ ## Citation
70
+
71
+ - Never cite `.canon/`. A record restates what it needs, since a clone without the gitignored records folder resolves nothing there.
72
+ - Cite a same-repository pull request or commit the way `publish.md` fixes for any tracked document.
73
+
74
+ ## Template
75
+
76
+ ```markdown
77
+ ---
78
+ title: <Decision, in sentence case>
79
+ description: <one line naming what was decided>
80
+ ---
81
+
82
+ # <Decision title>
83
+
84
+ ## Context
85
+
86
+ <The problem as it stood, self-contained.>
87
+
88
+ ## Decision
89
+
90
+ <What was chosen, and why.>
91
+
92
+ ## Alternatives
93
+
94
+ - **<Alternative>.** <Why it lost.>
95
+ - **<Alternative>.** <Why it lost.>
96
+
97
+ ## Measurements
98
+
99
+ <The number the decision rests on.> Measured at <short-sha> on <YYYY-MM-DD>.
100
+ ```
@@ -27,6 +27,7 @@ Does not govern:
27
27
 
28
28
  - CSS classes and prop names. Those live in code.
29
29
  - Anything that needs updating every time the code is refactored
30
+ - The history of how a value or a mark was chosen: the candidates, the rounds, the date of a pick. State the rule and the current value. The trail goes to the decision log.
30
31
 
31
32
  ## Format
32
33
 
@@ -48,6 +49,11 @@ A prose section takes its uncertainty inline instead, in a sentence saying what
48
49
 
49
50
  Use `## Personality`, `## Color`, `## Typography`, `## Spacing`, `## Borders`, `## Motion`, and `## Iconography`. The token tables carry fixed headers the renderer reads.
50
51
 
52
+ Add either optional section when the project has content for it:
53
+
54
+ - `## Layout`: page width, breakpoints, and grid rules, as current constraints
55
+ - `## Mark`: the logo's construction rules and the files that carry it
56
+
51
57
  ## Template
52
58
 
53
59
  The column headers are the strings the renderer parses, read by exact key, so they stay verbatim. Row names are not. Each is slugged into the variable name it emits, which leaves a project free to rename a row, add one, or drop one it has no use for.
@@ -11,6 +11,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
11
11
  - [Branch reference](branch.md): Branch naming format and type conventions
12
12
  - [Commit reference](commit.md): Commit message format and type conventions
13
13
  - [Context entry reference](context.md): Shape and content rules for canon/context/<domain>.md entries
14
+ - [Decisions reference](decisions.md): Folder layout, ordinal filename, frontmatter, record sections, and the append-only lifecycle for canon/decisions/
14
15
  - [Design reference](design.md): Shape and content rules for canon/DESIGN.md
15
16
  - [Diagram reference](diagrams.md): Shape and content rules for .canon/diagrams/<kind>.md files
16
17
  - [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
@@ -30,10 +30,11 @@ Does not govern:
30
30
 
31
31
  - Implementation details, API names, or internal component references
32
32
  - Anything that describes how a feature is built rather than what it does
33
+ - Measured results, such as scores, benchmark figures, or token counts. They move on every run and this file changes least. Name where the results live instead.
33
34
 
34
35
  ## Sections
35
36
 
36
- Use `## Problem`, `## Goals`, `## Non-goals`, `## MVP features`, `## Tech stack`, and `## Constraints`. Add `## Distribution` when the rule below applies. Drop a section rather than pad it with filler.
37
+ Use `## Problem`, `## Goals`, `## Non-goals`, `## MVP features`, `## Tech stack`, and `## Constraints`. Add `## Distribution` when the rule below applies. Add `## Premise` when the project exists to answer a question, stated as the question and what would count as an answer, never as the answer measured so far. Drop a section rather than pad it with filler.
37
38
 
38
39
  ## Lifecycle
39
40
 
@@ -24,11 +24,12 @@ Does not govern:
24
24
 
25
25
  A wireframe works when someone can rebuild the surface from it without opening the components:
26
26
 
27
- - What is on screen, and where does it sit relative to everything else?
28
- - Which states can a visitor reach, and what does each one look like?
27
+ - What regions are on screen, by name, and where does each sit relative to the others?
28
+ - Which states can a visitor reach, what triggers each, and where is its captured evidence?
29
29
  - What does its structural copy say, word for word, and where does its long-form content come from?
30
+ - What was deliberately left off the surface?
30
31
 
31
- A wireframe that fails these is non-conforming regardless of whether it satisfies every section rule below. The fences are the means. These three questions are the test.
32
+ A wireframe that fails these is non-conforming regardless of whether it satisfies every section rule below. The sections are the means. These four questions are the test.
32
33
 
33
34
  ## Transcription wireframes
34
35
 
@@ -45,18 +46,20 @@ Route detail the source citation does not already carry to `canon/context/` as u
45
46
 
46
47
  Both fields feed `canon/wireframes/index.md` when regenerated.
47
48
 
48
- ## Layout
49
+ ## Regions
49
50
 
50
- - Draw each surface as an ASCII block inside a `plaintext` fence. One fence per distinct layout.
51
- - Label regions with `←` annotations. Never use `#` for annotations.
52
- - Show a region's role, not its styling. `← status pill` reads better than a class name or hex value. A transcription wireframe is the exception: see `## Transcription wireframes`.
53
- - Keep the grid honest. The ASCII proportions should match the intended widths, since conveying proportion is the wireframe's job.
51
+ - Name every region the surface holds, in a bullet list ordered as a reader scans the page, each with where it sits relative to the others: `- Act pane: right of the answer from 1024 up, an overlay below 1024`. These names are the vocabulary plans, picks, and tests use, and the placement is what a list without a sketch would otherwise lose. Pixel sizes stay in `canon/DESIGN.md`.
52
+ - Draw an ASCII block inside a `plaintext` fence only for a layout that is not built yet. Label regions with `←` annotations, show a region's role rather than its styling, and keep the proportions honest.
53
+ - Remove the fence in the change that first captures evidence for the layout. The capture is the exact picture, and a sketch kept beside it drifts from it. A wireframe carrying both a sketch and an evidence folder for the same layout is non-conforming.
54
+ - Give a layout that changes across a breakpoint its own entry under the list, named by what triggers it (`At 1024 and wider`), not by an arbitrary label. A wider gutter alone is not a new layout.
55
+ - Keep one surface per file, indexed by `index.md`.
54
56
 
55
- ## Variants
57
+ ## States
56
58
 
57
- - Add a second fence only when the layout itself changes across a breakpoint or state. A wider gutter alone is not a new layout.
58
- - Name each variant by what triggers it (`## Desktop (≥768px)`, `## Empty state`), not by an arbitrary label.
59
- - One H2 per variant. Do not stack unrelated surfaces in one file. Keep one surface per file, indexed by `index.md`.
59
+ - List every state a visitor can reach in one table: the state name, what reaches it, what it shows in words, and its evidence folder.
60
+ - Name a state in plain kebab case (`answered`, `refused`) and name its evidence folder the same way, with no ordinal prefix. The table carries the reading order, so a state added in the middle renames nothing.
61
+ - Keep the table and the evidence folders one-to-one. A state with no capture yet reads `not captured` in its evidence cell, which is the one exception, and it stays visible until the capture lands.
62
+ - Add an H3 below the table only for a state that needs more than its row: what differs from the regions in words, and its own copy or behavior.
60
63
 
61
64
  ## Copy
62
65
 
@@ -66,9 +69,19 @@ Both fields feed `canon/wireframes/index.md` when regenerated.
66
69
 
67
70
  ## Behavior
68
71
 
69
- - Describe interaction intent: what the visitor does, what changes on screen, what each state looks like.
72
+ - Describe interaction intent: what the visitor does and what changes on screen.
70
73
  - State the rule, not the mechanism. `The rail tracks the active section as the visitor scrolls` is intent. The scroll handler, throttle, and observer margins are not.
71
- - Keep it to a short list. A Behavior section longer than the layout is a sign implementation detail has leaked in.
74
+ - Keep it to a short list. A Behavior section longer than the regions and states is a sign implementation detail has leaked in.
75
+
76
+ ## Not on this surface
77
+
78
+ - State what was deliberately left off, one bullet per exclusion, as a present-tense rule: `- No navigation rail`. This is the section that stops a draft from proposing what was already ruled out.
79
+ - Keep the reason to a clause where one is needed. The rounds that ruled it out go to the decision log.
80
+
81
+ ## What moves out
82
+
83
+ - Which candidate beat which, and when: the decision log
84
+ - Measurements such as contrast ratios and timings: `canon/DESIGN.md` for tokens and floors, the surface's context entry for the rest
72
85
 
73
86
  ## What moves to canon/context/
74
87
 
@@ -84,13 +97,12 @@ Reference the context entry from the wireframe by path when a reader needs the m
84
97
  ## Maintenance
85
98
 
86
99
  - When a surface's layout or interaction changes, update its wireframe file in the same PR. A wireframe showing a defunct layout is worse than none.
100
+ - When a state is added, removed, or renamed, update its row and its evidence folder in the same PR.
87
101
  - The Behavior and Copy prose around an ASCII block is prose and follows `markdown.md` and the `write-human` skill. The fenced block itself is not, so a check scoped to prose is the wrong thing to rely on for what sits inside it.
88
102
 
89
103
  ## Template
90
104
 
91
- One H2 per layout variant, each holding its own fence. A surface with a single layout carries one.
92
-
93
- ````markdown
105
+ ```markdown
94
106
  ---
95
107
  title: <Surface name>
96
108
  description: <when and where the surface appears>
@@ -98,17 +110,18 @@ description: <when and where the surface appears>
98
110
 
99
111
  # <Surface name>
100
112
 
101
- ## <what triggers this variant>
113
+ <One paragraph: what the surface is for and what it covers.>
102
114
 
103
- ```plaintext
104
- +------------------------------------------+
105
- | <region> ← <its role> |
106
- +------------------------------------------+
107
- | |
108
- | <region> ← <its role> |
109
- | |
110
- +------------------------------------------+
111
- ```
115
+ ## Regions
116
+
117
+ - <Region>: <what it holds>, <where it sits relative to the others>
118
+ - <Region>: <what it holds>, <where it sits relative to the others>
119
+
120
+ ## States
121
+
122
+ | State | Reached when | Shows | Evidence |
123
+ | ------- | ------------ | ---------- | -------------------------- |
124
+ | <state> | <trigger> | <in words> | `<evidence-root>/<state>/` |
112
125
 
113
126
  ## Copy
114
127
 
@@ -119,5 +132,8 @@ description: <when and where the surface appears>
119
132
  ## Behavior
120
133
 
121
134
  - <what the visitor does, and what changes on screen>
122
- - <what each reachable state looks like>
123
- ````
135
+
136
+ ## Not on this surface
137
+
138
+ - <what was deliberately left out, as a present-tense rule>
139
+ ```
@@ -14,7 +14,8 @@ canon/
14
14
  ├── ARCHITECTURE.md ← seeded. Technical design decisions and open questions
15
15
  ├── DESIGN.md ← seeded. Visual intent and the decisions behind it
16
16
  ├── context/ ← seeded. Per-domain narrative. `index.md` is the discovery anchor.
17
- └── wireframes/ ← seeded. Per-surface ASCII layouts. `index.md` is the discovery anchor; `<surface>.md` files hold the sketches and behavior bullets.
17
+ ├── decisions/ ← seeded. Decision history a canonical doc points at, never loaded eagerly. `<nn>-<slug>.md` records.
18
+ └── wireframes/ ← seeded. Per-surface regions and states. `index.md` is the discovery anchor; `<surface>.md` files hold the regions list, the states table, and the behavior bullets.
18
19
 
19
20
  .claude/
20
21
  ├── GOV.md ← retired. Removed by `canon gov sync` if present from a prior install
@@ -21,4 +21,5 @@
21
21
  - `src/`: [description]
22
22
  - `canon/DESIGN.md`: design tokens and the visual system
23
23
  - `canon/context/`: per-domain narrative (how a domain is structured, decisions, gotchas), indexed via `canon/context/index.md`
24
- - `canon/wireframes/`: per-surface ASCII layouts loaded on demand, indexed via `canon/wireframes/index.md`
24
+ - `canon/wireframes/`: per-surface regions and states loaded on demand, indexed via `canon/wireframes/index.md`
25
+ - `canon/decisions/`: decision history a project doc points at, never loaded eagerly
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: Decisions
3
+ subtitle: Decision history a project doc points at, never loaded eagerly
4
+ ---
5
+
6
+ # Decisions
7
+
8
+ Decision history a project doc points at, never loaded eagerly
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: Wireframes
3
- subtitle: Per-surface ASCII layouts loaded on demand
3
+ subtitle: Per-surface regions and states loaded on demand
4
4
  ---
5
5
 
6
6
  # Wireframes
7
7
 
8
- Per-surface ASCII layouts loaded on demand
8
+ Per-surface regions and states loaded on demand