@nurix/etna 0.20.35 → 0.20.37

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 (233) hide show
  1. package/data/catalog/rules/changelog.md +1 -1
  2. package/data/catalog/skills/nustack/SKILL.md +2 -2
  3. package/data/catalog/skills/nustack/references/cli-design.md +35 -35
  4. package/data/catalog/skills/nustack/references/monorepo-master-list.md +6 -6
  5. package/data/catalog/skills/nustack/references/monorepo-setup.md +13 -13
  6. package/data/catalog/skills/nustack/references/publishing.md +40 -39
  7. package/data/harness-index.json +1 -1
  8. package/data/harnesses/code/CLAUDE.md +4 -4
  9. package/data/harnesses/code/agents/brainstorm.md +2 -2
  10. package/data/harnesses/code/agents/lld-mode.md +2 -2
  11. package/data/harnesses/code/agents/maintenance.md +17 -17
  12. package/data/harnesses/code/codex/agents/brainstorm.toml +6 -2
  13. package/data/harnesses/code/codex/agents/lld-mode.toml +2 -2
  14. package/data/harnesses/code/codex/agents/maintenance.toml +18 -17
  15. package/data/harnesses/code/docs/README.md +9 -17
  16. package/data/harnesses/code/docs/history/CHANGELOG.md +19 -18
  17. package/data/harnesses/code/docs/structure.md +60 -45
  18. package/data/harnesses/code/harness.json +2 -2
  19. package/data/harnesses/code/hooks/compliance.mjs +8 -2
  20. package/data/harnesses/code/hooks/lib/sweep.mjs +3 -2
  21. package/data/harnesses/code/rules/changelog.md +1 -1
  22. package/data/harnesses/code/skills/boundary/SKILL.md +33 -22
  23. package/data/harnesses/code/skills/boundary/references/index.md +2 -4
  24. package/data/harnesses/code/skills/boundary/references/mechanisms.md +3 -10
  25. package/data/harnesses/code/skills/boundary/references/packs/frontend.md +17 -17
  26. package/data/harnesses/code/skills/boundary/references/packs/generic-tail.md +1 -1
  27. package/data/harnesses/code/skills/brainstorm/README.md +11 -13
  28. package/data/harnesses/code/skills/brainstorm/SKILL.md +149 -130
  29. package/data/harnesses/code/skills/brainstorm/references/doctrine.md +48 -51
  30. package/data/harnesses/code/skills/brainstorm/references/harvest.md +51 -57
  31. package/data/harnesses/code/skills/brainstorm/references/session-schema.md +48 -42
  32. package/data/harnesses/code/skills/design/SKILL.md +31 -35
  33. package/data/harnesses/code/skills/design/data/iconography.md +2 -2
  34. package/data/harnesses/code/skills/design/references/compose.md +14 -16
  35. package/data/harnesses/code/skills/design/references/foundations.md +5 -5
  36. package/data/harnesses/code/skills/design/references/intake.md +6 -6
  37. package/data/harnesses/code/skills/design/references/object-pass.md +1 -1
  38. package/data/harnesses/code/skills/design/references/ship-gate.md +5 -5
  39. package/data/harnesses/code/skills/design/references/ux-patterns/Readme.md +4 -4
  40. package/data/harnesses/code/skills/design/references/ux-patterns/ai-layer.md +1 -1
  41. package/data/harnesses/code/skills/design/references/ux-patterns/container-choice.md +1 -1
  42. package/data/harnesses/code/skills/design/references/ux-patterns/dashboards.md +1 -1
  43. package/data/harnesses/code/skills/design/references/ux-patterns/modes.md +1 -1
  44. package/data/harnesses/code/skills/design/references/ux-patterns/object-model.md +2 -2
  45. package/data/harnesses/code/skills/design/references/ux-patterns/promotion-demotion.md +1 -1
  46. package/data/harnesses/code/skills/design/references/ux-patterns/settings.md +1 -1
  47. package/data/harnesses/code/skills/design/references/ux-patterns/standard-pages.md +1 -1
  48. package/data/harnesses/code/skills/documentation/SKILL.md +92 -143
  49. package/data/harnesses/code/skills/documentation/references/examples.md +45 -72
  50. package/data/harnesses/code/skills/documentation/references/frontmatter.md +8 -8
  51. package/data/harnesses/code/skills/documentation/references/instruction-style.md +3 -3
  52. package/data/harnesses/code/skills/documentation/references/source-comments.md +19 -0
  53. package/data/harnesses/code/skills/documentation/references/style-guide.md +25 -11
  54. package/data/harnesses/code/skills/documentation/references/untemplated.md +11 -10
  55. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +181 -40
  56. package/data/harnesses/code/skills/documentation/templates/Readme.md +68 -41
  57. package/data/harnesses/code/skills/documentation/templates/acceptance-criteria.md +11 -19
  58. package/data/harnesses/code/skills/documentation/templates/api-reference.md +8 -3
  59. package/data/harnesses/code/skills/documentation/templates/architecture-decision.md +8 -12
  60. package/data/harnesses/code/skills/documentation/templates/backlog.md +2 -2
  61. package/data/harnesses/code/skills/documentation/templates/behavioral-checklist.md +10 -17
  62. package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +2 -2
  63. package/data/harnesses/code/skills/documentation/templates/changelog-entry.md +2 -2
  64. package/data/harnesses/code/skills/documentation/templates/changelog-index.md +21 -29
  65. package/data/harnesses/code/skills/documentation/templates/docs-readme.md +4 -4
  66. package/data/harnesses/code/skills/documentation/templates/doctrine.md +3 -3
  67. package/data/harnesses/code/skills/documentation/templates/domain-model.md +10 -5
  68. package/data/harnesses/code/skills/documentation/templates/environment-registry.md +7 -2
  69. package/data/harnesses/code/skills/documentation/templates/feature.md +6 -6
  70. package/data/harnesses/code/skills/documentation/templates/glossary.md +6 -1
  71. package/data/harnesses/code/skills/documentation/templates/handover.md +17 -17
  72. package/data/harnesses/code/skills/documentation/templates/implementation-map.md +10 -5
  73. package/data/harnesses/code/skills/documentation/templates/journal.md +2 -2
  74. package/data/harnesses/code/skills/documentation/templates/memory-main.md +2 -2
  75. package/data/harnesses/code/skills/documentation/templates/migration-runbook.md +1 -1
  76. package/data/harnesses/code/skills/documentation/templates/non-goals.md +8 -3
  77. package/data/harnesses/code/skills/documentation/templates/operations-procedure.md +8 -3
  78. package/data/harnesses/code/skills/documentation/templates/package.md +5 -9
  79. package/data/harnesses/code/skills/documentation/templates/plan.md +5 -11
  80. package/data/harnesses/code/skills/documentation/templates/protected-files.md +7 -2
  81. package/data/harnesses/code/skills/documentation/templates/root-readme.md +5 -5
  82. package/data/harnesses/code/skills/documentation/templates/security-overview.md +9 -4
  83. package/data/harnesses/code/skills/documentation/templates/setup.md +6 -6
  84. package/data/harnesses/code/skills/documentation/templates/system-overview.md +12 -7
  85. package/data/harnesses/code/skills/documentation/templates/system.md +2 -2
  86. package/data/harnesses/code/skills/documentation/templates/testing-strategy.md +10 -5
  87. package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +14 -5
  88. package/data/harnesses/code/skills/issue-bridge/SKILL.md +51 -16
  89. package/data/harnesses/code/skills/issue-bridge/references/trackers/github.md +5 -5
  90. package/data/harnesses/code/skills/issue-bridge/references/trackers/jira.md +11 -8
  91. package/data/harnesses/code/skills/issue-bridge/references/trackers/linear.md +16 -11
  92. package/data/harnesses/code/skills/nustack-graph/SKILL.md +38 -29
  93. package/data/harnesses/code/skills/polygraph/references/dispatch.md +31 -18
  94. package/data/harnesses/code/skills/polygraph/references/rows.md +11 -6
  95. package/data/harnesses/code/skills/schema-design/SKILL.md +39 -41
  96. package/data/harnesses/code/skills/test/SKILL.md +38 -42
  97. package/data/harnesses/code/skills/testing/SKILL.md +83 -73
  98. package/data/harnesses/code/skills/workflow-mapper/SKILL.md +156 -193
  99. package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +15 -15
  100. package/data/harnesses/delivery/CLAUDE.md +1 -1
  101. package/data/harnesses/delivery/agents/brainstorm.md +2 -2
  102. package/data/harnesses/delivery/agents/lld-mode.md +2 -2
  103. package/data/harnesses/delivery/agents/maintenance.md +17 -17
  104. package/data/harnesses/delivery/codex/agents/brainstorm.toml +6 -2
  105. package/data/harnesses/delivery/codex/agents/lld-mode.toml +2 -2
  106. package/data/harnesses/delivery/codex/agents/maintenance.toml +18 -17
  107. package/data/harnesses/delivery/harness.json +2 -2
  108. package/data/harnesses/delivery/rules/changelog.md +1 -1
  109. package/data/harnesses/delivery/skills/brainstorm/README.md +11 -13
  110. package/data/harnesses/delivery/skills/brainstorm/SKILL.md +149 -130
  111. package/data/harnesses/delivery/skills/brainstorm/references/doctrine.md +48 -51
  112. package/data/harnesses/delivery/skills/brainstorm/references/harvest.md +51 -57
  113. package/data/harnesses/delivery/skills/brainstorm/references/session-schema.md +48 -42
  114. package/data/harnesses/delivery/skills/cost-report/SKILL.md +10 -10
  115. package/data/harnesses/delivery/skills/documentation/SKILL.md +92 -143
  116. package/data/harnesses/delivery/skills/documentation/references/examples.md +45 -72
  117. package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +8 -8
  118. package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +3 -3
  119. package/data/harnesses/delivery/skills/documentation/references/source-comments.md +19 -0
  120. package/data/harnesses/delivery/skills/documentation/references/style-guide.md +25 -11
  121. package/data/harnesses/delivery/skills/documentation/references/untemplated.md +11 -10
  122. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +181 -40
  123. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +68 -41
  124. package/data/harnesses/delivery/skills/documentation/templates/acceptance-criteria.md +11 -19
  125. package/data/harnesses/delivery/skills/documentation/templates/api-reference.md +8 -3
  126. package/data/harnesses/delivery/skills/documentation/templates/architecture-decision.md +8 -12
  127. package/data/harnesses/delivery/skills/documentation/templates/backlog.md +2 -2
  128. package/data/harnesses/delivery/skills/documentation/templates/behavioral-checklist.md +10 -17
  129. package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +2 -2
  130. package/data/harnesses/delivery/skills/documentation/templates/changelog-entry.md +2 -2
  131. package/data/harnesses/delivery/skills/documentation/templates/changelog-index.md +21 -29
  132. package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +4 -4
  133. package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +3 -3
  134. package/data/harnesses/delivery/skills/documentation/templates/domain-model.md +10 -5
  135. package/data/harnesses/delivery/skills/documentation/templates/environment-registry.md +7 -2
  136. package/data/harnesses/delivery/skills/documentation/templates/feature.md +6 -6
  137. package/data/harnesses/delivery/skills/documentation/templates/glossary.md +6 -1
  138. package/data/harnesses/delivery/skills/documentation/templates/handover.md +17 -17
  139. package/data/harnesses/delivery/skills/documentation/templates/implementation-map.md +10 -5
  140. package/data/harnesses/delivery/skills/documentation/templates/journal.md +2 -2
  141. package/data/harnesses/delivery/skills/documentation/templates/memory-main.md +2 -2
  142. package/data/harnesses/delivery/skills/documentation/templates/migration-runbook.md +1 -1
  143. package/data/harnesses/delivery/skills/documentation/templates/non-goals.md +8 -3
  144. package/data/harnesses/delivery/skills/documentation/templates/operations-procedure.md +8 -3
  145. package/data/harnesses/delivery/skills/documentation/templates/package.md +5 -9
  146. package/data/harnesses/delivery/skills/documentation/templates/plan.md +5 -11
  147. package/data/harnesses/delivery/skills/documentation/templates/protected-files.md +7 -2
  148. package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +5 -5
  149. package/data/harnesses/delivery/skills/documentation/templates/security-overview.md +9 -4
  150. package/data/harnesses/delivery/skills/documentation/templates/setup.md +6 -6
  151. package/data/harnesses/delivery/skills/documentation/templates/system-overview.md +12 -7
  152. package/data/harnesses/delivery/skills/documentation/templates/system.md +2 -2
  153. package/data/harnesses/delivery/skills/documentation/templates/testing-strategy.md +10 -5
  154. package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +14 -5
  155. package/data/harnesses/delivery/skills/escalate/SKILL.md +11 -11
  156. package/data/harnesses/delivery/skills/prompt-eval/SKILL.md +8 -8
  157. package/data/harnesses/delivery/skills/prompt-library/SKILL.md +30 -9
  158. package/data/harnesses/delivery/skills/route/SKILL.md +8 -8
  159. package/data/harnesses/etna/CLAUDE.md +2 -2
  160. package/data/harnesses/etna/agents/brainstorm.md +2 -2
  161. package/data/harnesses/etna/agents/lld-mode.md +2 -2
  162. package/data/harnesses/etna/agents/maintenance.md +17 -17
  163. package/data/harnesses/etna/codex/agents/brainstorm.toml +6 -2
  164. package/data/harnesses/etna/codex/agents/lld-mode.toml +2 -2
  165. package/data/harnesses/etna/codex/agents/maintenance.toml +18 -17
  166. package/data/harnesses/etna/harness.json +2 -2
  167. package/data/harnesses/etna/rules/changelog.md +1 -1
  168. package/data/harnesses/etna/skills/boundary/SKILL.md +33 -22
  169. package/data/harnesses/etna/skills/boundary/references/index.md +2 -4
  170. package/data/harnesses/etna/skills/boundary/references/mechanisms.md +3 -10
  171. package/data/harnesses/etna/skills/boundary/references/packs/frontend.md +17 -17
  172. package/data/harnesses/etna/skills/boundary/references/packs/generic-tail.md +1 -1
  173. package/data/harnesses/etna/skills/brainstorm/README.md +11 -13
  174. package/data/harnesses/etna/skills/brainstorm/SKILL.md +149 -130
  175. package/data/harnesses/etna/skills/brainstorm/references/doctrine.md +48 -51
  176. package/data/harnesses/etna/skills/brainstorm/references/harvest.md +51 -57
  177. package/data/harnesses/etna/skills/brainstorm/references/session-schema.md +48 -42
  178. package/data/harnesses/etna/skills/documentation/SKILL.md +92 -143
  179. package/data/harnesses/etna/skills/documentation/references/examples.md +45 -72
  180. package/data/harnesses/etna/skills/documentation/references/frontmatter.md +8 -8
  181. package/data/harnesses/etna/skills/documentation/references/instruction-style.md +3 -3
  182. package/data/harnesses/etna/skills/documentation/references/source-comments.md +19 -0
  183. package/data/harnesses/etna/skills/documentation/references/style-guide.md +25 -11
  184. package/data/harnesses/etna/skills/documentation/references/untemplated.md +11 -10
  185. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +181 -40
  186. package/data/harnesses/etna/skills/documentation/templates/Readme.md +68 -41
  187. package/data/harnesses/etna/skills/documentation/templates/acceptance-criteria.md +11 -19
  188. package/data/harnesses/etna/skills/documentation/templates/api-reference.md +8 -3
  189. package/data/harnesses/etna/skills/documentation/templates/architecture-decision.md +8 -12
  190. package/data/harnesses/etna/skills/documentation/templates/backlog.md +2 -2
  191. package/data/harnesses/etna/skills/documentation/templates/behavioral-checklist.md +10 -17
  192. package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +2 -2
  193. package/data/harnesses/etna/skills/documentation/templates/changelog-entry.md +2 -2
  194. package/data/harnesses/etna/skills/documentation/templates/changelog-index.md +21 -29
  195. package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +4 -4
  196. package/data/harnesses/etna/skills/documentation/templates/doctrine.md +3 -3
  197. package/data/harnesses/etna/skills/documentation/templates/domain-model.md +10 -5
  198. package/data/harnesses/etna/skills/documentation/templates/environment-registry.md +7 -2
  199. package/data/harnesses/etna/skills/documentation/templates/feature.md +6 -6
  200. package/data/harnesses/etna/skills/documentation/templates/glossary.md +6 -1
  201. package/data/harnesses/etna/skills/documentation/templates/handover.md +17 -17
  202. package/data/harnesses/etna/skills/documentation/templates/implementation-map.md +10 -5
  203. package/data/harnesses/etna/skills/documentation/templates/journal.md +2 -2
  204. package/data/harnesses/etna/skills/documentation/templates/memory-main.md +2 -2
  205. package/data/harnesses/etna/skills/documentation/templates/migration-runbook.md +1 -1
  206. package/data/harnesses/etna/skills/documentation/templates/non-goals.md +8 -3
  207. package/data/harnesses/etna/skills/documentation/templates/operations-procedure.md +8 -3
  208. package/data/harnesses/etna/skills/documentation/templates/package.md +5 -9
  209. package/data/harnesses/etna/skills/documentation/templates/plan.md +5 -11
  210. package/data/harnesses/etna/skills/documentation/templates/protected-files.md +7 -2
  211. package/data/harnesses/etna/skills/documentation/templates/root-readme.md +5 -5
  212. package/data/harnesses/etna/skills/documentation/templates/security-overview.md +9 -4
  213. package/data/harnesses/etna/skills/documentation/templates/setup.md +6 -6
  214. package/data/harnesses/etna/skills/documentation/templates/system-overview.md +12 -7
  215. package/data/harnesses/etna/skills/documentation/templates/system.md +2 -2
  216. package/data/harnesses/etna/skills/documentation/templates/testing-strategy.md +10 -5
  217. package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +14 -5
  218. package/data/harnesses/etna/skills/workflow-mapper/SKILL.md +156 -193
  219. package/data/harnesses/etna/skills/workflow-mapper/references/patterns.md +15 -15
  220. package/data/harnesses/hr/harness.json +1 -1
  221. package/data/harnesses/hr/skills/evaluator/SKILL.md +71 -73
  222. package/data/harnesses/hr/skills/evaluator/criteria/code-quality.md +28 -47
  223. package/data/harnesses/hr/skills/evaluator/criteria/deployment-readiness.md +33 -43
  224. package/data/harnesses/hr/skills/evaluator/criteria/maintainability.md +34 -46
  225. package/data/harnesses/hr/skills/evaluator/criteria/mechanical-ambition.md +13 -13
  226. package/data/harnesses/hr/skills/evaluator/criteria/problem-substantiveness.md +7 -7
  227. package/data/harnesses/hr/skills/evaluator/criteria/testing.md +36 -41
  228. package/data/harnesses/hr/skills/evaluator/criteria/ux-mechanics.md +25 -11
  229. package/data/harnesses/hr/skills/evaluator/criteria/value-articulation.md +7 -7
  230. package/data/harnesses/hr/skills/evaluator/preflight.md +27 -46
  231. package/data/harnesses/hr/skills/interview-eval/SKILL.md +37 -42
  232. package/data/harnesses/hr/skills/interview-eval/rubric.md +21 -23
  233. package/package.json +2 -2
@@ -1,49 +1,147 @@
1
- // skills/documentation/scripts/doc-lint.mjs - mechanical checks over markdown docs (tense narration, dates, stubs, redirects)
1
+ // skills/documentation/scripts/doc-lint.mjs - mechanical checks over markdown docs (tense and history narration, dates, redirect stubs, frontmatter, index shape, owning-document body)
2
2
  // Usage: node doc-lint.mjs <file.md ...> | node doc-lint.mjs --changed (git-changed + untracked .md)
3
- // Exit 1 when any block-severity finding survives. Escape hatch: <!-- doc-lint-disable: id[, id] --> (file-wide, or on the offending line).
3
+ // Exit 1 when any block-severity finding survives. Escape hatch: <!-- doc-lint-disable: id[, id] --> alone on its
4
+ // own line (file-wide, with a reason comment beside it) or at the end of the offending line (that line only).
5
+ // Genre facts (nature, type, location, required keys and sections) come from ../templates/Readme.md § Registry.
4
6
 
5
7
  import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
6
8
  import { execSync } from 'node:child_process';
9
+ import { dirname, join } from 'node:path';
10
+
11
+ const MONTH = '(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)';
12
+ const DATE = new RegExp(`\\b20\\d{2}[-/.]\\d{1,2}[-/.]\\d{1,2}\\b|\\b\\d{1,2}/\\d{1,2}/20\\d{2}\\b|\\b${MONTH}\\.? (\\d{1,2},? )?20\\d{2}\\b|\\b\\d{1,2} ${MONTH}\\.? 20\\d{2}\\b`);
7
13
 
8
14
  const RULES = [
9
15
  { id: 'stale-tripwire', severity: 'block', scope: 'truth', pattern: /treat (any )?[\w`./-]+ ?(references? )?as stale|superseded (by|the)|\bused to (be|target|live|hold)\b/i, message: 'stale-reference tripwire / supersession talk — fix the references, state the present fact' },
10
- { id: 'history-narration', severity: 'block', scope: 'truth', pattern: /\bno longer\b|\bwas (renamed|removed|extracted|folded|migrated|replaced)\b|\bmoved (to|from|into) [\w`./-]+ \(?20\d{2}/i, message: 'history narration — the journey belongs in a dated record' },
16
+ { id: 'history-narration', severity: 'block', scope: 'truth', pattern: /\b(was|were|has been|have been) (renamed|extracted|folded|migrated|replaced|superseded)\b|\bmoved (to|from|into) [\w`./-]+ \(?20\d{2}/i, message: 'history narration — the journey belongs in a dated record' },
17
+ // A ticked box in a committed checklist or procedure records one run's state inside a document about the present.
18
+ { id: 'ticked-checkbox', severity: 'warn', scope: 'truth', only: /(^|\/)docs\/(tests|operations|acceptance)\//, pattern: /^\s*[-*] \[[xX]\]/, message: 'ticked box in a truth document — a run\'s result belongs to the changelog entry of the unit that ran it; commit the box unticked' },
11
19
  { id: 'progress-marker', severity: 'block', scope: 'truth', pattern: /\bphases?\s+\d[^.\n]{0,40}\b(complete|done)\b|\b(build|implementation) (is )?(\d+%|partially) (complete|done)\b/i, message: 'progress marker — progress lives in the plan/backlog record' },
12
20
  { id: 'temporal-hedge', severity: 'warn', scope: 'truth', pattern: /\bnot yet\b|\bfor now\b|\bcurrently\b|\bstill (unsupported|missing|pending|unimplemented|in progress)\b/i, message: 'temporal hedge — state the fact without "currently / not yet / for now"' },
13
- // "previously-fetched" is a hyphenated adjective naming a thing, not a predecessor design — don't fire on it.
14
- { id: 'predecessor-frame', severity: 'warn', scope: 'truth', pattern: /\bpreviously\b(?!-)|\bthe old \w+\b|\blegacy \w+ (was|is)\b/i, message: 'predecessor framing — describe the one current design' },
21
+ // "previously-fetched" is a hyphenated adjective naming a thing, not a predecessor design — don't fire on it. "No longer"
22
+ // and "was removed" also describe a state a person meets ("a removed member no longer sees the panel"), so they warn.
23
+ { id: 'predecessor-frame', severity: 'warn', scope: 'truth', pattern: /\bpreviously\b(?!-)|\bthe old \w+\b|\blegacy \w+ (was|is)\b|\bno longer\b|\bwas removed\b|\bv\d+(\.\d+)*\s*(→|->)\s*v?\d+/i, message: 'predecessor framing — describe the one current design' },
15
24
  // docs/tests/ behavioral checklists use `- [ ]` as test-step notation (step → **Assert**) and docs/operations/
16
25
  // procedures use it for operator verification steps — neither is progress tracking. Every other truth doc still bans checkboxes.
17
- { id: 'status-glyph', severity: 'warn', scope: 'truth', exempt: /(^|\/)docs\/(tests|operations)\//, pattern: /(^|\s)(✅|⏳|- \[[ x]\])/u, message: 'status glyph / checklist in a truth doc — tracking belongs in a record' },
26
+ { id: 'status-glyph', severity: 'warn', scope: 'truth', exempt: /(^|\/)docs\/(tests|operations|acceptance)\//, pattern: /(^|\s)(✅|⏳|- \[[ xX]\](?!\())/u, message: 'status glyph / checklist in a truth doc — tracking belongs in a record' },
18
27
  // Truth docs are supposed to cite records, and record filenames are dated — so a date inside a link
19
- // target or a code span is a path, not narration. Strip those before matching.
20
- { id: 'date-in-truth-doc', severity: 'block', scope: 'truth', strip: /\[[^\]]*\]\([^)]*\)|`[^`]*`|^(last-reviewed|last-updated|rev|harness-version):.*$/gm, pattern: /\b20\d{2}-\d{2}-\d{2}\b/, message: 'date in a truth doc — cite the record by name and link; the closed exceptions are the frontmatter tooling keys, an AD Decision Changelog tail and a dated path inside a link or code span' },
28
+ // target is a path, not narration. Code spans are stripped before every rule.
29
+ { id: 'date-in-truth-doc', severity: 'block', scope: 'truth', strip: /\[[^\]]*\]\([^)]*\)|^(last-reviewed|last-updated|rev|harness-version):.*$/gm, pattern: DATE, message: 'date in a truth doc — cite the record by name and link; the closed exceptions are the frontmatter tooling keys, an AD Decision Changelog tail and a dated path inside a link or code span' },
21
30
  ];
22
31
 
23
- // Sections inside a truth doc where dated history is sanctioned (the two bridges).
24
- const BRIDGE_HEADINGS = /^#{1,4}\s+(decision changelog|health log)\b/i;
25
- // Instruction files, the three harness-installed mirrors and generated trees are out of scope; the authoring
26
- // roots are anchored to the repo root.
27
- const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
32
+ // The one section inside a truth doc where dated history is sanctioned: an AD's Decision Changelog.
33
+ const BRIDGE_HEADINGS = /^#{1,4}\s+decision changelog\b/i;
34
+ // Instruction files, the three harness-installed mirrors, generated trees and the hook-written bookkeeping queue
35
+ // (hooks/lib/state.mjs, a fixed header and a dated row grammar) are out of scope; the authoring roots are anchored
36
+ // to the repo root.
37
+ const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\.|(^|\/)docs\/operations\/bookkeeping\.md$/;
28
38
  // `operations/issues/YYYY-MM-DD-{slug}.md` is a sanctioned record genre (`type: issue`, the
29
39
  // `issue-bridge` skill's local pack) living outside the record folders, so it is named here too —
30
40
  // graded as a truth document it fails the date rule for carrying the date its filename already does.
31
41
  const RECORD = /(^|\/)docs\/(history|research|memory)\/|(^|\/)docs\/operations\/issues\/|(^|\/)docs\/operations\/release-notes\/(?!readme\.md$)[^/]+\.md$/i;
32
42
 
33
- const RECORD_TYPES = /^(changelog-entry|plan|migration|migration-runbook|research|brainstorm|journal|record|ledger|issue|triage|project-memory|agent-memory)$/;
43
+ const RECORD_TYPES = /^(changelog-entry|plan|migration|migration-runbook|research|brainstorm|journal|record|ledger|issue|triage|handover|project-memory|agent-memory)$/;
34
44
  const TRUTH_LOCATIONS = /(^|\/)docs\/(features|architecture|tests|acceptance)\//;
45
+ // Types other skills stamp on documents this registry does not template (product-dna's ledger, generic records).
46
+ const EXTERNAL_TYPES = /^(ledger|record|migration|migration-runbook|changelog-entry|plan|journal)$/;
47
+
48
+ // The genre registry (templates/Readme.md § Registry), one entry per row, or null when the skill's templates are
49
+ // not installed beside this script — every registry check then skips, and scope falls back to the path rules.
50
+ function loadRegistry() {
51
+ let text;
52
+ try { text = readFileSync(new URL('../templates/Readme.md', import.meta.url), 'utf8'); } catch { return null; }
53
+ const at = text.search(/^## Registry\s*$/m);
54
+ if (at === -1) return null;
55
+ const ticks = (cell) => [...cell.matchAll(/`([^`]+)`/g)].map((m) => m[1]);
56
+ const rows = [];
57
+ for (const line of text.slice(at).split('\n').slice(1)) {
58
+ if (/^## /.test(line)) break;
59
+ if (!line.startsWith('|') || /^\|\s*(Genre|-{3})/.test(line)) continue;
60
+ const cells = line.split('|').slice(1, -1).map((c) => c.trim());
61
+ if (cells.length !== 7) continue;
62
+ const [genre, , nature, type, location, keys, sections] = cells;
63
+ const where = ticks(location)[0] || null;
64
+ rows.push({
65
+ genre,
66
+ nature,
67
+ type: ticks(type)[0] || null,
68
+ location: where,
69
+ match: where ? locationPattern(where) : null,
70
+ // Literal characters outside placeholders: the most specific location wins a tie (`{surface}-ux.md` over `{feature}.md`).
71
+ specificity: where ? where.replace(/\{[^}]+\}|YYYY-MM-DD|NNN|X\.Y\.Z/g, '').length : 0,
72
+ keys: ticks(keys),
73
+ sections: sections === '—' ? [] : sections.split('·').map((x) => x.trim()).filter(Boolean),
74
+ });
75
+ }
76
+ return rows.length ? rows : null;
77
+ }
78
+
79
+ // A registry location as a path RegExp: `{name}` is one segment, the date and number placeholders their digits, a trailing slash any file beneath.
80
+ function locationPattern(location) {
81
+ const body = location
82
+ .replace(/[.+?^$()[\]\\|]/g, '\\$&')
83
+ .replace(/\\\{[^}]+\}|\{[^}]+\}/g, '[^/]+')
84
+ .replace(/YYYY-MM-DD/g, '\\d{4}-\\d{2}-\\d{2}')
85
+ .replace(/NNN/g, '\\d{3}')
86
+ .replace(/X\\\.Y\\\.Z/g, '[\\d.]+');
87
+ // A location with no folder (the root README) sits at the repo root only, never at any depth.
88
+ return new RegExp(`${location.includes('/') ? '(^|/)' : '^(\\./)?'}${body}${location.endsWith('/') ? '' : '$'}`, 'i');
89
+ }
90
+
91
+ const REGISTRY = loadRegistry();
92
+
93
+ // The registry row a document belongs to: its declared `type:` first (the location breaking a tie between genres
94
+ // sharing a type), else the most specific location it sits at; null when neither names one.
95
+ function genreOf(path, text) {
96
+ if (!REGISTRY) return null;
97
+ const declared = text === undefined ? null : declaredType(text);
98
+ // A folder's Readme is an index whatever folder it sits in, and only an index location can claim one.
99
+ const isIndex = /(^|\/)readme\.md$/i.test(path);
100
+ const at = REGISTRY.filter((r) => r.match && r.match.test(path) && /(^|\/)readme\.md$/i.test(r.location) === isIndex)
101
+ .sort((a, b) => b.specificity - a.specificity);
102
+ if (declared) {
103
+ const typed = REGISTRY.filter((r) => r.type === declared);
104
+ return typed.find((r) => at.includes(r)) || typed[0] || null;
105
+ }
106
+ return at[0] || null;
107
+ }
35
108
 
36
109
  // The genre a document declares for itself, from its own frontmatter `type:` key, or null.
37
110
  function declaredType(text) {
38
111
  if (!text.startsWith('---')) return null;
39
112
  const end = text.indexOf('\n---', 3);
40
- const m = /^type:\s*(\S+)\s*$/m.exec(end === -1 ? text : text.slice(0, end));
41
- return m ? m[1] : null;
113
+ const m = /^type:\s*(\S+)\s*(#.*)?$/m.exec(end === -1 ? text : text.slice(0, end));
114
+ return m ? m[1].replace(/^["']|["']$/g, '') : null;
115
+ }
116
+
117
+ // Top-level frontmatter keys and their raw values (a block list joined one item per line), or null with no block.
118
+ function frontmatter(text) {
119
+ if (!/^---\r?\n/.test(text)) return null;
120
+ const end = text.indexOf('\n---', 3);
121
+ if (end === -1) return null;
122
+ const keys = new Map();
123
+ let last = null;
124
+ for (const line of text.slice(text.indexOf('\n') + 1, end).split('\n')) {
125
+ const key = /^([A-Za-z][\w-]*):[ \t]*(.*)$/.exec(line);
126
+ if (key) { last = key[1]; keys.set(last, key[2].replace(/\s+#.*$/, '').trim()); continue; }
127
+ const item = /^\s+-\s+(.*)$/.exec(line);
128
+ if (item && last) keys.set(last, [keys.get(last), item[1].replace(/\s+#.*$/, '').trim()].filter(Boolean).join('\n'));
129
+ }
130
+ return keys;
131
+ }
132
+
133
+ // A frontmatter value as a list: a flow list, a block list, or one scalar; quotes dropped, empties removed.
134
+ function listValue(raw) {
135
+ if (!raw) return [];
136
+ const items = raw.startsWith('[') ? raw.replace(/^\[|\]$/g, '').split(',') : raw.split('\n');
137
+ return items.map((v) => v.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
42
138
  }
43
139
 
44
- // Classify a document: record or truth, the declared genre outranking the path.
140
+ // Classify a document: truth, record, queue or out of scope — the registry's nature first, then the path rules.
45
141
  function scopeOf(path, text) {
46
142
  if (SKIP.test(path)) return 'skip';
143
+ const genre = genreOf(path, text);
144
+ if (genre) return genre.nature === 'truth' ? 'truth' : genre.nature === 'queue' ? 'queue' : 'record';
47
145
  if (RECORD.test(path)) return 'record';
48
146
  const declared = text === undefined ? null : declaredType(text);
49
147
  return declared && RECORD_TYPES.test(declared) ? 'record' : 'truth';
@@ -80,14 +178,27 @@ function proseLines(text) {
80
178
  return out;
81
179
  }
82
180
 
83
- // Collect file-wide disabled rule ids from <!-- doc-lint-disable: a, b --> comments.
181
+ // Collect file-wide disabled rule ids: a <!-- doc-lint-disable: a, b --> comment alone on its own line. One that shares
182
+ // a line with prose disables that line only, and is honoured where each rule reads its lines.
84
183
  function fileDisables(text) {
85
184
  const ids = new Set();
86
- for (const m of text.matchAll(/<!--\s*doc-lint-disable:\s*([\w-,\s]+?)\s*-->/g))
185
+ for (const m of text.matchAll(/^\s*<!--\s*doc-lint-disable:\s*([\w-,\s]+?)\s*-->\s*$/gm))
87
186
  m[1].split(',').forEach((id) => ids.add(id.trim()));
88
187
  return ids;
89
188
  }
90
189
 
190
+ // File-wide disables with no reason beside them: no comment on the next non-blank line, the way SKILL.md asks.
191
+ function unexplainedDisables(text) {
192
+ const lines = text.split('\n');
193
+ const out = [];
194
+ lines.forEach((line, i) => {
195
+ if (!/^\s*<!--\s*doc-lint-disable:[^>]*-->\s*$/.test(line)) return;
196
+ const next = lines.slice(i + 1).find((l) => l.trim());
197
+ if (!next || !/^\s*<!--/.test(next) || /doc-lint-disable:/.test(next)) out.push({ n: i + 1, line });
198
+ });
199
+ return out;
200
+ }
201
+
91
202
  function lintFile(path, findings) {
92
203
  if (SKIP.test(path) || !existsSync(path)) return;
93
204
  const text = readFileSync(path, 'utf8');
@@ -99,11 +210,17 @@ function lintFile(path, findings) {
99
210
  if (scope === 'truth' && nonEmpty <= 10 && /\b(moved to|relocated to|see [\w`./-]+ instead)\b/i.test(text) && !disabled.has('redirect-doc'))
100
211
  findings.push({ path, n: 1, severity: 'block', id: 'redirect-doc', message: 'redirect stub — delete the file and repoint every inbound link' });
101
212
 
213
+ for (const { n, line } of unexplainedDisables(text))
214
+ if (!disabled.has('disable-without-reason')) findings.push({ path, n, severity: 'warn', id: 'disable-without-reason', message: 'file-wide doc-lint-disable with no reason comment on the next line — say why the rule does not apply here', excerpt: line.trim().slice(0, 90) });
215
+
102
216
  for (const rule of RULES) {
103
217
  if (rule.scope !== scope || disabled.has(rule.id)) continue;
104
218
  if (rule.exempt && rule.exempt.test(path)) continue;
219
+ if (rule.only && !rule.only.test(path)) continue;
105
220
  for (const { n, line } of lines) {
106
- const subject = rule.strip ? line.replace(rule.strip, '') : line;
221
+ // A code span quotes a name or a phrase rather than saying it, so no tense rule reads inside one.
222
+ const bare = line.replace(/`[^`]*`/g, '');
223
+ const subject = rule.strip ? bare.replace(rule.strip, '') : bare;
107
224
  if (!rule.pattern.test(subject)) continue;
108
225
  if (line.includes(`doc-lint-disable: ${rule.id}`) || new RegExp(`doc-lint-disable:[^>]*\\b${rule.id}\\b`).test(line)) continue;
109
226
  findings.push({ path, n, severity: rule.severity, id: rule.id, message: rule.message, excerpt: line.trim().slice(0, 90) });
@@ -117,17 +234,20 @@ function lintFile(path, findings) {
117
234
  findings.push({ path, n: 1, severity: 'block', id: 'record-type-in-truth-location', message: `\`type: ${declared}\` is a record genre declared in a truth location — move the file to \`docs/history/\` if it is a record, or give it its location's genre` });
118
235
  }
119
236
 
120
- // A folder index (Readme.md/README.md) is a browse page, not a feature doc — it owns no code.
121
- const isFolderIndex = /(^|\/)(readme)\.md$/i.test(path);
237
+ // A folder index (Readme.md/README.md) is a browse page that owns no code; a Readme declaring an owning genre is
238
+ // that folder's landing document and is graded as its genre instead.
239
+ const isFolderIndex = /(^|\/)(readme)\.md$/i.test(path) && !['feature', 'package', 'system'].includes(declaredType(text));
122
240
 
123
- // An index names folders, never files (SKILL.md § The Two Natures). A link from a folder index or
241
+ // An index names folders, never files (SKILL.md § Indexes and descriptions). A link from a folder index or
124
242
  // the authority map to an individual document is a row that must be edited on every add, rename
125
243
  // and retirement; the only files an index may link are the mandated scaffolds and a sibling
126
244
  // folder's own Readme.
127
245
  const isAuthorityMap = /(^|\/)docs\/structure\.md$/.test(path);
128
246
  const isRootReadme = /^(\.\/)?README\.md$/i.test(path.replace(process.cwd() + '/', ''));
129
- if (scope === 'truth' && (isFolderIndex || isAuthorityMap) && !disabled.has('index-lists-files')) {
130
- const SCAFFOLD = /(^|\/)(readme|structure|changelog|backlog|main|health-log|journal|claude|agents)\.md$|(^|\/)operations\/setup\.md$/i;
247
+ // Only the docs tree's indexes and the repo-root README are graded as indexes: a package's README links its own CONTRIBUTING.md.
248
+ const inDocs = /(^|\/)docs\//.test(path);
249
+ if (scope === 'truth' && ((isFolderIndex && (inDocs || isRootReadme)) || isAuthorityMap) && !disabled.has('index-lists-files')) {
250
+ const SCAFFOLD = /(^|\/)(readme|structure|changelog|backlog|main|health-log|journal|claude|agents|contributing|license|security|code_of_conduct)\.md$|(^|\/)operations\/setup\.md$/i;
131
251
  for (const { n, line } of lines) {
132
252
  if (line.includes('doc-lint-disable: index-lists-files')) continue;
133
253
  for (const m of line.matchAll(/\]\(([^)\s#]+\.md)(#[^)]*)?\)/g)) {
@@ -142,18 +262,16 @@ function lintFile(path, findings) {
142
262
  }
143
263
  }
144
264
  // A folder index describes directories; a drawn tree is a file roster in another costume
145
- // (SKILL.md § Indexes describe directories, never files). structure.md is the authority map and keeps its tree.
265
+ // (SKILL.md § Indexes and descriptions). structure.md is the authority map and keeps its tree.
146
266
  if (scope === 'truth' && isFolderIndex && (isRootReadme || /(^|\/)docs\//.test(path)) && !disabled.has('index-file-tree')) {
147
267
  // Raw lines, not proseLines: a tree is drawn inside a code fence, which proseLines drops. The repo-root README routes by a Map table; its tree is docs/structure.md's too (templates/root-readme.md).
148
268
  const rawLines = text.split('\n').map((line, i) => ({ n: i + 1, line }));
149
269
  const tree = rawLines.find(({ line }) => /^\s*[│├└]/.test(line));
150
270
  if (tree) findings.push({ path, n: tree.n, severity: 'block', id: 'index-file-tree', message: 'a tree drawn in a folder index — describe each directory in a sentence; the tree is docs/structure.md\'s', excerpt: tree.line.trim().slice(0, 90) });
151
271
  }
152
- // A file or record named without a link is the same per-file row as a link (SKILL.md § Every document describes itself).
272
+ // A file or record named without a link is the same per-file row as a link (SKILL.md § Indexes and descriptions).
153
273
  // docs/dna/ is the product-dna skill's generated tree, laid out by that skill's own schema.
154
274
  const isGeneratedTree = /(^|\/)docs\/dna\//.test(path);
155
- // The bookkeeping queue is written by the harness hooks from a fixed header (hooks/lib/state.mjs); it carries no frontmatter.
156
- const isHookQueue = /(^|\/)docs\/operations\/bookkeeping\.md$/.test(path);
157
275
  if (scope === 'truth' && isFolderIndex && /(^|\/)docs\//.test(path) && !isGeneratedTree && !disabled.has('index-names-records')) {
158
276
  const SCAFFOLD_NAME = /^(readme|structure|changelog|backlog|main|health-log|journal|claude|agents|setup)\.md$/i;
159
277
  for (const { n, line } of lines) {
@@ -164,7 +282,7 @@ function lintFile(path, findings) {
164
282
  }
165
283
  }
166
284
  // Every truth document and folder index under docs/ describes itself in at most 300 characters (references/frontmatter.md § description:).
167
- if (scope === 'truth' && /(^|\/)docs\//.test(path) && !isGeneratedTree && !isAuthorityMap && !isHookQueue && !disabled.has('description-missing')) {
285
+ if (scope === 'truth' && /(^|\/)docs\//.test(path) && !isGeneratedTree && !isAuthorityMap && !disabled.has('description-missing')) {
168
286
  const fmEnd = text.startsWith('---') ? text.indexOf('\n---', 3) : -1;
169
287
  const fmText = fmEnd === -1 ? '' : text.slice(0, fmEnd);
170
288
  const m = /^description:[ \t]*(.*)$/m.exec(fmText);
@@ -177,7 +295,7 @@ function lintFile(path, findings) {
177
295
  for (const l of rest) { if (/^\s+\S/.test(l)) block.push(l.trim()); else break; }
178
296
  value = block.join(' ');
179
297
  }
180
- value = value.replace(/^(["'])(.*)\1$/, '$2');
298
+ value = /^["']/.test(value) ? value.replace(/^(["'])(.*)\1\s*(#.*)?$/, '$2') : value.replace(/\s+#.*$/, '');
181
299
  if (!value) findings.push({ path, n: 1, severity: 'block', id: 'description-missing', message: '`description:` is empty — at most 300 characters saying what this document settles and where its boundary sits' });
182
300
  else if (value.length > 300 && !disabled.has('description-length')) findings.push({ path, n: 1, severity: 'block', id: 'description-length', message: `\`description:\` is ${value.length} characters — at most 300` });
183
301
  }
@@ -190,24 +308,47 @@ function lintFile(path, findings) {
190
308
  const chrome = raw.findIndex((l) => /shields\.io|<img\b|<div\b|<p align|^!\[/.test(l));
191
309
  if (!disabled.has('root-readme-chrome') && chrome !== -1) findings.push({ path, n: chrome + 1, severity: 'block', id: 'root-readme-chrome', message: 'badge, image or HTML layout in the root README — agents cannot read it and the team does not need it', excerpt: raw[chrome].trim().slice(0, 90) });
192
310
  }
193
- if (scope === 'truth' && !isFolderIndex && /(^|\/)docs\/features\/[^/]+\.md$/.test(path)) {
194
- const fm = text.startsWith('---') ? text.slice(0, text.indexOf('\n---', 3)) : '';
195
- for (const key of ['objects:', 'status:', 'type:'])
196
- if (!fm.includes(key)) findings.push({ path, n: 1, severity: 'block', id: 'feature-frontmatter', message: `feature doc missing \`${key}\` frontmatter` });
311
+ // The genre's own contract, from the registry: its required keys, its required sections, and a `type:` it knows.
312
+ const genre = genreOf(path, text);
313
+ const fmKeys = frontmatter(text) || new Map();
314
+ const declared = declaredType(text);
315
+ if (REGISTRY && declared && !REGISTRY.some((r) => r.type === declared) && !EXTERNAL_TYPES.test(declared) && !disabled.has('type-unknown'))
316
+ findings.push({ path, n: 1, severity: 'warn', id: 'type-unknown', message: `\`type: ${declared}\` names no genre in the registry (templates/Readme.md) — use its row's value` });
317
+ // An owning genre's keys are what the ownership scan and the graph read, so their absence blocks; elsewhere it warns.
318
+ const owningGenre = genre && ['feature', 'package', 'system'].includes(genre.type);
319
+ if (scope === 'truth' && genre && !isFolderIndex && !disabled.has('required-key') && !(genre.type === 'feature' && disabled.has('feature-frontmatter'))) {
320
+ for (const key of genre.keys) {
321
+ // `description` carries its own rule above, and an owning document's `objects` its own below.
322
+ if (key === 'description' || (key === 'objects' && owningGenre) || fmKeys.has(key)) continue;
323
+ findings.push({ path, n: 1, severity: owningGenre ? 'block' : 'warn', id: 'required-key', message: `${genre.genre.toLowerCase()} document missing \`${key}:\` frontmatter (templates/Readme.md § Registry)` });
324
+ }
325
+ }
326
+ if (scope === 'truth' && genre && genre.sections.length && !isFolderIndex && fmKeys.get('status') !== 'deprecated' && !disabled.has('section-missing')) {
327
+ const headings = new Set(lines.map(({ line }) => /^##\s+(.+?)\s*$/.exec(line)).filter(Boolean).map((m) => m[1].toLowerCase()));
328
+ for (const section of genre.sections)
329
+ if (!headings.has(section.toLowerCase())) findings.push({ path, n: 1, severity: 'warn', id: 'section-missing', message: `${genre.genre.toLowerCase()} document has no \`## ${section}\` section (templates/Readme.md § Registry)` });
330
+ }
331
+ // A `verifies:` edge is the one doc → doc edge a person authors; a target that does not exist is a typo or a deletion.
332
+ for (const target of listValue(fmKeys.get('verifies'))) {
333
+ if (disabled.has('dangling-edge') || existsSync(target) || existsSync(join(dirname(path), target))) continue;
334
+ findings.push({ path, n: 1, severity: 'block', id: 'dangling-edge', message: `\`verifies: ${target}\` names no document — repo-relative path of the document whose promises this proves` });
197
335
  }
198
336
 
199
337
  // An owning document (feature, package, system) is intent and promises; the sections that narrate
200
338
  // mechanism — a file roster, a walk through the code, a schema or route table — are the graph's
201
- // to compute and are retired from the genre (SKILL.md § A document is intent and promises).
202
- const isOwningDoc = scope === 'truth' && !isFolderIndex && /(^|\/)docs\/(features|architecture\/(packages|systems))\/[^/]+\.md$/.test(path);
339
+ // to compute and are retired from the genre (SKILL.md § Truth documents).
340
+ const isOwningDoc = scope === 'truth' && !isFolderIndex && /(^|\/)docs\/(features\/(?:[^/]+\/)*|architecture\/(packages|systems)\/)[^/]+\.md$/.test(path);
341
+ if (isOwningDoc && /(^|\/)docs\/features\/[^/]+\/.+\.md$/.test(path) && !disabled.has('nested-feature'))
342
+ findings.push({ path, n: 1, severity: 'warn', id: 'nested-feature', message: 'feature document in a subfolder — `docs/features/` is flat; a subject too large for one file is two files' });
203
343
  if (isOwningDoc && !disabled.has('mechanism-section')) {
204
- const RETIRED = /^#{2,4}\s+(Files|Current Behavior|Key Functions|Data|API|Refusals?|Architecture|State|Redux|Affected Modules|Conventions to Follow|Ownership Conflicts|Tests|The shape|Layout|Build and test|Commands|Routes|Schema|Wire)\b/i;
344
+ // Whole headings only: "## Data retention" is a promise, "## Data" the retired table.
345
+ const RETIRED = /^#{2,4}\s+(Files|Key files|Current Behavior|Key Functions|Data|Data model|Data flow|API|API surface|Refusals?|Architecture|State|State management|Redux|Affected Modules|Conventions to Follow|Ownership Conflicts|Tests|The shape|Layout|Build and test|Commands|Routes|Schema|Wire)\s*:?\s*$/i;
205
346
  for (const { n, line } of lines) {
206
347
  if (!RETIRED.test(line) || line.includes('doc-lint-disable: mechanism-section')) continue;
207
348
  findings.push({ path, n, severity: 'block', id: 'mechanism-section', message: 'mechanism section in an owning document — columns, routes, guards, functions and files are the graph\'s to compute; keep the decision or the promise, point by name, cut the rest', excerpt: line.trim().slice(0, 90) });
208
349
  }
209
- const fm = text.startsWith('---') ? text.slice(0, text.indexOf('\n---', 3)) : '';
210
- if (!/^objects:/m.test(fm)) findings.push({ path, n: 1, severity: 'block', id: 'objects-missing', message: 'owning document names no `objects:` — the product objects it designs are its edge into the graph and its discovery terms' });
350
+ if (!disabled.has('objects-missing') && !listValue(fmKeys.get('objects')).length)
351
+ findings.push({ path, n: 1, severity: 'block', id: 'objects-missing', message: 'owning document names no `objects:` — the product objects it designs are its edge into the graph and its discovery terms' });
211
352
  }
212
353
 
213
354
  // The body of an owning document is scanned by people; its "For the agent" footer holds the pointers
@@ -1,41 +1,68 @@
1
- # Documentation Templates
2
-
3
- One file per template. Every template is either a **truth document** (states current absolutes, updated in place) or a **record** (dated account of activity, frozen or append-only) — the split is defined in `SKILL.md` § The Two Natures, and each template names its nature in its opening line.
4
-
5
- | Template | File | Nature |
6
- | --- | --- | --- |
7
- | Feature (LLD) | [feature.md](feature.md) | Truth |
8
- | Package (one workspace package) | [package.md](package.md) | Truth |
9
- | Doctrine (one concern the platform handles everywhere) | [doctrine.md](doctrine.md) | Truth |
10
- | System (one machine spanning packages) | [system.md](system.md) | Truth |
11
- | Architecture Decision (AD) | [architecture-decision.md](architecture-decision.md) | Truth (Decision Changelog tail sanctioned) |
12
- | System overview | [system-overview.md](system-overview.md) | Truth |
13
- | Domain model | [domain-model.md](domain-model.md) | Truth |
14
- | Glossary | [glossary.md](glossary.md) | Truth |
15
- | Non-goals | [non-goals.md](non-goals.md) | Truth |
16
- | API reference | [api-reference.md](api-reference.md) | Truth |
17
- | Environment registry | [environment-registry.md](environment-registry.md) | Truth |
18
- | Security overview | [security-overview.md](security-overview.md) | Truth |
19
- | Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
20
- | Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
21
- | UX behavior | [ux-behavior.md](ux-behavior.md) | Truth |
22
- | Setup (first run, one per repo) | [setup.md](setup.md) | Truth |
23
- | Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
24
- | Implementation map | [implementation-map.md](implementation-map.md) | Truth |
25
- | Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
26
- | Protected files | [protected-files.md](protected-files.md) | Truth |
27
- | Project root README (outward opening, then orientation and routing) | [root-readme.md](root-readme.md) | Truth |
28
- | docs/README.md (doc index) | [docs-readme.md](docs-readme.md) | Truth |
29
- | Plan | [plan.md](plan.md) | Record |
30
- | Changelog entry | [changelog-entry.md](changelog-entry.md) | Record |
31
- | Migration runbook | [migration-runbook.md](migration-runbook.md) | Record |
32
- | Journal | [journal.md](journal.md) | Record (append-only) |
33
- | CHANGELOG master index | [changelog-index.md](changelog-index.md) | Record index (append-only) |
34
- | Backlog | [backlog.md](backlog.md) | Record (append-only) |
35
- | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
36
-
37
- **The `type:` enum lives here.** A doc's `type:` frontmatter takes its value from this table — one genre per template (`feature`, `architecture-decision`, `non-goals`, …). External generators consume this list rather than defining their own; a type this table does not name defaults to `docs/architecture/` placement and is never added to the protected-files list.
38
-
39
- **Baseline scaffolds** — files that must exist after every baseline (`docs/structure.md` is not among them: it is harness-owned, shipped whole by the installer and overwritten on every refresh, and has no template); the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), [setup.md](setup.md) (when the repo has a lockfile and a runnable root script; never overwriting an existing first-run procedure), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
40
-
41
- **Ownership.** Three genres speak for code — **feature**, **package**, **system**. A document names the objects it designs in `objects:`; a source file names the document that speaks for it in its path header; which document speaks for a path is computed from those headers and the object closure, and answered per path by the ownership index (`references/frontmatter.md`). Until the harness's ownership scan switches to that computation, it reads a transitional `owns:` region glob on each of the three genres: `packages/{name}/**` on a package document (the residual owner), a machine's subtrees on a system document, a feature's own directory on a feature document; the narrower glob wins.
1
+ # Genre registry
2
+
3
+ Every fact about a genre lives in one row of the table below: its template, its nature, its `type:` value, where it lives, the frontmatter keys it must carry and the sections it must have. `SKILL.md`, the `maintenance` agent and the shipped `docs/structure.md` name genres and defer here for these facts, and `scripts/doc-lint.mjs` reads this table to check them. The column order is a contract: the lint parses the table, and `packages/cli/tests/scripts/genreRegistry.test.mjs` in the forge fails when a row stops parsing or a template stops agreeing with it.
4
+
5
+ ## The three natures
6
+
7
+ | Nature | Holds | Changes by | Dates | Ends |
8
+ | --- | --- | --- | --- | --- |
9
+ | `truth` | What is true now | Rewritten in place, present tense; no history in the body (an AD's Decision Changelog tail is the one bridge) | Frontmatter tooling keys only | Deleted, or `status: deprecated`, when its subject is gone |
10
+ | `record` | What happened | Written while live, then frozen at its terminal status; never edited after | In the filename or header | Never; it is the archive |
11
+ | `queue` | Work not done yet | Rows appended at the end and deleted when resolved, drained or promoted; nothing else is edited | Per row, as row metadata | Never; an empty queue is the healthy state |
12
+
13
+ ## Registry
14
+
15
+ | Genre | Template | Nature | `type:` | Location | Required keys | Required sections |
16
+ | --- | --- | --- | --- | --- | --- | --- |
17
+ | Feature | [feature.md](feature.md) | truth | `feature` | `docs/features/{feature}.md` | `type` `description` `status` `objects` | Scope · Promises · For the agent |
18
+ | Package | [package.md](package.md) | truth | `package` | `docs/architecture/packages/{package}.md` | `type` `description` `status` `objects` | Scope · Ownership · Usage · Promises · For the agent |
19
+ | System | [system.md](system.md) | truth | `system` | `docs/architecture/systems/{system}.md` | `type` `description` `status` `objects` | Scope · Flow · Promises · For the agent |
20
+ | Doctrine | [doctrine.md](doctrine.md) | truth | `doctrine` | `docs/architecture/common/{concern}.md` | `type` `description` `status` `objects` | The model · For the agent |
21
+ | Architecture decision | [architecture-decision.md](architecture-decision.md) | truth | `architecture-decision` | `docs/architecture/decisions/AD-NNN-{subject}.md` | `type` `title` `description` `status` `last-updated` | Decision · Context · Consequences |
22
+ | UX behavior | [ux-behavior.md](ux-behavior.md) | truth | `ux` | `docs/features/{surface}-ux.md` | `type` `description` `status` `objects` | Purpose · Anatomy · States · Affordances · Promises · Transitions |
23
+ | System overview | [system-overview.md](system-overview.md) | truth | `system-overview` | `docs/architecture/system-design.md` | `type` `description` | — |
24
+ | Domain model | [domain-model.md](domain-model.md) | truth | `domain-model` | `docs/architecture/domain-model.md` | `type` `description` | — |
25
+ | Glossary | [glossary.md](glossary.md) | truth | `glossary` | `docs/architecture/glossary.md` | `type` `description` | — |
26
+ | Non-goals | [non-goals.md](non-goals.md) | truth | `non-goals` | `docs/architecture/non-goals.md` | `type` `description` | — |
27
+ | Security overview | [security-overview.md](security-overview.md) | truth | `security-overview` | `docs/architecture/security.md` | `type` `description` | — |
28
+ | Testing strategy | [testing-strategy.md](testing-strategy.md) | truth | `testing-strategy` | `docs/architecture/common/testing.md` | `type` `description` | — |
29
+ | API reference | [api-reference.md](api-reference.md) | truth | `api-reference` | `docs/architecture/api.md` | `type` `description` | — |
30
+ | Environment registry | [environment-registry.md](environment-registry.md) | truth | `environment-registry` | `docs/operations/environment.md` | `type` `description` | — |
31
+ | Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | truth | `checklist` | `docs/tests/{feature}.md` | `type` `description` `verifies` | — |
32
+ | Acceptance criteria | [acceptance-criteria.md](acceptance-criteria.md) | truth | `acceptance-criteria` | `docs/acceptance/{capability}.md` | `type` `description` `status` | — |
33
+ | Setup | [setup.md](setup.md) | truth | `operations` | `docs/operations/setup.md` | `type` `description` | Installation · First run · Verification |
34
+ | Operations procedure | [operations-procedure.md](operations-procedure.md) | truth | `operations` | `docs/operations/{procedure}.md` | `type` `description` | Steps · Verification |
35
+ | Implementation map | [implementation-map.md](implementation-map.md) | truth | `implementation-map` | `docs/operations/implementation-map.md` | `type` `description` | Waves · Active wave |
36
+ | Protected files | [protected-files.md](protected-files.md) | truth | `protected-files` | `docs/operations/protected-files.md` | `type` `description` | — |
37
+ | Docs index | [docs-readme.md](docs-readme.md) | truth | `docs-readme` | `docs/README.md` | `type` `description` | — |
38
+ | Folder index | — | truth | `folder-index` | `docs/{folder}/Readme.md` | `type` `description` | — |
39
+ | Changelog guide | [changelog-index.md](changelog-index.md) | truth | `changelog-guide` | `docs/history/CHANGELOG.md` | `type` `description` | — |
40
+ | Root README | [root-readme.md](root-readme.md) | truth | — | `README.md` | — | — |
41
+ | Plan | [plan.md](plan.md) | record | — | `docs/history/plans/YYYY-MM-DD-{slug}.md` | — | Background · Proposed Changes · Doc Obligations · Implementation Steps · Test Obligations |
42
+ | Changelog entry | [changelog-entry.md](changelog-entry.md) | record | — | `docs/history/changelog/YYYY-MM-DD-{three-words}.md` | — | — |
43
+ | Migration runbook | [migration-runbook.md](migration-runbook.md) | record | — | `docs/history/migrations/YYYY-MM-DD-{slug}.md` | — | — |
44
+ | Hand-over | [handover.md](handover.md) | record | `handover` | `docs/history/handovers/YYYY-MM-DD-{slug}.md` | `type` `description` `status` | — |
45
+ | Health log | — | record | — | `docs/history/health-log.md` | — | — |
46
+ | Deep research | — | record | `research` | `docs/history/research/{topic}/` | — | — |
47
+ | Brainstorm session | — | record | `brainstorm` | `docs/research/{topic}-brainstorm/` | — | — |
48
+ | Issue | — | record | `issue` | `docs/operations/issues/YYYY-MM-DD-{slug}.md` | — | — |
49
+ | Triage dossier | — | record | `triage` | `docs/operations/triage/YYYY-MM-DD-{slug}.md` | — | — |
50
+ | Release note | — | record | — | `docs/operations/release-notes/YYYY-MM-DD-vX.Y.Z.md` | — | — |
51
+ | Backlog | [backlog.md](backlog.md) | queue | — | `docs/history/backlog.md` | — | — |
52
+ | Journal | [journal.md](journal.md) | queue | — | `docs/history/journal.md` | — | — |
53
+ | Bookkeeping | [bookkeeping.md](bookkeeping.md) | queue | — | `docs/operations/bookkeeping.md` | — | — |
54
+ | Project memory | [memory-main.md](memory-main.md) | queue | `project-memory` | `docs/memory/main.md` | `type` `description` | — |
55
+ | Agent memory | — | queue | `agent-memory` | `docs/memory/{agent}.md` | — | — |
56
+
57
+ - **`type:` values:** a genre's `type:` value is the one its template's skeleton carries, and two genres may share one (setup and operations procedure are both `operations`). A dash means the genre carries no frontmatter, or carries none it must have. A value this table does not name is reported by the lint as `type-unknown`.
58
+ - **Locations:** `{name}` is one kebab-case path segment, `YYYY-MM-DD` a date, `AD-NNN` a zero-padded number. A location is where the genre lives in a repository that uses it; a folder nothing uses is absent.
59
+ - **Required sections:** the `##` headings every document of the genre carries, by name; the template states the optional ones and when each is written. A dash means the template's sections are guidance, not a fixed set. The lint checks them on truth documents; a plan's are checked by the hooks that read it.
60
+ - **Rows without a template:** each is written by its own tool (the `maintenance` agent's Health Log, the `brainstorm` skill, the deep-research and `issue-bridge` packs, a release pipeline) and is listed so its nature and place are settled here too.
61
+
62
+ ## Ownership
63
+
64
+ Three genres speak for code — **feature**, **package**, **system** — and a **UX** document speaks for a surface's components where none of them does (`ux-behavior.md`). A document names the objects it designs in `objects:`; a source file names the document that speaks for it in its path header. The harness's ownership scan reads one edge, the transitional `owns:` region glob on each owning document (`packages/{name}/**` on a package, a machine's subtrees on a system, a feature's own directory on a feature), and the narrower glob wins. The header pointer is written alongside it so the scan can switch to it and the object closure without a second pass over the code (`references/frontmatter.md`).
65
+
66
+ ## Baseline scaffolds
67
+
68
+ The `maintenance` agent's baseline creates these when missing, from their templates: the docs index, the changelog guide, the backlog, project memory, setup (when the repo has a lockfile and a runnable root script; never a second first-run document) and the root README (never overwriting one). `docs/structure.md` is not among them: the installer ships it whole and overwrites it on every refresh.
@@ -1,23 +1,13 @@
1
- # Acceptance Criteria
1
+ # Acceptance criteria
2
2
 
3
- Truth document. Use for: the promises one capability must keep, stated as machine-parseable
4
- claims. Lives in `docs/acceptance/`, one `kebab-case.md` per capability. Written at specification
5
- time — before implementation — and read by verification surfaces, which parse each `AC-n` line as
6
- a claim to grade the built product against.
3
+ Truth document. Use for: the promises one capability must keep, stated as machine-parseable claims. Lives in `docs/acceptance/`, one `kebab-case.md` per capability. Written at specification time — before implementation — and read by verification surfaces, which parse each `AC-n` line as a claim to grade the built product against.
7
4
 
8
5
  ### Rules
9
6
 
10
- - **Frontmatter is required**: `type: acceptance-criteria`, `status:` (`draft` → `active` →
11
- `deprecated`). No `owns:` — acceptance criteria own no code.
12
- - **One `AC-n` key per criterion, stable forever.** Keys are never renumbered and never reused: a
13
- criterion that no longer applies is marked retired in place, so every claim a past verification
14
- graded still resolves.
15
- - **Each criterion is one observable assert**, phrased as a checkable statement about the running
16
- product — never about the code's internals. If it cannot be observed from outside, it is a
17
- design note, not a criterion.
18
- - **The `AC-n:` line grammar is load-bearing** — verification parses these lines. Keep the key at
19
- line start, exactly `AC-<number>:`, followed by the assert sentence. Formatting that buries the
20
- key (tables, emphasis-wrapped keys, nested bullets) breaks the parse.
7
+ - **Frontmatter is required**: `type: acceptance-criteria`, `status:` (`draft` → `active` → `deprecated`). No `owns:` — acceptance criteria own no code.
8
+ - **One `AC-n` key per criterion, stable forever.** Keys are never renumbered and never reused: a criterion that no longer applies is marked retired in place, so every claim a past verification graded still resolves.
9
+ - **Each criterion is one observable assert**, phrased as a checkable statement about the running product — never about the code's internals. If it cannot be observed from outside, it is a design note, not a criterion.
10
+ - **The `AC-n:` line grammar is load-bearing** — verification parses these lines. Keep the key at line start, exactly `AC-<number>:`, followed by the assert sentence. Formatting that buries the key (tables, emphasis-wrapped keys, nested bullets) breaks the parse.
21
11
 
22
12
  ### Template
23
13
 
@@ -32,7 +22,9 @@ status: draft
32
22
 
33
23
  Scope: {one line — what part of the product these criteria bind}.
34
24
 
35
- - AC-1: {observable assert about the running product}.
36
- - AC-2: {observable assert}.
37
- - AC-3 (retired): {kept verbatim, marked retired — never renumbered}.
25
+ AC-1: {observable assert about the running product}.
26
+
27
+ AC-2: {observable assert}.
28
+
29
+ AC-3: (retired) {kept verbatim, marked retired — never renumbered}.
38
30
  ```
@@ -1,10 +1,15 @@
1
- # API Reference (docs/architecture/api.md)
1
+ # API reference (docs/architecture/api.md)
2
2
 
3
- Truth document. Use for: the consolidated catalog of the system's HTTP surface — every live route in one place. This is the **index**: request/response shapes stay in the owning feature docs; this doc answers "what exists and where is it documented." If the repo generates an OpenAPI spec, this doc points at it and carries only what the spec can't (auth model, conventions, ownership links).
3
+ Truth document. Use for: the consolidated catalog of the system's HTTP surface — every live route in one place. This is the **reference document** for the API surface: a route's shape lives here or in the generated spec, never in a feature doc, which states what the route promises and leaves its shape to this catalog. If the repo generates an OpenAPI spec, this doc points at it and carries only what the spec can't (auth model, conventions, ownership links).
4
4
 
5
5
  Required: **Conventions**, the **route tables**. One row per live route; a route that isn't here doesn't exist.
6
6
 
7
- ```
7
+ ```markdown
8
+ ---
9
+ type: api-reference
10
+ description: "{at most 300 characters: what this document settles and where its boundary sits}"
11
+ ---
12
+
8
13
  # API Reference
9
14
 
10
15
  ## Conventions