@erclx/canon 5.38.0 → 5.40.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 (246) hide show
  1. package/README.md +5 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +12 -9
  4. package/claude/skills/auto-ship/SKILL.md +43 -23
  5. package/claude/skills/backlog-triage/SKILL.md +62 -53
  6. package/claude/skills/backlog-triage/references/apply.md +69 -0
  7. package/claude/skills/build-in-slices/SKILL.md +8 -4
  8. package/claude/skills/canon-cli/SKILL.md +5 -1
  9. package/claude/skills/canon-feedback/SKILL.md +18 -8
  10. package/claude/skills/canon-feedback-triage/SKILL.md +10 -3
  11. package/claude/skills/canvas-board/SKILL.md +37 -18
  12. package/claude/skills/ci-workflow/SKILL.md +5 -4
  13. package/claude/skills/code-craft/SKILL.md +10 -6
  14. package/claude/skills/codebase-layout/SKILL.md +3 -5
  15. package/claude/skills/context-fold/REQUIREMENT.md +4 -4
  16. package/claude/skills/context-fold/SKILL.md +42 -23
  17. package/claude/skills/context-fold/references/context-refresh.md +4 -3
  18. package/claude/skills/context-fold/references/context.md +79 -64
  19. package/claude/skills/create-rule/SKILL.md +7 -3
  20. package/claude/skills/create-skill/SKILL.md +27 -7
  21. package/claude/skills/create-skill/references/skill-reference.md +41 -17
  22. package/claude/skills/decision-escalate/SKILL.md +8 -5
  23. package/claude/skills/deploy-app/SKILL.md +1 -1
  24. package/claude/skills/deprecation-migration/SKILL.md +12 -4
  25. package/claude/skills/design-taste/SKILL.md +17 -11
  26. package/claude/skills/docs-sync/REQUIREMENT.md +1 -0
  27. package/claude/skills/docs-sync/SKILL.md +17 -6
  28. package/claude/skills/docs-sync/references/docs.md +54 -21
  29. package/claude/skills/docs-sync/references/readme.md +32 -8
  30. package/claude/skills/document-health/SKILL.md +3 -4
  31. package/claude/skills/draft-and-pick/SKILL.md +73 -47
  32. package/claude/skills/draft-doc/SKILL.md +9 -4
  33. package/claude/skills/draft-doc/references/context.md +4 -4
  34. package/claude/skills/draft-doc/references/docs.md +1 -0
  35. package/claude/skills/draft-doc/references/wiki.md +3 -2
  36. package/claude/skills/draft-figure/SKILL.md +9 -7
  37. package/claude/skills/draft-identity/SKILL.md +24 -52
  38. package/claude/skills/draft-identity/references/card-route.md +46 -0
  39. package/claude/skills/draft-ready/SKILL.md +19 -7
  40. package/claude/skills/draft-screencast/SKILL.md +6 -112
  41. package/claude/skills/draft-screencast/references/beats-template.md +119 -0
  42. package/claude/skills/draft-slides/SKILL.md +9 -7
  43. package/claude/skills/draft-slides/references/deck.md +183 -0
  44. package/claude/skills/git-branch/SKILL.md +3 -3
  45. package/claude/skills/git-commit/SKILL.md +5 -3
  46. package/claude/skills/git-followup/SKILL.md +16 -9
  47. package/claude/skills/git-issue/SKILL.md +6 -5
  48. package/claude/skills/git-pr/SKILL.md +56 -44
  49. package/claude/skills/git-pr/references/evidence.md +2 -2
  50. package/claude/skills/git-pr/references/final-command.md +1 -1
  51. package/claude/skills/git-pr/references/labels.md +1 -1
  52. package/claude/skills/git-pr/references/publish.md +1 -1
  53. package/claude/skills/git-ship/SKILL.md +43 -34
  54. package/claude/skills/git-ship/references/ci-watch.md +31 -0
  55. package/claude/skills/git-split/SKILL.md +28 -100
  56. package/claude/skills/git-split/references/independent.md +40 -0
  57. package/claude/skills/git-split/references/stacked.md +73 -0
  58. package/claude/skills/git-stage/SKILL.md +9 -7
  59. package/claude/skills/git-worktree/SKILL.md +27 -49
  60. package/claude/skills/git-worktree/references/cleanup.md +55 -0
  61. package/claude/skills/markdown-craft/SKILL.md +2 -2
  62. package/claude/skills/markdown-craft/references/markdown.md +82 -53
  63. package/claude/skills/plan-feature/SKILL.md +52 -46
  64. package/claude/skills/plan-feature/references/batches.md +46 -0
  65. package/claude/skills/plan-groundwork/SKILL.md +60 -74
  66. package/claude/skills/plan-groundwork/references/output.md +48 -0
  67. package/claude/skills/plan-intake/SKILL.md +28 -21
  68. package/claude/skills/plan-intake-answer/SKILL.md +14 -13
  69. package/claude/skills/read-frames/SKILL.md +2 -0
  70. package/claude/skills/record-screencast/SKILL.md +31 -11
  71. package/claude/skills/review-address/SKILL.md +76 -158
  72. package/claude/skills/review-address/references/rebase-only.md +34 -0
  73. package/claude/skills/review-craft/SKILL.md +27 -11
  74. package/claude/skills/review-pr/SKILL.md +91 -40
  75. package/claude/skills/review-ui/SKILL.md +63 -37
  76. package/claude/skills/review-ui/references/hosted-preview.md +30 -0
  77. package/claude/skills/role-orchestrator/SKILL.md +105 -74
  78. package/claude/skills/role-orchestrator/references/orchestrator-filing.md +26 -0
  79. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  80. package/claude/skills/role-planner/SKILL.md +66 -64
  81. package/claude/skills/role-reviewer/SKILL.md +57 -46
  82. package/claude/skills/role-worker/SKILL.md +68 -63
  83. package/claude/skills/search-craft/SKILL.md +7 -5
  84. package/claude/skills/session-compact/SKILL.md +16 -11
  85. package/claude/skills/session-map/SKILL.md +31 -10
  86. package/claude/skills/session-relay/SKILL.md +32 -65
  87. package/claude/skills/session-resume/SKILL.md +18 -13
  88. package/claude/skills/session-worktree/SKILL.md +70 -42
  89. package/claude/skills/systematic-debugging/SKILL.md +6 -1
  90. package/claude/skills/target-check/SKILL.md +13 -6
  91. package/claude/skills/target-setup/SKILL.md +39 -27
  92. package/claude/skills/task-board/SKILL.md +68 -60
  93. package/claude/skills/task-board/references/board.md +1 -1
  94. package/claude/skills/task-board/references/create-report.md +36 -0
  95. package/claude/skills/task-board/references/tasks.md +1 -1
  96. package/claude/skills/task-complete/SKILL.md +20 -18
  97. package/claude/skills/teach-workspace/SKILL.md +61 -33
  98. package/claude/skills/test-craft/SKILL.md +2 -1
  99. package/claude/skills/test-first/SKILL.md +15 -9
  100. package/claude/skills/ui-checklist/SKILL.md +18 -16
  101. package/claude/skills/ux-audit/SKILL.md +9 -10
  102. package/claude/skills/ux-measure/SKILL.md +23 -19
  103. package/claude/skills/video-craft/SKILL.md +15 -7
  104. package/claude/skills/write-human/SKILL.md +9 -1
  105. package/claude/skills/youtube-transcripts/SKILL.md +2 -1
  106. package/docs/agents/audits.md +61 -60
  107. package/docs/agents/context-audit.md +40 -58
  108. package/docs/agents/index.md +11 -61
  109. package/docs/agents/indexes.md +25 -29
  110. package/docs/agents/install-and-sync.md +125 -235
  111. package/docs/agents/markdown-audit.md +54 -96
  112. package/docs/agents/migrate.md +41 -16
  113. package/docs/agents/output-shape.md +60 -45
  114. package/docs/agents/overview.md +36 -12
  115. package/docs/agents/records.md +37 -79
  116. package/docs/agents/sessions-transfer.md +26 -70
  117. package/docs/agents/targets.md +25 -77
  118. package/docs/target/index.md +0 -1
  119. package/docs/target/migrations.md +57 -66
  120. package/docs/target/projects.md +36 -78
  121. package/docs/target/scenarios.md +9 -19
  122. package/docs/target/sync.md +27 -68
  123. package/docs/workflow/ai-workflow.md +22 -68
  124. package/docs/workflow/operating-model.md +32 -186
  125. package/docs/workflow/parallel-features.md +29 -66
  126. package/docs/workflow/skill-map-on-request.md +22 -24
  127. package/docs/workflow/skill-map.md +72 -76
  128. package/docs/workflow/visual-design-workflow.md +25 -118
  129. package/docs/workflow/zshrc-aliases.md +18 -37
  130. package/package.json +1 -1
  131. package/src/canvas/client/inspector/fields.css +4 -0
  132. package/src/canvas/index.html +6 -0
  133. package/src/claude/skills-reach.ts +2 -2
  134. package/src/cli/ui.ts +3 -1
  135. package/src/commands/canvas.ts +23 -0
  136. package/src/commands/capture.ts +14 -0
  137. package/src/commands/claude.ts +14 -1
  138. package/src/commands/context.ts +106 -7
  139. package/src/commands/demo.ts +22 -0
  140. package/src/commands/deps.ts +6 -0
  141. package/src/commands/driver.ts +28 -2
  142. package/src/commands/gov.ts +52 -18
  143. package/src/commands/hooks.ts +63 -0
  144. package/src/commands/intake.ts +17 -0
  145. package/src/commands/labels.ts +38 -5
  146. package/src/commands/markdown.ts +169 -32
  147. package/src/commands/pr.ts +160 -43
  148. package/src/commands/records.ts +8 -0
  149. package/src/commands/secrets.ts +6 -0
  150. package/src/commands/sessions.ts +8 -0
  151. package/src/commands/tasks.ts +52 -0
  152. package/src/commands/teach.ts +13 -0
  153. package/src/commands/upstream.ts +25 -1
  154. package/src/commands/worktrees.ts +5 -0
  155. package/src/context/audit.ts +274 -5
  156. package/src/context/classify/prompts.ts +1 -1
  157. package/src/context/folders.ts +1 -1
  158. package/src/context/gate.ts +23 -7
  159. package/src/context/shape-gated.ts +30 -0
  160. package/src/counts/scan.ts +1 -1
  161. package/src/design/components.ts +1 -1
  162. package/src/design/document.ts +1 -1
  163. package/src/design/tokens.ts +1 -1
  164. package/src/gate/boundaries.ts +4 -1
  165. package/src/gate/measures.ts +16 -191
  166. package/src/gate/stages.ts +14 -30
  167. package/src/git/env.ts +6 -0
  168. package/src/hooks/reply-links.ts +110 -0
  169. package/src/markdown/density.ts +258 -0
  170. package/src/markdown/gate.ts +57 -18
  171. package/src/markdown/reference-shape.ts +103 -0
  172. package/src/markdown/scope.ts +162 -0
  173. package/src/markdown/structure.ts +9 -59
  174. package/src/pr/lift.ts +153 -19
  175. package/src/pr/painted.ts +87 -0
  176. package/src/roots/record.ts +18 -1
  177. package/src/sandbox/census.ts +1 -1
  178. package/src/sandbox/headless/run.ts +3 -2
  179. package/src/sandbox/headless/watch.ts +3 -4
  180. package/src/shipped/references.ts +1 -1
  181. package/src/slides/convert/master.ts +3 -0
  182. package/src/sync/format.ts +46 -0
  183. package/src/sync/stamp.ts +6 -0
  184. package/tooling/astro/reference.md +2 -2
  185. package/tooling/base/reference.md +1 -1
  186. package/tooling/claude/seeds/.claude/hooks/reply-links.sh +72 -0
  187. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +22 -1
  188. package/tooling/claude/seeds/.claude/settings.json +4 -0
  189. package/tooling/go/reference.md +1 -1
  190. package/tooling/nextjs/reference.md +1 -1
  191. package/tooling/php/reference.md +1 -1
  192. package/tooling/python/reference.md +1 -1
  193. package/tooling/vite-react/reference.md +1 -1
  194. package/tooling/web/reference.md +1 -1
  195. package/docs/agents/canvas.md +0 -86
  196. package/docs/agents/capture.md +0 -65
  197. package/docs/agents/census.md +0 -23
  198. package/docs/agents/commands.md +0 -154
  199. package/docs/agents/comments.md +0 -34
  200. package/docs/agents/context-audit-checks.md +0 -90
  201. package/docs/agents/context-classify.md +0 -101
  202. package/docs/agents/counts.md +0 -74
  203. package/docs/agents/demo.md +0 -140
  204. package/docs/agents/docs.md +0 -27
  205. package/docs/agents/drift-surfaces.md +0 -201
  206. package/docs/agents/driver.md +0 -103
  207. package/docs/agents/gate.md +0 -90
  208. package/docs/agents/gov-detect.md +0 -47
  209. package/docs/agents/hooks.md +0 -58
  210. package/docs/agents/intake.md +0 -81
  211. package/docs/agents/key-changes.md +0 -116
  212. package/docs/agents/label-coverage.md +0 -86
  213. package/docs/agents/markdown-audit-weight.md +0 -81
  214. package/docs/agents/pr-evidence-layout.md +0 -70
  215. package/docs/agents/pr-evidence.md +0 -273
  216. package/docs/agents/pr-frames.md +0 -131
  217. package/docs/agents/pr-lift.md +0 -132
  218. package/docs/agents/pr-local.md +0 -111
  219. package/docs/agents/pr-preview.md +0 -155
  220. package/docs/agents/pr-reads.md +0 -111
  221. package/docs/agents/pr-tick.md +0 -72
  222. package/docs/agents/records-push.md +0 -61
  223. package/docs/agents/records-stale.md +0 -40
  224. package/docs/agents/records-validate.md +0 -57
  225. package/docs/agents/restated.md +0 -83
  226. package/docs/agents/routing.md +0 -61
  227. package/docs/agents/rule-citations.md +0 -90
  228. package/docs/agents/sandbox.md +0 -126
  229. package/docs/agents/scripting.md +0 -142
  230. package/docs/agents/sessions.md +0 -128
  231. package/docs/agents/skills-audit.md +0 -120
  232. package/docs/agents/skills-reach.md +0 -64
  233. package/docs/agents/slides.md +0 -176
  234. package/docs/agents/state-scoped-risk.md +0 -105
  235. package/docs/agents/superseded.md +0 -85
  236. package/docs/agents/tasks-archive.md +0 -83
  237. package/docs/agents/tasks-plans.md +0 -152
  238. package/docs/agents/tasks-validate.md +0 -133
  239. package/docs/agents/tasks.md +0 -123
  240. package/docs/agents/teach-authoring.md +0 -118
  241. package/docs/agents/teach.md +0 -157
  242. package/docs/agents/test-order.md +0 -58
  243. package/docs/agents/upstream.md +0 -94
  244. package/docs/agents/worktrees.md +0 -118
  245. package/docs/target/standards-moved.md +0 -34
  246. package/src/markdown/growth.ts +0 -43
@@ -67,8 +67,7 @@ export const REQUIRED_SECTIONS_BY_FOLDER: Readonly<
67
67
  * The closed set of H2 sections per audited folder name, which a required
68
68
  * section belongs to. An entry carrying another H2 is a finding under
69
69
  * `--gate`, since the set is what makes a context entry cheap to find a fact
70
- * in: a domain's own topic names go one level down as H3 items under
71
- * `Decisions` and `Gotchas`.
70
+ * in: a domain's folders go one level down as H3 items under `Layout`.
72
71
  *
73
72
  * Held beside the required lists for the same reason they are held here, and
74
73
  * keyed on the folder whose standard states it.
@@ -76,7 +75,46 @@ export const REQUIRED_SECTIONS_BY_FOLDER: Readonly<
76
75
  export const ALLOWED_SECTIONS_BY_FOLDER: Readonly<
77
76
  Record<string, readonly string[]>
78
77
  > = {
79
- context: ['Overview', 'Layout', 'Decisions', 'Gotchas'],
78
+ context: ['Overview', 'Layout', 'Gotchas'],
79
+ }
80
+
81
+ /**
82
+ * H2 sections the standard once allowed and now forbids, per audited folder.
83
+ *
84
+ * Kept apart from the extra sections because every unrebuilt entry still
85
+ * carries one, and an extra section gates under `--gate` everywhere. A retired
86
+ * section is a shape finding instead, which gates only where the folder was
87
+ * rebuilt, per `SHAPE_GATED_FOLDERS`.
88
+ */
89
+ export const RETIRED_SECTIONS_BY_FOLDER: Readonly<
90
+ Record<string, readonly string[]>
91
+ > = {
92
+ context: ['Decisions'],
93
+ }
94
+
95
+ /** Bullets a `Gotchas` section may hold, counted at any indent. */
96
+ export const GOTCHA_CAP = 5
97
+
98
+ /** Bullets a folder H3 under `Layout` may hold, counted at any indent. */
99
+ export const LAYOUT_BULLET_CAP = 5
100
+
101
+ /** Paragraphs a folder H3 under `Layout` may hold, being its one lead. */
102
+ export const LAYOUT_PARAGRAPH_CAP = 1
103
+
104
+ /** The one Layout H3 not titled with a path, holding the trivial folders. */
105
+ export const OTHER_FOLDERS = 'Other folders'
106
+
107
+ /** The run-command table H3 only the development entry carries under `Layout`. */
108
+ export const SCRIPTS_HEADING = 'Scripts'
109
+
110
+ /**
111
+ * Whether an entry path is the development entry, being the flat entry, the
112
+ * overview of a split one a target may still carry, or this repository's
113
+ * `local-loop` entry. The standard exempts the
114
+ * `Scripts` H3 for that entry alone, so the path is what tells.
115
+ */
116
+ function isDevelopmentEntry(rel: string): boolean {
117
+ return /(?:^|\/)development(?:\/(?:overview|local-loop))?\.md$/.test(rel)
80
118
  }
81
119
 
82
120
  /**
@@ -210,6 +248,34 @@ export interface BareReferenceFinding {
210
248
  readonly name: string
211
249
  }
212
250
 
251
+ /**
252
+ * Each way an entry breaks the section shape the context standard states.
253
+ *
254
+ * `retired-section` is a `Decisions` H2. The three `gotcha-*` kinds are a
255
+ * `Gotchas` section past its bullet cap, holding a heading, or holding a
256
+ * paragraph. The three `layout-*` kinds are a Layout H3 titled neither a path
257
+ * nor `Other folders`, a folder H3 past its bullet or paragraph cap, and a
258
+ * heading below H3 under `Layout`.
259
+ */
260
+ export type ShapeKind =
261
+ | 'retired-section'
262
+ | 'gotcha-count'
263
+ | 'gotcha-heading'
264
+ | 'gotcha-paragraph'
265
+ | 'layout-heading'
266
+ | 'layout-bullets'
267
+ | 'layout-paragraphs'
268
+ | 'layout-depth'
269
+
270
+ export interface ShapeIssue {
271
+ readonly line: number
272
+ readonly kind: ShapeKind
273
+ /** The heading or line as written, so a report names where to look. */
274
+ readonly text: string
275
+ /** Bullets or paragraphs counted, on the two cap kinds alone. */
276
+ readonly count?: number
277
+ }
278
+
213
279
  export interface EntryReport {
214
280
  readonly rel: string
215
281
  /**
@@ -247,6 +313,12 @@ export interface EntryReport {
247
313
  * all is `extraSections`.
248
314
  */
249
315
  readonly otherSections: readonly string[]
316
+ /**
317
+ * Breaks of the section shape, in document order, and empty outside the
318
+ * governed folder. Whether one gates depends on where the entry sits, which
319
+ * `shapeFindings` decides rather than the measure.
320
+ */
321
+ readonly shape: readonly ShapeIssue[]
250
322
  /**
251
323
  * Whether the file declares itself a skeleton, which excludes it from the
252
324
  * section check alone. Every other measure still reads it, since a stub is
@@ -483,6 +555,7 @@ const H2_TEXT = /^##\s+(.+?)\s*$/
483
555
  function otherSections(
484
556
  lines: readonly BodyLine[],
485
557
  allowed: readonly string[],
558
+ retired: readonly string[],
486
559
  ): string[] {
487
560
  if (allowed.length === 0) return []
488
561
 
@@ -492,12 +565,166 @@ function otherSections(
492
565
  if (line.fenced) continue
493
566
 
494
567
  const match = line.text.match(H2_TEXT)
495
- if (match && !allowed.includes(match[1])) found.push(match[1])
568
+ if (match && !allowed.includes(match[1]) && !retired.includes(match[1])) {
569
+ found.push(match[1])
570
+ }
496
571
  }
497
572
 
498
573
  return found
499
574
  }
500
575
 
576
+ const HEADING_LEVEL = /^(#{1,6})\s+(.+?)\s*$/
577
+ const LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+/
578
+ const INDENTED = /^\s+\S/
579
+ const FOLDER_TITLE = /^`[^`]+`$/
580
+
581
+ interface FolderHeading {
582
+ readonly line: number
583
+ readonly text: string
584
+ bullets: number
585
+ paragraphs: number
586
+ }
587
+
588
+ /**
589
+ * Finds where the entry breaks the section shape.
590
+ *
591
+ * Bullets are counted at any indent and across any paragraph, so a list split
592
+ * by a bold label or nested one level down still counts against the cap it
593
+ * would otherwise slip under. A line continuing a list item, lazily or
594
+ * indented after a blank, belongs to the item rather than opening a paragraph,
595
+ * which is what keeps a wrapped bullet from reading as prose.
596
+ *
597
+ * A cap reports once per section or folder heading however far past it the
598
+ * count runs, since the fix is one cut either way. `Other folders` is exempt
599
+ * from both folder caps, its one-line bullets being held by the bullet run
600
+ * limit `canon markdown audit` already gates. The development entry's `Scripts`
601
+ * H3 is exempt from the heading rule and both caps the same way, its table
602
+ * being a run-command surface rather than a folder.
603
+ */
604
+ function shapeIssues(
605
+ rel: string,
606
+ lines: readonly BodyLine[],
607
+ retired: readonly string[],
608
+ ): ShapeIssue[] {
609
+ const issues: ShapeIssue[] = []
610
+ const exempt = new Set([OTHER_FOLDERS])
611
+ if (isDevelopmentEntry(rel)) exempt.add(SCRIPTS_HEADING)
612
+ let section: string | undefined
613
+ let gotchas: { line: number; bullets: number } | undefined
614
+ let folder: FolderHeading | undefined
615
+ let inItem = false
616
+ let inParagraph = false
617
+ let afterBlank = false
618
+
619
+ const closeFolder = (): void => {
620
+ if (folder && !exempt.has(folder.text)) {
621
+ if (folder.paragraphs > LAYOUT_PARAGRAPH_CAP) {
622
+ issues.push({
623
+ line: folder.line,
624
+ kind: 'layout-paragraphs',
625
+ text: folder.text,
626
+ count: folder.paragraphs,
627
+ })
628
+ }
629
+ if (folder.bullets > LAYOUT_BULLET_CAP) {
630
+ issues.push({
631
+ line: folder.line,
632
+ kind: 'layout-bullets',
633
+ text: folder.text,
634
+ count: folder.bullets,
635
+ })
636
+ }
637
+ }
638
+ folder = undefined
639
+ }
640
+
641
+ const closeGotchas = (): void => {
642
+ if (gotchas && gotchas.bullets > GOTCHA_CAP) {
643
+ issues.push({
644
+ line: gotchas.line,
645
+ kind: 'gotcha-count',
646
+ text: 'Gotchas',
647
+ count: gotchas.bullets,
648
+ })
649
+ }
650
+ gotchas = undefined
651
+ }
652
+
653
+ for (const line of lines) {
654
+ if (line.fenced) continue
655
+
656
+ const heading = line.text.match(HEADING_LEVEL)
657
+ if (heading) {
658
+ const level = heading[1].length
659
+ const text = heading[2]
660
+ inItem = false
661
+ inParagraph = false
662
+ afterBlank = false
663
+
664
+ if (level <= 2) {
665
+ closeFolder()
666
+ closeGotchas()
667
+ section = level === 2 ? text : undefined
668
+ if (level === 2 && retired.includes(text)) {
669
+ issues.push({ line: line.number, kind: 'retired-section', text })
670
+ }
671
+ if (section === 'Gotchas') gotchas = { line: line.number, bullets: 0 }
672
+ continue
673
+ }
674
+
675
+ if (section === 'Gotchas') {
676
+ issues.push({ line: line.number, kind: 'gotcha-heading', text })
677
+ } else if (section === 'Layout' && level === 3) {
678
+ closeFolder()
679
+ folder = { line: line.number, text, bullets: 0, paragraphs: 0 }
680
+ if (!FOLDER_TITLE.test(text) && !exempt.has(text)) {
681
+ issues.push({ line: line.number, kind: 'layout-heading', text })
682
+ }
683
+ } else if (section === 'Layout') {
684
+ issues.push({ line: line.number, kind: 'layout-depth', text })
685
+ }
686
+ continue
687
+ }
688
+
689
+ if (line.text.trim() === '') {
690
+ inParagraph = false
691
+ afterBlank = true
692
+ continue
693
+ }
694
+
695
+ if (LIST_ITEM.test(line.text)) {
696
+ inItem = true
697
+ inParagraph = false
698
+ afterBlank = false
699
+ if (gotchas) gotchas.bullets++
700
+ if (folder) folder.bullets++
701
+ continue
702
+ }
703
+
704
+ const continues = inItem && (!afterBlank || INDENTED.test(line.text))
705
+ afterBlank = false
706
+ if (continues) continue
707
+
708
+ inItem = false
709
+ if (inParagraph) continue
710
+ inParagraph = true
711
+
712
+ if (gotchas) {
713
+ issues.push({
714
+ line: line.number,
715
+ kind: 'gotcha-paragraph',
716
+ text: line.text.trim(),
717
+ })
718
+ }
719
+ if (folder) folder.paragraphs++
720
+ }
721
+
722
+ closeFolder()
723
+ closeGotchas()
724
+
725
+ return issues.sort((a, b) => a.line - b.line)
726
+ }
727
+
501
728
  /**
502
729
  * Finds the references naming a sibling entry by bare filename.
503
730
  *
@@ -557,6 +784,9 @@ export function measureEntry(
557
784
  siblings: readonly string[] = [],
558
785
  required: readonly string[] = governsContent ? REQUIRED_SECTIONS : [],
559
786
  allowed: readonly string[] = [],
787
+ retired: readonly string[] = governsContent
788
+ ? RETIRED_SECTIONS_BY_FOLDER.context
789
+ : [],
560
790
  ): EntryReport {
561
791
  const lines = bodyLines(source)
562
792
 
@@ -567,7 +797,8 @@ export function measureEntry(
567
797
  provenance: governsContent ? provenance(lines) : [],
568
798
  bareReferences: bareReferences(lines, siblings),
569
799
  sections: declaredSections(lines, required),
570
- otherSections: otherSections(lines, allowed),
800
+ otherSections: otherSections(lines, allowed, retired),
801
+ shape: governsContent ? shapeIssues(rel, lines, retired) : [],
571
802
  stub: isStubSeed(source),
572
803
  governed: governsContent,
573
804
  }
@@ -648,6 +879,7 @@ export async function measureFolders(
648
879
  names.filter((name) => name !== self),
649
880
  requiredSections(folder),
650
881
  allowedSections(folder),
882
+ retiredSections(folder),
651
883
  ),
652
884
  )
653
885
  }
@@ -675,6 +907,13 @@ export function allowedSections(folder: AuditedFolder): readonly string[] {
675
907
  : []
676
908
  }
677
909
 
910
+ /** Names the H2 sections the folder's standard retired, empty where it retired none. */
911
+ export function retiredSections(folder: AuditedFolder): readonly string[] {
912
+ return Object.hasOwn(RETIRED_SECTIONS_BY_FOLDER, folder.name)
913
+ ? RETIRED_SECTIONS_BY_FOLDER[folder.name]
914
+ : []
915
+ }
916
+
678
917
  /**
679
918
  * Reports whether a bare sibling name here is a reference by construction.
680
919
  *
@@ -771,3 +1010,33 @@ export function extraSections(
771
1010
  .filter((entry) => !entry.stub && entry.otherSections.length > 0)
772
1011
  .map((entry) => ({ rel: entry.rel, headings: entry.otherSections }))
773
1012
  }
1013
+
1014
+ export interface ShapeFinding {
1015
+ /** Repo-relative path of the entry breaking the shape. */
1016
+ readonly rel: string
1017
+ /** Whether the entry sits where the shape gates under `--gate`. */
1018
+ readonly gated: boolean
1019
+ /** Every break in the entry, never empty. */
1020
+ readonly issues: readonly ShapeIssue[]
1021
+ }
1022
+
1023
+ /**
1024
+ * Names each entry breaking the section shape, and whether it gates.
1025
+ *
1026
+ * Where it gates is the caller's predicate rather than a list read here, since
1027
+ * the folders rebuilt to the shape are named by path from the repository root
1028
+ * and `measureFolders` holds only paths from the audit root. A stub seed is
1029
+ * dropped for the reason `extraSections` drops one.
1030
+ */
1031
+ export function shapeFindings(
1032
+ entries: readonly EntryReport[],
1033
+ isGated: (rel: string) => boolean,
1034
+ ): ShapeFinding[] {
1035
+ return entries
1036
+ .filter((entry) => !entry.stub && entry.shape.length > 0)
1037
+ .map((entry) => ({
1038
+ rel: entry.rel,
1039
+ gated: isGated(entry.rel),
1040
+ issues: entry.shape,
1041
+ }))
1042
+ }
@@ -5,7 +5,7 @@
5
5
  * mode) and `sweep-v1.md` (sweep mode). The diff prompt is verbatim. The sweep
6
6
  * prompt was tuned past `sweep-v1.md` with one decision check, measured
7
7
  * against the groundwork's labelled set in
8
- * `canon/context/context-model/overview.md`.
8
+ * `canon/context/context-model.md`.
9
9
  *
10
10
  * Each prompt already states its own rules per canonical doc type inside one
11
11
  * string, which is the shape the spike measured, so there is one prompt per
@@ -56,7 +56,7 @@ export interface AuditedFolder {
56
56
  * The requested folder name this was resolved under, which is what says
57
57
  * which standard governs the entries. A nested split folder carries the name
58
58
  * of the folder it sits beneath rather than its own, so
59
- * `canon/context/claude-plugin` is governed as `context`.
59
+ * `canon/context/<domain>` is governed as `context`.
60
60
  */
61
61
  readonly name: string
62
62
  /**
@@ -1,4 +1,8 @@
1
- import type { ExtraSectionFinding, SectionFinding } from '@/context/audit'
1
+ import type {
2
+ ExtraSectionFinding,
3
+ SectionFinding,
4
+ ShapeFinding,
5
+ } from '@/context/audit'
2
6
  import type { FolderDrift } from '@/context/index-drift'
3
7
 
4
8
  export interface GateInput {
@@ -15,6 +19,11 @@ export interface GateInput {
15
19
  readonly sections: readonly SectionFinding[]
16
20
  /** H2 headings outside a folder's closed set, gating under `--gate` alone. */
17
21
  readonly extraSections: readonly ExtraSectionFinding[]
22
+ /**
23
+ * Section shape breaks, gating under `--gate` only where the finding is
24
+ * marked gated, so an unrebuilt entry reports and never fails a push.
25
+ */
26
+ readonly shape: readonly ShapeFinding[]
18
27
  readonly drift: readonly FolderDrift[]
19
28
  /**
20
29
  * Whether the caller asked for the widened gate. False leaves a missing
@@ -36,17 +45,19 @@ export function hasDrift(drift: readonly FolderDrift[]): boolean {
36
45
  *
37
46
  * An unresolved citation is a broken pointer and gates unconditionally, and so
38
47
  * does a requirements record past its own word cap: the record states its rule
39
- * for itself, which makes it a fact rather than a threshold a reader weighs. The findings `--gate` adds are
40
- * the ones answerable from the file itself: a required section it does not
41
- * declare, an H2 outside the closed set, and an index disagreeing with its folder. Entry length, depth, bullet,
42
- * table, provenance, and the record's claim coverage are judgments, so they
43
- * stay out under both modes.
48
+ * for itself, which makes it a fact rather than a threshold a reader weighs.
49
+ * The findings `--gate` adds are the ones answerable from the file itself: a
50
+ * required section it does not declare, an H2 outside the closed set, a shape
51
+ * break in a folder rebuilt to the shape, and an index disagreeing with its
52
+ * folder. Entry length, depth, bullet, table, provenance, and the record's
53
+ * claim coverage are judgments, so they stay out under both modes.
44
54
  */
45
55
  export function isGating({
46
56
  unresolvedCitations,
47
57
  requirementsOverWords,
48
58
  sections,
49
59
  extraSections,
60
+ shape,
50
61
  drift,
51
62
  widened,
52
63
  }: GateInput): boolean {
@@ -54,5 +65,10 @@ export function isGating({
54
65
  if (requirementsOverWords) return true
55
66
  if (!widened) return false
56
67
 
57
- return sections.length > 0 || extraSections.length > 0 || hasDrift(drift)
68
+ return (
69
+ sections.length > 0 ||
70
+ extraSections.length > 0 ||
71
+ shape.some((finding) => finding.gated) ||
72
+ hasDrift(drift)
73
+ )
58
74
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Path prefixes, from the repository root, whose context entries gate on the
3
+ * section shape under `--gate`.
4
+ *
5
+ * Every other entry still reports its shape findings and never fails a push,
6
+ * since the entries are rebuilt to the shape a batch at a time and an unrebuilt
7
+ * one carries `## Decisions` by construction. A batch rebuilding a folder adds
8
+ * its prefix here in the same change, which is what holds the folder to the
9
+ * shape from then on.
10
+ *
11
+ * Read against the repository root rather than the audit root, so a seed tree
12
+ * audited at its own root is listed by its path in the toolkit, such as
13
+ * `tooling/base/seeds/`, and never collides with the entry a target installs
14
+ * from it.
15
+ */
16
+ export const SHAPE_GATED_FOLDERS: readonly string[] = [
17
+ 'canon/context/context-model.md',
18
+ 'canon/context/development/',
19
+ 'canon/context/features/',
20
+ 'canon/context/surfaces/',
21
+ 'canon/context/workflow/',
22
+ ]
23
+
24
+ /** Whether a repository-relative entry path sits under a gated prefix. */
25
+ export function isShapeGated(
26
+ rel: string,
27
+ prefixes: readonly string[] = SHAPE_GATED_FOLDERS,
28
+ ): boolean {
29
+ return prefixes.some((prefix) => rel.startsWith(prefix))
30
+ }
@@ -80,7 +80,7 @@ function sentencesOf(line: string): string[] {
80
80
  * the 290 showed removing all of them while keeping that one.
81
81
  *
82
82
  * `authors` joined the list on a corpus measurement rather than on the shape
83
- * argument above. `canon/context/development/regeneration.md` states `the
83
+ * argument above. A context entry asserting `the
84
84
  * toolkit authors 69 rules under governance/rules/`, which is the assertion
85
85
  * shape this list was built for with a verb the list did not carry, and the
86
86
  * word costs one further sentence across the whole corpus and no finding.
@@ -250,7 +250,7 @@ const TEACH_CHROME: Component = {
250
250
  'navigation shared by every teach page. Recovered from two gitignored',
251
251
  'course.css files that predate a regression that dropped this layer from the',
252
252
  "generator, and rewritten onto this module's tokens rather than the",
253
- 'incompatible palette they carried. See canon/context/features/teach/chrome.md',
253
+ 'incompatible palette they carried. See canon/context/features/teach.md',
254
254
  'for the shape.',
255
255
  ].join('\n '),
256
256
  reads: [
@@ -95,7 +95,7 @@ function borderRows(tokens: readonly BorderToken[]): string[][] {
95
95
  */
96
96
  const LEAD = [
97
97
  'Authoring guidance: `claude/skills/canvas-board/references/design.md`. Rendered from `src/design/tokens.ts` by `canon design regen`, so edit the module and never this file.',
98
- 'Why each value was chosen lives in `canon/context/design/tokens.md`.',
98
+ 'Why each value was chosen lives in `canon/context/features/design.md`.',
99
99
  ].join(' ')
100
100
 
101
101
  function bullets(rules: readonly string[]): string {
@@ -59,7 +59,7 @@ export interface BorderToken {
59
59
  /**
60
60
  * Every prose slot is a list of one-line rules rather than a string, so a
61
61
  * bullet is the unit the renderer emits and the prose budget counts, and a
62
- * value needing a paragraph of why goes to `canon/context/design/tokens.md`.
62
+ * value needing a paragraph of why goes to `canon/context/features/design.md`.
63
63
  */
64
64
  export interface DesignTokens {
65
65
  readonly personality: string
@@ -211,7 +211,7 @@ function collectCommands(value: unknown, into: string[] = []): string[] {
211
211
  /**
212
212
  * A hook, a workflow, or a husky script reaching one side of the seed or config
213
213
  * boundary and not the other is a capability withheld with no recorded reason,
214
- * per the criterion in `canon/context/tooling/seeds.md`.
214
+ * per the criterion in `canon/context/surfaces/tooling.md`.
215
215
  *
216
216
  * Presence on either side clears a name, and a `canon-no-seed:` comment in the
217
217
  * file is the recorded reason. The same marker on a destination clears a
@@ -434,6 +434,9 @@ function grepLines(
434
434
  * seed itself installs, so every other spelling reports. No seed root, or roots
435
435
  * carrying no markdown, is unmeasured rather than a pass, which agrees with
436
436
  * `seedStandards` on the no-root case.
437
+ *
438
+ * The token matches as a substring, so `canonical` reports as `canon` does. The
439
+ * walk covers the seed roots alone, so a shipped rule citing the CLI passes.
437
440
  */
438
441
  export const seedIndependence: Measure = async (ctx) => {
439
442
  if (!isDirectory(join(ctx.root, 'tooling'))) {