@erclx/canon 4.89.0 → 4.92.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 (65) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/design-taste/REQUIREMENT.md +78 -0
  3. package/claude/skills/design-taste/SKILL.md +148 -0
  4. package/claude/skills/design-taste/references/craft.md +58 -0
  5. package/claude/skills/design-taste/references/kinds.md +52 -0
  6. package/claude/skills/design-taste/references/preflight.md +77 -0
  7. package/claude/skills/design-taste/references/systems.md +49 -0
  8. package/claude/skills/design-taste/references/tells.md +101 -0
  9. package/claude/skills/design-taste/references/vocabulary.md +62 -0
  10. package/claude/skills/docs-fold/REQUIREMENT.md +2 -0
  11. package/claude/skills/docs-fold/SKILL.md +6 -0
  12. package/claude/skills/draft-and-pick/REQUIREMENT.md +8 -2
  13. package/claude/skills/draft-and-pick/SKILL.md +13 -5
  14. package/claude/skills/draft-ready/REQUIREMENT.md +53 -0
  15. package/claude/skills/draft-ready/SKILL.md +118 -0
  16. package/claude/skills/draft-ready/references/assembly.md +67 -0
  17. package/claude/skills/session-compact/REQUIREMENT.md +44 -0
  18. package/claude/skills/session-compact/SKILL.md +60 -0
  19. package/claude/skills/session-compact/references/handoff-note.md +68 -0
  20. package/claude/skills/session-map/REQUIREMENT.md +3 -2
  21. package/claude/skills/session-map/SKILL.md +2 -2
  22. package/claude/skills/session-resume/SKILL.md +3 -3
  23. package/docs/agents/audits.md +1 -1
  24. package/docs/agents/commands.md +1 -1
  25. package/docs/agents/context-audit-checks.md +4 -2
  26. package/docs/agents/context-audit.md +3 -1
  27. package/docs/agents/index.md +1 -1
  28. package/docs/agents/review-classification.md +1 -1
  29. package/docs/agents/routing.md +1 -1
  30. package/docs/agents/state-scoped-risk.md +1 -1
  31. package/docs/agents/teach.md +4 -2
  32. package/docs/workflow/ai-workflow.md +3 -1
  33. package/governance/rules/claude/540-architecture.md +1 -0
  34. package/governance/rules/claude/563-ready.md +4 -0
  35. package/governance/rules/ui/410-a11y.md +9 -0
  36. package/governance/rules/ui/420-forms.md +9 -0
  37. package/governance/rules/ui/460-design-taste.md +29 -0
  38. package/governance/rules/ui/470-motion.md +23 -0
  39. package/governance/stacks/astro.toml +1 -1
  40. package/governance/stacks/react.toml +1 -1
  41. package/package.json +1 -1
  42. package/src/audits/catalog.ts +6 -0
  43. package/src/autoship/paths.ts +1 -1
  44. package/src/claude/cases/authoring.ts +5 -0
  45. package/src/claude/cases/misc.ts +10 -0
  46. package/src/commands/capture.ts +1 -0
  47. package/src/commands/context.ts +22 -4
  48. package/src/commands/design.ts +5 -0
  49. package/src/commands/feedback.ts +1 -0
  50. package/src/commands/slides.ts +3 -0
  51. package/src/commands/transcripts.ts +1 -0
  52. package/src/context/architecture.ts +51 -1
  53. package/src/context/gate.ts +10 -4
  54. package/src/design/base.css +25 -19
  55. package/src/design/css.ts +4 -0
  56. package/src/design/fonts.ts +17 -0
  57. package/src/design/tokens.ts +46 -31
  58. package/src/gate/measures.ts +40 -0
  59. package/src/gate/stages.ts +9 -1
  60. package/src/teach/workspace.ts +8 -2
  61. package/standards/architecture.md +36 -11
  62. package/standards/context.md +2 -2
  63. package/standards/ready.md +2 -0
  64. package/standards/session.md +1 -0
  65. package/tooling/claude/seeds/canon/ARCHITECTURE.md +12 -2
@@ -39,6 +39,12 @@ export interface TypeToken {
39
39
  readonly verify?: readonly ('family' | 'weight' | 'size' | 'lineHeight')[]
40
40
  }
41
41
 
42
+ /** One size the system offers, named apart from any role that takes it. */
43
+ export interface TypeStep {
44
+ readonly step: string
45
+ readonly size: string
46
+ }
47
+
42
48
  export interface SpaceToken {
43
49
  readonly step: string
44
50
  readonly multiplier: string
@@ -57,6 +63,7 @@ export interface DesignTokens {
57
63
  readonly personality: string
58
64
  readonly color: readonly ColorToken[]
59
65
  readonly colorNote: string
66
+ readonly typeScale: readonly TypeStep[]
60
67
  readonly typography: readonly TypeToken[]
61
68
  readonly typographyNote: string
62
69
  readonly spacing: readonly SpaceToken[]
@@ -68,20 +75,19 @@ export interface DesignTokens {
68
75
  readonly preamble: string
69
76
  }
70
77
 
71
- /** The monospace stack every surface but the landing page hero declares. */
78
+ /** The monospace stack, taken by code and by the terminal frames a page embeds. */
72
79
  const MONO = 'Noto Sans Mono, DejaVu Sans Mono, monospace'
73
80
 
74
81
  /**
75
- * The proportional stack the landing page hero declares and nothing else does.
76
- * Noto Sans is the proportional sibling of the mono above rather than a face
77
- * picked for taste, so the pairing is one superfamily rather than two systems.
82
+ * The proportional stack every role but `code` declares. Geist is the face the
83
+ * visual-direction track set throughout, embedded through `FONT_FACES`.
78
84
  *
79
85
  * The stack stops at two names and a generic because `src/design/base.css` is
80
86
  * written by `canon design regen` and formatted by prettier, and a declaration
81
87
  * past 80 columns is wrapped by the second and flattened by the first. Every
82
88
  * emitted line stays inside the width so the two writers never disagree.
83
89
  */
84
- const SANS = 'Noto Sans, DejaVu Sans, sans-serif'
90
+ const SANS = 'Geist Variable, DejaVu Sans, sans-serif'
85
91
 
86
92
  const DARK_GROUNDS = ['background', 'surface'] as const
87
93
  const LIGHT_GROUNDS = ['light-background', 'light-surface'] as const
@@ -94,13 +100,13 @@ export const TOKENS: DesignTokens = {
94
100
  '',
95
101
  'The two rendered captures read it as well. `scripts/core/regen-hero.sh` fills `assets/captures/hero.html.tmpl` and `assets/captures/install.html.tmpl` with what `canon design css --no-components` emits, so both frames now carry the custom properties rather than their own copies of the hex, and a value moved here moves what the next capture renders.',
96
102
  '',
97
- 'The terminal framing is the one surface left holding its own values, and that is a decision rather than a gap. `scripts/lib/ui.sh` and `src/ui.ts` each spell six escape constants, and `canon/ARCHITECTURE.md` records one color source per language with a check behind each, so generating a third spelling from here would break the rule those two checks enforce. What the record is still incomplete about is the other half of those six: `WHITE` and `GREY` name no role below, so the terminal palette is described here in part rather than in whole.',
103
+ 'The terminal framing is the one surface left holding its own values, and that is a decision rather than a gap. `scripts/lib/ui.sh` and `src/ui.ts` each spell six escape constants, and `canon/context/scripts/framing.md` records one color source per language with a check behind each, so generating a third spelling from here would break the rule those two checks enforce. What the record is still incomplete about is the other half of those six: `WHITE` and `GREY` name no role below, so the terminal palette is described here in part rather than in whole.',
98
104
  ].join('\n'),
99
105
 
100
106
  personality: [
101
- 'Warm neutrals carry the frame under a single rust accent, rendered in the same monospace the terminal uses. The subject picks the register rather than taste: a toolkit whose primary surface is a shell has no proportional voice available, so the rendered surfaces match the terminal instead of the reverse. One accent carries every count, link, and primary action. Promoting a second and third into structural roles is what reads as a generated interface, so the palette stays at one.',
107
+ 'Warm neutrals carry the frame under a single rust accent, set in Geist. The subject is a toolkit for people who read documents and diffs as much as they run commands, so the voice is a proportional one and the page reads as prose. One accent carries every count, link, and primary action. Promoting a second and third into structural roles is what reads as a generated interface, so the palette stays at one.',
102
108
  '',
103
- 'One surface is carved out of the sentence above, and it is the landing page hero. A rendered surface here shows a reader what the terminal did, so matching the terminal is what makes it legible. A public page addresses somebody who has never opened the terminal at all, and the shell has no voice available for that, which is the reverse of the case the rule was written for. The `page-display` role below is the whole of the carve-out. Every other role on that page stays monospace, including its body, its controls, and every frame it embeds, so the page reads as two families rather than as a second design system.',
109
+ 'Monospace is a role the page uses rather than its voice. The `code` role takes it, and so does any terminal frame a surface embeds, since those show what the shell printed and matching the shell is what makes them legible. Every other role is Geist.',
104
110
  ].join('\n'),
105
111
 
106
112
  colorNote: [
@@ -236,52 +242,62 @@ export const TOKENS: DesignTokens = {
236
242
  ],
237
243
 
238
244
  typographyNote: [
239
- 'One family covers every role but `page-display`, which is the landing page hero and takes the proportional sibling of the same superfamily. The size scale runs from 11.5 to 52 pixels, and six values map onto a role. Five further values are adjustments inside a single component and get no role here, since a scale with five invented steps reads as a system the surfaces do not implement. They are 11.5, 12.5, 13, 14, and 15 pixels.',
245
+ 'Seven sizes are on offer, named `t0` through `t6` and emitted as custom properties beside the roles. They are 3.175, 2.375, 1.375, 1.125, 0.9375, 0.8125 and 0.6875 rem, which paint at 50.8, 38, 22, 18, 15, 13 and 11 pixels, and each role takes one. A step answers what sizes exist and a role answers what a stylesheet asks for, so the two stay separate: a role whose step did not move renders as it did, and a role that moved is the only thing that changes what a surface paints. Two steps, `t3` at 18 pixels and `t6` at 11, are offered with no role pointing at them yet.',
240
246
  '',
241
- 'The 52 pixel step sits above the 34 the rest of the scale tops out at, and it is the one size no other surface reaches, since a hero headline set at the display cap reads as an opening rather than as a section heading.',
247
+ 'Every role but `code` is Geist and `code` is monospace. The hero headline takes the largest step at 50.8 pixels, which no other role reaches, since a headline set at the display cap reads as an opening rather than as a section heading.',
242
248
  '',
243
249
  'A tagged cell is one no rendering surface exercises yet, which is a declaration the system has not tested rather than one it has.',
244
250
  '',
245
251
  'Two rules set tracking and no others touch it. The label role carries `0.05em`, and the display role tightens to `-0.01em`.',
246
252
  ].join('\n'),
247
253
 
254
+ typeScale: [
255
+ { step: 't0', size: '3.175rem' },
256
+ { step: 't1', size: '2.375rem' },
257
+ { step: 't2', size: '1.375rem' },
258
+ { step: 't3', size: '1.125rem' },
259
+ { step: 't4', size: '0.9375rem' },
260
+ { step: 't5', size: '0.8125rem' },
261
+ { step: 't6', size: '0.6875rem' },
262
+ ],
263
+
248
264
  typography: [
249
265
  {
250
266
  role: 'display',
251
- family: MONO,
267
+ family: SANS,
252
268
  weight: '700',
253
- size: '34px',
269
+ size: '2.375rem',
254
270
  lineHeight: '1.3',
255
271
  },
256
272
  {
257
273
  role: 'page-display',
258
274
  family: SANS,
259
275
  weight: '700',
260
- size: '52px',
276
+ size: '3.175rem',
261
277
  lineHeight: '1.1',
262
- verify: ['family', 'size', 'lineHeight'],
278
+ verify: ['size', 'lineHeight'],
263
279
  },
264
280
  {
265
281
  role: 'heading',
266
- family: MONO,
282
+ family: SANS,
267
283
  weight: '700',
268
- size: '19px',
284
+ size: '1.375rem',
269
285
  lineHeight: '1.3',
270
286
  verify: ['lineHeight'],
271
287
  },
272
288
  {
273
289
  role: 'body',
274
- family: MONO,
290
+ family: SANS,
275
291
  weight: '400',
276
- size: '16px',
292
+ size: '0.9375rem',
277
293
  lineHeight: '1.65',
278
294
  verify: ['weight'],
279
295
  },
280
296
  {
281
297
  role: 'label',
282
- family: MONO,
298
+ family: SANS,
283
299
  weight: '400',
284
- size: '12px',
300
+ size: '0.8125rem',
285
301
  lineHeight: '1.45',
286
302
  verify: ['weight', 'lineHeight'],
287
303
  },
@@ -289,27 +305,26 @@ export const TOKENS: DesignTokens = {
289
305
  role: 'code',
290
306
  family: MONO,
291
307
  weight: '700',
292
- size: '14.5px',
308
+ size: '0.8125rem',
293
309
  lineHeight: '1.3',
294
310
  verify: ['lineHeight'],
295
311
  },
296
312
  ],
297
313
 
298
314
  spacingNote: [
299
- 'The base is six pixels, which is the largest unit dividing the values that recur: 6, 12, 18, 24, and 30. One-off paddings at 9, 10, 11, 13, 14, 16, 22, 26, 34, and 40 pixels sit off the scale entirely and get no step.',
315
+ 'Seven steps run from a quarter rem to six, at 4, 8, 14, 24, 40, 64 and 96 pixels on a 16 pixel root. The scale is not a grid of one base, since the 14 between the 8 and the 24 is the step a control needs and no multiple of four supplies it, so the multiplier column counts quarter rems rather than claiming a unit.',
300
316
  '',
301
- 'The outer window padding is a single declaration reading `44px 52px 38px`, and none of its three values divides by six. They carry no multiplier for that reason, and one declaration setting all three is the only thing grouping them, so they are a frame register rather than a scale.',
317
+ 'The outer window padding of the capture frames, which the previous record carried as a three-value frame register, is not part of the scale. Those frames set their own padding in the template and read no step, so the register is retired here rather than mapped onto steps no frame uses.',
302
318
  ].join('\n'),
303
319
 
304
320
  spacing: [
305
- { step: 'xs', multiplier: '1', value: '6px' },
306
- { step: 'sm', multiplier: '2', value: '12px' },
307
- { step: 'md', multiplier: '3', value: '18px' },
308
- { step: 'lg', multiplier: '4', value: '24px' },
309
- { step: 'xl', multiplier: '5', value: '30px' },
310
- { step: 'frame-top', multiplier: 'none', value: '44px' },
311
- { step: 'frame-inline', multiplier: 'none', value: '52px' },
312
- { step: 'frame-bottom', multiplier: 'none', value: '38px' },
321
+ { step: 'xs', multiplier: '1', value: '0.25rem' },
322
+ { step: 'sm', multiplier: '2', value: '0.5rem' },
323
+ { step: 'md', multiplier: '3.5', value: '0.875rem' },
324
+ { step: 'lg', multiplier: '6', value: '1.5rem' },
325
+ { step: 'xl', multiplier: '10', value: '2.5rem' },
326
+ { step: '2xl', multiplier: '16', value: '4rem' },
327
+ { step: '3xl', multiplier: '24', value: '6rem' },
313
328
  ],
314
329
 
315
330
  bordersNote: [
@@ -7,6 +7,11 @@ import {
7
7
  type ClientCommand,
8
8
  clientCommandCitationsIn,
9
9
  } from '@/client-commands'
10
+ import {
11
+ isOverCount,
12
+ isOverLength,
13
+ measureArchitecture,
14
+ } from '@/context/architecture'
10
15
  import { listRepositoryFiles } from '@/git-files'
11
16
  import {
12
17
  isShippedCorpus,
@@ -222,6 +227,41 @@ function citedPaths(record: { paths?: unknown } | undefined): string[] {
222
227
  })
223
228
  }
224
229
 
230
+ /**
231
+ * The architecture record against the two limits it states for itself: the
232
+ * line ceiling its allowances derive and the entry cap.
233
+ *
234
+ * Read in-process rather than through `context audit`, whose one gating stage
235
+ * here runs `--citations-only` and never opens the record, which is why the
236
+ * line ceiling went unenforced by `bun run check` until this stage. A project
237
+ * carrying no record, or a record stating neither limit, passes, since both
238
+ * limits belong to the record rather than to the toolkit.
239
+ */
240
+ export const architectureRecord: Measure = async (ctx) => {
241
+ const report = await measureArchitecture(ctx.root)
242
+ if (report === undefined) {
243
+ return { emissions: [info('No architecture record to measure')] }
244
+ }
245
+
246
+ const decisions = report.decisions.length
247
+ const failures = [
248
+ isOverCount(report) &&
249
+ `${decisions} decisions against a cap of ${report.entryCap}. Merge two or retire one in ${report.rel}, never compress.`,
250
+ isOverLength(report) &&
251
+ `${report.lines} lines against a ceiling of ${report.ceiling} in ${report.rel}.`,
252
+ ].filter((failure): failure is string => typeof failure === 'string')
253
+
254
+ if (failures.length > 0) return { emissions: [], failure: failures.join(' ') }
255
+
256
+ const cap =
257
+ report.entryCap === undefined
258
+ ? 'no entry cap stated'
259
+ : `a cap of ${report.entryCap}`
260
+ return {
261
+ emissions: [info(`${decisions} decisions against ${cap}`)],
262
+ }
263
+ }
264
+
225
265
  /**
226
266
  * A second run of the records move should rewrite nothing, and the count is
227
267
  * only knowable once the folders themselves have landed.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ architectureRecord,
2
3
  auditSet,
3
4
  captureStamps,
4
5
  clientCommandCitations,
@@ -326,7 +327,7 @@ export const STAGES: readonly Stage[] = [
326
327
  {
327
328
  // A hook, a workflow, or a husky script reaching one side of the seed or
328
329
  // config boundary and not the other is a capability withheld with no
329
- // recorded reason, per the criterion in .claude/ARCHITECTURE.md.
330
+ // recorded reason, per the criterion in canon/context/tooling.md.
330
331
  id: 'capability-seeding',
331
332
  label: 'Capability seeding',
332
333
  checks: [
@@ -362,6 +363,13 @@ export const STAGES: readonly Stage[] = [
362
363
  ],
363
364
  success: 'Context citations resolve',
364
365
  },
366
+ {
367
+ // The record's two limits are facts it states about itself, unlike the
368
+ // judgment thresholds the stage above leaves out, so they gate here.
369
+ id: 'architecture-record',
370
+ label: 'Architecture record',
371
+ checks: [{ kind: 'measure', measure: architectureRecord }],
372
+ },
365
373
  {
366
374
  // A rule citing a file that moved fails silently. The consumed-copy drift
367
375
  // stage passes an authored rule and its copy that are wrong together, and
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
3
- import { join, relative } from 'node:path'
3
+ import { basename, join, relative } from 'node:path'
4
4
  import { TEACH_STYLESHEET_COMPONENTS } from '@/design/components'
5
5
  import { buildDesignCss } from '@/design/css'
6
6
  import { parseFrontmatter, readField } from '@/indexes/frontmatter'
@@ -230,9 +230,14 @@ export function refuse(
230
230
  * Every workspace sits under the main worktree root rather than under the
231
231
  * checkout the caller stands in. Resolving that root belongs to the caller, so
232
232
  * this takes one and never reads the working directory.
233
+ *
234
+ * A root already named `teach` is taken as the teach folder itself rather than
235
+ * a project root to wrap, which is what lets a caller point straight at a
236
+ * folder such as `examples/teach` that holds workspaces outside any `.canon/`
237
+ * or `.claude/` record root.
233
238
  */
234
239
  export function teachDir(root: string): string {
235
- return recordDir(root, 'teach')
240
+ return basename(root) === 'teach' ? root : recordDir(root, 'teach')
236
241
  }
237
242
 
238
243
  async function listSlugs(dir: string): Promise<string[]> {
@@ -241,6 +246,7 @@ async function listSlugs(dir: string): Promise<string[]> {
241
246
  return entries
242
247
  .filter((entry) => entry.isDirectory())
243
248
  .map((entry) => entry.name)
249
+ .filter((name) => WORKSPACE_NAME.test(name))
244
250
  .sort()
245
251
  }
246
252
 
@@ -5,7 +5,7 @@ description: Shape and content rules for canon/ARCHITECTURE.md
5
5
 
6
6
  # Architecture reference
7
7
 
8
- Applies to `canon/ARCHITECTURE.md`. Describes the system shape and the decisions behind it, not a tutorial, setup guide, or implementation walkthrough. Pair it with `CLAUDE.md`: principles live there, patterns and decisions live here. Update when a decision is made or a risk is resolved.
8
+ Applies to `canon/ARCHITECTURE.md`. Describes the system shape and the decisions behind it, not a tutorial, setup guide, or implementation walkthrough. Pair it with `CLAUDE.md`: session behavior lives there, and the system's shape lives here. Update when a decision is made or a risk is resolved.
9
9
 
10
10
  ## Scope
11
11
 
@@ -20,15 +20,26 @@ Does not govern:
20
20
  ## What goes in
21
21
 
22
22
  - A high-level overview of how the system is structured and why
23
- - Key technical decisions as named H3 entries: what was chosen and why over the alternatives, including stack and library choices
23
+ - Key technical decisions as named H3 entries, each filling one of the five slots below: what was chosen and why over the alternatives
24
24
  - Risks and open questions still unresolved
25
25
 
26
+ A decision belongs here only when it fills a slot:
27
+
28
+ - Stack and runtime: the language, the runtime, and the major libraries the system is built on
29
+ - Delivery: how the system's content or code reaches the place it runs
30
+ - Enforced boundaries: a boundary the system checks mechanically, and what enforces it
31
+ - Layout: how the project's top-level roots and context tiers divide what they hold
32
+ - Build principles: a rule for how a behavior is built that changes work in more than one domain, stated in one sentence
33
+
34
+ The slots come from a scale test. Ask whether the decision would still be in this file if the project were ten times its size. At that size an always-loaded file holding one entry per decision fits no context window, and what survives is the shape: the stack, the delivery, the boundaries, the layout, and the principles. A decision that fits no slot is a domain decision, and its home is that domain's `canon/context/<domain>.md` entry under `## Decisions`, however many domains its reasoning touches. Reach is not the test, since nearly every decision reaches a second domain.
35
+
26
36
  ## What does not go in
27
37
 
28
38
  - How individual functions work line by line. The code carries its own behavior.
29
39
  - Full type definitions. They live in code. Reference the shape conceptually if needed.
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.
40
+ - A decision that fills no slot. It lives in the domain context entry it constrains, and this file carries at most one line pointing at it.
41
+ - A measurement paragraph specific to one domain's own mechanism, even behind a slot decision. Route it to that domain's context entry and keep the choice, the alternative that lost, and one reason here.
42
+ - The instances of a build principle. The principle takes one sentence here, and each instance lives in the domain entry where it applies.
32
43
  - 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.
33
44
 
34
45
  ## Sections
@@ -38,7 +49,7 @@ Use `## Overview`, `## Key technical decisions` with one named H3 per decision,
38
49
  ## Keeping it current
39
50
 
40
51
  - 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.
52
+ - 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 fills a slot and moving to the domain context entry it constrains when it does not.
42
53
 
43
54
  ## Verification anchors
44
55
 
@@ -54,27 +65,41 @@ A decision's reasoning stays correct while the numbers it cites move. The anchor
54
65
  - Do not edit a claim in the pass that first anchors it. The anchor states what the claim was measured against, so changing both at once leaves nothing to check the anchor against.
55
66
  - Refresh the anchor whenever the number is re-read, whether or not it moved. A confirmed number and an unread one are the same text without the date.
56
67
 
57
- ## Length
68
+ ## Entry cap
58
69
 
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.
70
+ Every session pays for this file before any work starts, so a heavy read is a real cost. A file that reads heavy is carrying too many decisions, not decisions written too long, so the bound is a count of decisions rather than of words.
60
71
 
61
- - Bring a heavy file back by merging two decisions or retiring one, never by compressing a decision's prose.
72
+ - State the cap in the record itself as a clause of the form `This record holds at most <n> decisions.` The cap is the record's own, so a checker reads it from the file, and a record stating none is measured and never gated.
73
+ - Set the cap near one decision per slot plus a small margin.
74
+ - At the cap, merge two decisions or retire one before adding another, and name which in the change that does it. Never compress a decision's prose to fit, and never pack two decisions under one heading, which the count cannot see.
75
+ - Retire a decision by moving it to the domain context entry it constrains, not by deleting its reasoning.
76
+ - A word count, for the file and for each decision, is read alongside the judgment when one is available, and it never gates.
62
77
  - Yield to the paragraph weight checkpoint in `markdown.md`. A paragraph past the checkpoint is a defect no length guideline licenses.
63
78
 
64
79
  ## Template
65
80
 
66
- The anchor sentence closes a decision whose reasoning cites a measured number and is absent from one that cites none.
81
+ The anchor sentence closes a decision whose reasoning cites a measured number and is absent from one that cites none. Each heading below names a slot, so a record starts with one decision per slot and renames each heading to the choice it records.
67
82
 
68
83
  ```markdown
69
84
  # Architecture
70
85
 
71
86
  ## Overview
72
87
 
88
+ This record holds at most 12 decisions.
89
+
73
90
  ## Key technical decisions
74
91
 
75
- ### Decision name
92
+ ### Stack and runtime
93
+
94
+ What was chosen, the alternative that lost, and why. Measured at <short-sha> on <YYYY-MM-DD>.
95
+
96
+ ### Delivery
97
+
98
+ ### Enforced boundaries
99
+
100
+ ### Layout
76
101
 
77
- Reasoning and tradeoffs, carrying the measured number the choice rested on. Measured at <short-sha> on <YYYY-MM-DD>.
102
+ ### Build principles
78
103
 
79
104
  ## Risks / open questions
80
105
  ```
@@ -13,7 +13,7 @@ Governs per-domain narrative entries under `canon/context/`: their structure, th
13
13
 
14
14
  Does not govern:
15
15
 
16
- - Cross-domain decisions and system-wide risks: `architecture.md`
16
+ - Decisions filling a stack, delivery, boundary, layout, or build-principle slot, and system-wide risks: `architecture.md`
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`
@@ -82,7 +82,7 @@ Only the `development` entry carries this section. It is not a general-purpose h
82
82
  ## What goes in
83
83
 
84
84
  - Per-domain narrative on how the code is structured and why
85
- - Decisions specific to the domain. Broader cross-domain decisions belong in `canon/ARCHITECTURE.md`.
85
+ - Decisions that constrain the domain, whether or not their reasoning touches another one. This is a decision's default home, and `canon/ARCHITECTURE.md` takes only a decision that fills one of the slots its standard names.
86
86
  - Constraints, gotchas, things tried and rejected
87
87
  - Domain-specific conventions that do not fit a `paths:`-scoped rule
88
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.
@@ -11,6 +11,8 @@ The folder is gitignored, and backed wherever a records remote is configured: `c
11
11
 
12
12
  ## Scope
13
13
 
14
+ The `draft-ready` skill owns the procedure that writes a ready folder, and this standard owns the shape it writes to.
15
+
14
16
  Governs a ready folder under `.canon/ready/<nn>-<slug>/`: folder layout, ordinal naming, the overview's frontmatter, what the mirrored tree holds, the thin-plan contract that ships it, and the lifecycle from the live folder to the archive.
15
17
 
16
18
  Does not govern:
@@ -18,6 +18,7 @@ Does not govern:
18
18
  - The task board the file sits beside, its filenames, and its archiving: `tasks.md`
19
19
  - The transform from a branch name to the slug in the filename: `slug.md`
20
20
  - The role-specific sections a caller adds over the core, which belong to that caller
21
+ - The note a plain session writes at `.canon/compact/<slug>.md`, whose shape lives in the `session-compact` skill's own reference
21
22
  - Which memories a session captures before writing, which is the capture surface's own subject
22
23
 
23
24
  ## What a working session map looks like
@@ -4,10 +4,20 @@ Authoring guidance: the architecture standard.
4
4
 
5
5
  ## Overview
6
6
 
7
+ This record holds at most 12 decisions.
8
+
7
9
  ## Key technical decisions
8
10
 
9
- ### Decision name
11
+ ### Stack and runtime
12
+
13
+ What was chosen, the alternative that lost, and why.
14
+
15
+ ### Delivery
16
+
17
+ ### Enforced boundaries
18
+
19
+ ### Layout
10
20
 
11
- Reasoning and tradeoffs.
21
+ ### Build principles
12
22
 
13
23
  ## Risks / open questions