@nurix/etna 0.19.66 → 0.19.68

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 (234) hide show
  1. package/bin/etna.js +10 -1
  2. package/data/harness-index.json +1 -5
  3. package/data/harnesses/code/CLAUDE.md +2 -0
  4. package/data/harnesses/code/agents/maintenance.md +4 -1
  5. package/data/harnesses/code/harness.json +7 -2
  6. package/data/harnesses/code/hooks/awareness.mjs +69 -0
  7. package/data/harnesses/code/hooks/compliance.mjs +2 -14
  8. package/data/harnesses/code/hooks/lib/approved.mjs +30 -0
  9. package/data/harnesses/code/hooks/lib/frozen.mjs +3 -0
  10. package/data/harnesses/code/hooks/lib/ownership.mjs +3 -1
  11. package/data/harnesses/code/skills/documentation/references/frontmatter.md +1 -1
  12. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +1 -1
  13. package/data/harnesses/code/skills/documentation/templates/Readme.md +2 -0
  14. package/data/harnesses/code/skills/documentation/templates/structure.md +1 -0
  15. package/data/harnesses/delivery/agents/maintenance.md +4 -1
  16. package/data/harnesses/delivery/harness.json +2 -2
  17. package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +1 -1
  18. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +1 -1
  19. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +2 -0
  20. package/data/harnesses/delivery/skills/documentation/templates/structure.md +1 -0
  21. package/data/harnesses/etna/agents/maintenance.md +4 -1
  22. package/data/harnesses/etna/harness.json +2 -2
  23. package/data/harnesses/etna/skills/documentation/references/frontmatter.md +1 -1
  24. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +1 -1
  25. package/data/harnesses/etna/skills/documentation/templates/Readme.md +2 -0
  26. package/data/harnesses/etna/skills/documentation/templates/structure.md +1 -0
  27. package/lib/install/materialize.mjs +51 -57
  28. package/package.json +2 -2
  29. package/data/harnesses/apollo/.mcp.json +0 -8
  30. package/data/harnesses/apollo/.prettierignore +0 -29
  31. package/data/harnesses/apollo/.prettierrc.json +0 -3
  32. package/data/harnesses/apollo/.vscode/settings.json +0 -19
  33. package/data/harnesses/apollo/CLAUDE.md +0 -162
  34. package/data/harnesses/apollo/agents/bookkeeper.md +0 -28
  35. package/data/harnesses/apollo/agents/brainstorm.md +0 -144
  36. package/data/harnesses/apollo/agents/change-review.md +0 -251
  37. package/data/harnesses/apollo/agents/lld-mode.md +0 -58
  38. package/data/harnesses/apollo/agents/maintenance.md +0 -234
  39. package/data/harnesses/apollo/agents/performance-review.md +0 -223
  40. package/data/harnesses/apollo/agents/retro.md +0 -46
  41. package/data/harnesses/apollo/codex/agents/bookkeeper.toml +0 -26
  42. package/data/harnesses/apollo/codex/agents/brainstorm.toml +0 -142
  43. package/data/harnesses/apollo/codex/agents/change-review.toml +0 -249
  44. package/data/harnesses/apollo/codex/agents/lld-mode.toml +0 -52
  45. package/data/harnesses/apollo/codex/agents/maintenance.toml +0 -228
  46. package/data/harnesses/apollo/codex/agents/performance-review.toml +0 -222
  47. package/data/harnesses/apollo/codex/agents/retro.toml +0 -45
  48. package/data/harnesses/apollo/harness.json +0 -19
  49. package/data/harnesses/apollo/hooks/approved-packages.md +0 -44
  50. package/data/harnesses/apollo/hooks/awareness.mjs +0 -405
  51. package/data/harnesses/apollo/hooks/check-bookkeeping-clean.mjs +0 -19
  52. package/data/harnesses/apollo/hooks/check-changelog-presence.mjs +0 -57
  53. package/data/harnesses/apollo/hooks/check-completion-integrity.mjs +0 -71
  54. package/data/harnesses/apollo/hooks/check-ownership.mjs +0 -22
  55. package/data/harnesses/apollo/hooks/compliance.mjs +0 -457
  56. package/data/harnesses/apollo/hooks/hooks.json +0 -23
  57. package/data/harnesses/apollo/hooks/lib/frozen.mjs +0 -58
  58. package/data/harnesses/apollo/hooks/lib/ownership.mjs +0 -242
  59. package/data/harnesses/apollo/hooks/lib/state.mjs +0 -170
  60. package/data/harnesses/apollo/rules/changelog.md +0 -27
  61. package/data/harnesses/apollo/rules/database.md +0 -34
  62. package/data/harnesses/apollo/rules/frontend.md +0 -30
  63. package/data/harnesses/apollo/rules/general.md +0 -40
  64. package/data/harnesses/apollo/rules/nomenclature.md +0 -30
  65. package/data/harnesses/apollo/rules/pnpm.md +0 -57
  66. package/data/harnesses/apollo/rules/testing.md +0 -64
  67. package/data/harnesses/apollo/skills/boundary/SKILL.md +0 -75
  68. package/data/harnesses/apollo/skills/boundary/references/index.md +0 -16
  69. package/data/harnesses/apollo/skills/boundary/references/mechanisms.md +0 -99
  70. package/data/harnesses/apollo/skills/boundary/references/packs/async-messaging.md +0 -81
  71. package/data/harnesses/apollo/skills/boundary/references/packs/auth.md +0 -93
  72. package/data/harnesses/apollo/skills/boundary/references/packs/build-config-runtime.md +0 -93
  73. package/data/harnesses/apollo/skills/boundary/references/packs/cloudflare.md +0 -93
  74. package/data/harnesses/apollo/skills/boundary/references/packs/frontend.md +0 -93
  75. package/data/harnesses/apollo/skills/boundary/references/packs/generic-tail.md +0 -55
  76. package/data/harnesses/apollo/skills/boundary/references/packs/persistence.md +0 -93
  77. package/data/harnesses/apollo/skills/boundary/references/packs/wire-api.md +0 -87
  78. package/data/harnesses/apollo/skills/brainstorm/README.md +0 -56
  79. package/data/harnesses/apollo/skills/brainstorm/SKILL.md +0 -173
  80. package/data/harnesses/apollo/skills/brainstorm/references/doctrine.md +0 -98
  81. package/data/harnesses/apollo/skills/brainstorm/references/harvest.md +0 -86
  82. package/data/harnesses/apollo/skills/brainstorm/references/session-schema.md +0 -186
  83. package/data/harnesses/apollo/skills/brainstorm/scripts/detector.mjs +0 -460
  84. package/data/harnesses/apollo/skills/design/SKILL.md +0 -121
  85. package/data/harnesses/apollo/skills/design/components/charts.md +0 -76
  86. package/data/harnesses/apollo/skills/design/components/collections.md +0 -132
  87. package/data/harnesses/apollo/skills/design/components/feedback.md +0 -47
  88. package/data/harnesses/apollo/skills/design/components/hooks.md +0 -35
  89. package/data/harnesses/apollo/skills/design/components/index.md +0 -143
  90. package/data/harnesses/apollo/skills/design/components/inputs.md +0 -327
  91. package/data/harnesses/apollo/skills/design/components/layout.md +0 -52
  92. package/data/harnesses/apollo/skills/design/components/media.md +0 -66
  93. package/data/harnesses/apollo/skills/design/components/motion.md +0 -82
  94. package/data/harnesses/apollo/skills/design/components/navigation.md +0 -29
  95. package/data/harnesses/apollo/skills/design/components/overlays.md +0 -72
  96. package/data/harnesses/apollo/skills/design/components/setup.md +0 -40
  97. package/data/harnesses/apollo/skills/design/components/typography.md +0 -46
  98. package/data/harnesses/apollo/skills/design/components/utils.md +0 -37
  99. package/data/harnesses/apollo/skills/design/data/blueprints/browse.md +0 -38
  100. package/data/harnesses/apollo/skills/design/data/blueprints/creation-flow.md +0 -38
  101. package/data/harnesses/apollo/skills/design/data/blueprints/dashboard.md +0 -38
  102. package/data/harnesses/apollo/skills/design/data/blueprints/grid.md +0 -38
  103. package/data/harnesses/apollo/skills/design/data/blueprints/queue.md +0 -38
  104. package/data/harnesses/apollo/skills/design/data/blueprints/record-detail.md +0 -38
  105. package/data/harnesses/apollo/skills/design/data/blueprints/settings.md +0 -38
  106. package/data/harnesses/apollo/skills/design/data/charts/census.csv +0 -58
  107. package/data/harnesses/apollo/skills/design/data/checklists/browse.md +0 -21
  108. package/data/harnesses/apollo/skills/design/data/checklists/creation-flow.md +0 -20
  109. package/data/harnesses/apollo/skills/design/data/checklists/dashboard.md +0 -21
  110. package/data/harnesses/apollo/skills/design/data/checklists/grid.md +0 -21
  111. package/data/harnesses/apollo/skills/design/data/checklists/queue.md +0 -21
  112. package/data/harnesses/apollo/skills/design/data/checklists/record-detail.md +0 -21
  113. package/data/harnesses/apollo/skills/design/data/checklists/settings.md +0 -21
  114. package/data/harnesses/apollo/skills/design/data/chrome/board-column.md +0 -68
  115. package/data/harnesses/apollo/skills/design/data/chrome/data-table.md +0 -63
  116. package/data/harnesses/apollo/skills/design/data/chrome/side-panel.md +0 -60
  117. package/data/harnesses/apollo/skills/design/data/chrome/sidebar.md +0 -78
  118. package/data/harnesses/apollo/skills/design/data/chrome/stat-cards.md +0 -62
  119. package/data/harnesses/apollo/skills/design/data/chrome/topbar.md +0 -65
  120. package/data/harnesses/apollo/skills/design/data/elevation/ant-design-directional-shadow.md +0 -19
  121. package/data/harnesses/apollo/skills/design/data/elevation/apple-liquid-glass-material.md +0 -18
  122. package/data/harnesses/apollo/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +0 -20
  123. package/data/harnesses/apollo/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +0 -20
  124. package/data/harnesses/apollo/skills/design/data/elevation/github-primer-sparing-shadow.md +0 -19
  125. package/data/harnesses/apollo/skills/design/data/elevation/ibm-carbon-layer-and-border.md +0 -19
  126. package/data/harnesses/apollo/skills/design/data/elevation/linear-dark-surface-ladder.md +0 -17
  127. package/data/harnesses/apollo/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +0 -19
  128. package/data/harnesses/apollo/skills/design/data/elevation/material-3-tonal-elevation.md +0 -21
  129. package/data/harnesses/apollo/skills/design/data/elevation/neobrutalist-hard-offset.md +0 -19
  130. package/data/harnesses/apollo/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +0 -18
  131. package/data/harnesses/apollo/skills/design/data/elevation/notion-warm-minimal-hairline.md +0 -17
  132. package/data/harnesses/apollo/skills/design/data/elevation/polaris-elevation-inset-bevel.md +0 -21
  133. package/data/harnesses/apollo/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +0 -19
  134. package/data/harnesses/apollo/skills/design/data/elevation/shadcn-neutral-derived-radius.md +0 -19
  135. package/data/harnesses/apollo/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +0 -18
  136. package/data/harnesses/apollo/skills/design/data/elevation/tailwind-default-utility-scale.md +0 -19
  137. package/data/harnesses/apollo/skills/design/data/elevation/vercel-geist-shadow-as-border.md +0 -20
  138. package/data/harnesses/apollo/skills/design/data/iconography.md +0 -199
  139. package/data/harnesses/apollo/skills/design/data/motion/accordion-expand.md +0 -18
  140. package/data/harnesses/apollo/skills/design/data/motion/destructive-hold-confirm.md +0 -18
  141. package/data/harnesses/apollo/skills/design/data/motion/drag-dismiss-momentum.md +0 -18
  142. package/data/harnesses/apollo/skills/design/data/motion/empty-state-arrive.md +0 -18
  143. package/data/harnesses/apollo/skills/design/data/motion/hover-lift.md +0 -18
  144. package/data/harnesses/apollo/skills/design/data/motion/modal-center-arrive.md +0 -18
  145. package/data/harnesses/apollo/skills/design/data/motion/number-ticker-roll.md +0 -18
  146. package/data/harnesses/apollo/skills/design/data/motion/onboarding-bounce.md +0 -18
  147. package/data/harnesses/apollo/skills/design/data/motion/panel-slide.md +0 -18
  148. package/data/harnesses/apollo/skills/design/data/motion/press-feedback.md +0 -18
  149. package/data/harnesses/apollo/skills/design/data/motion/progress-linear-loop.md +0 -18
  150. package/data/harnesses/apollo/skills/design/data/motion/route-crossfade.md +0 -18
  151. package/data/harnesses/apollo/skills/design/data/motion/rubber-band-overscroll.md +0 -18
  152. package/data/harnesses/apollo/skills/design/data/motion/skeleton-shimmer.md +0 -18
  153. package/data/harnesses/apollo/skills/design/data/motion/stagger-arrive.md +0 -18
  154. package/data/harnesses/apollo/skills/design/data/motion/toast-enter-stack.md +0 -18
  155. package/data/harnesses/apollo/skills/design/data/motion/tooltip-chain-instant.md +0 -18
  156. package/data/harnesses/apollo/skills/design/data/motion/trigger-anchored-reveal.md +0 -18
  157. package/data/harnesses/apollo/skills/design/data/tells.csv +0 -34
  158. package/data/harnesses/apollo/skills/design/data/voices/bright-collaborator.md +0 -29
  159. package/data/harnesses/apollo/skills/design/data/voices/direct-operator.md +0 -29
  160. package/data/harnesses/apollo/skills/design/data/voices/earnest-helper.md +0 -29
  161. package/data/harnesses/apollo/skills/design/data/voices/editorial-composed.md +0 -29
  162. package/data/harnesses/apollo/skills/design/data/voices/quiet-concierge.md +0 -29
  163. package/data/harnesses/apollo/skills/design/data/voices/warm-guide.md +0 -29
  164. package/data/harnesses/apollo/skills/design/references/compose.md +0 -23
  165. package/data/harnesses/apollo/skills/design/references/content.md +0 -28
  166. package/data/harnesses/apollo/skills/design/references/emission.md +0 -21
  167. package/data/harnesses/apollo/skills/design/references/existing-apps.md +0 -27
  168. package/data/harnesses/apollo/skills/design/references/foundations.md +0 -57
  169. package/data/harnesses/apollo/skills/design/references/intake.md +0 -40
  170. package/data/harnesses/apollo/skills/design/references/object-pass.md +0 -24
  171. package/data/harnesses/apollo/skills/design/references/ship-gate.md +0 -29
  172. package/data/harnesses/apollo/skills/design/references/ux-patterns/Readme.md +0 -195
  173. package/data/harnesses/apollo/skills/design/references/ux-patterns/ai-layer.md +0 -49
  174. package/data/harnesses/apollo/skills/design/references/ux-patterns/attributes-lifecycle.md +0 -43
  175. package/data/harnesses/apollo/skills/design/references/ux-patterns/container-choice.md +0 -44
  176. package/data/harnesses/apollo/skills/design/references/ux-patterns/creation-paths.md +0 -46
  177. package/data/harnesses/apollo/skills/design/references/ux-patterns/dashboards.md +0 -42
  178. package/data/harnesses/apollo/skills/design/references/ux-patterns/modes.md +0 -42
  179. package/data/harnesses/apollo/skills/design/references/ux-patterns/object-model.md +0 -46
  180. package/data/harnesses/apollo/skills/design/references/ux-patterns/page-archetype.md +0 -48
  181. package/data/harnesses/apollo/skills/design/references/ux-patterns/promotion-demotion.md +0 -37
  182. package/data/harnesses/apollo/skills/design/references/ux-patterns/settings.md +0 -42
  183. package/data/harnesses/apollo/skills/design/references/ux-patterns/standard-pages.md +0 -46
  184. package/data/harnesses/apollo/skills/design/references/ux-patterns/state-matrix.md +0 -41
  185. package/data/harnesses/apollo/skills/design/references/ux-patterns/table-mechanics.md +0 -48
  186. package/data/harnesses/apollo/skills/design/references/ux-patterns/verbs.md +0 -45
  187. package/data/harnesses/apollo/skills/design/references/ux-patterns/viewer-context.md +0 -40
  188. package/data/harnesses/apollo/skills/design/scripts/README-validator.md +0 -27
  189. package/data/harnesses/apollo/skills/design/scripts/select-chart.mjs +0 -622
  190. package/data/harnesses/apollo/skills/design/scripts/validate-design-md.mjs +0 -1198
  191. package/data/harnesses/apollo/skills/documentation/SKILL.md +0 -135
  192. package/data/harnesses/apollo/skills/documentation/references/examples.md +0 -279
  193. package/data/harnesses/apollo/skills/documentation/references/frontmatter.md +0 -44
  194. package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +0 -50
  195. package/data/harnesses/apollo/skills/documentation/references/style-guide.md +0 -384
  196. package/data/harnesses/apollo/skills/documentation/references/untemplated.md +0 -38
  197. package/data/harnesses/apollo/skills/documentation/scripts/doc-lint.mjs +0 -140
  198. package/data/harnesses/apollo/skills/documentation/templates/Readme.md +0 -35
  199. package/data/harnesses/apollo/skills/documentation/templates/api-reference.md +0 -20
  200. package/data/harnesses/apollo/skills/documentation/templates/architecture-decision.md +0 -121
  201. package/data/harnesses/apollo/skills/documentation/templates/backlog.md +0 -33
  202. package/data/harnesses/apollo/skills/documentation/templates/behavioral-checklist.md +0 -81
  203. package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +0 -18
  204. package/data/harnesses/apollo/skills/documentation/templates/changelog-entry.md +0 -57
  205. package/data/harnesses/apollo/skills/documentation/templates/changelog-index.md +0 -41
  206. package/data/harnesses/apollo/skills/documentation/templates/docs-readme.md +0 -72
  207. package/data/harnesses/apollo/skills/documentation/templates/domain-model.md +0 -30
  208. package/data/harnesses/apollo/skills/documentation/templates/environment-registry.md +0 -15
  209. package/data/harnesses/apollo/skills/documentation/templates/feature.md +0 -150
  210. package/data/harnesses/apollo/skills/documentation/templates/glossary.md +0 -11
  211. package/data/harnesses/apollo/skills/documentation/templates/implementation-map.md +0 -42
  212. package/data/harnesses/apollo/skills/documentation/templates/journal.md +0 -13
  213. package/data/harnesses/apollo/skills/documentation/templates/memory-main.md +0 -25
  214. package/data/harnesses/apollo/skills/documentation/templates/migration-runbook.md +0 -34
  215. package/data/harnesses/apollo/skills/documentation/templates/non-goals.md +0 -11
  216. package/data/harnesses/apollo/skills/documentation/templates/operations-procedure.md +0 -36
  217. package/data/harnesses/apollo/skills/documentation/templates/plan.md +0 -88
  218. package/data/harnesses/apollo/skills/documentation/templates/protected-files.md +0 -37
  219. package/data/harnesses/apollo/skills/documentation/templates/root-readme.md +0 -104
  220. package/data/harnesses/apollo/skills/documentation/templates/security-overview.md +0 -27
  221. package/data/harnesses/apollo/skills/documentation/templates/structure.md +0 -65
  222. package/data/harnesses/apollo/skills/documentation/templates/system-overview.md +0 -32
  223. package/data/harnesses/apollo/skills/documentation/templates/testing-strategy.md +0 -29
  224. package/data/harnesses/apollo/skills/schema-design/SKILL.md +0 -141
  225. package/data/harnesses/apollo/skills/test/SKILL.md +0 -104
  226. package/data/harnesses/apollo/skills/testing/SKILL.md +0 -166
  227. package/data/harnesses/apollo/skills/workflow-mapper/SKILL.md +0 -451
  228. package/data/harnesses/apollo/skills/workflow-mapper/references/patterns.md +0 -214
  229. /package/data/harnesses/{apollo → code}/docs/README.md +0 -0
  230. /package/data/harnesses/{apollo → code}/docs/history/CHANGELOG.md +0 -0
  231. /package/data/harnesses/{apollo → code}/docs/history/backlog.md +0 -0
  232. /package/data/harnesses/{apollo → code}/docs/memory/main.md +0 -0
  233. /package/data/harnesses/{apollo → code}/docs/structure.md +0 -0
  234. /package/data/harnesses/{apollo → code}/rules/cloudflare.md +0 -0
@@ -1,186 +0,0 @@
1
- # Session schema — the single format authority
2
-
3
- Load when: bootstrapping a session, writing any working-surface file, dispatching the twin, or
4
- interpreting detector output. The detector (`scripts/detector.mjs`) is the mechanical half of this
5
- contract — every rule below that carries a rule ID is machine-checked; everything else is the
6
- twin's semantic judgment.
7
-
8
- ## Folder layout
9
-
10
- ```
11
- docs/research/{topic}-brainstorm/ ← open session (kebab-case topic slug)
12
- ├── state.md ← anchor + aspect map (twin-written)
13
- ├── thesis.md ← composed claim → argument → what changed (twin-written)
14
- ├── forks.md ← open questions with close conditions (twin-written)
15
- ├── ledger.md ← commitments (twin-written)
16
- ├── basis/ ← frozen entry material (facilitator-written once, at intake)
17
- ├── fragments/ ← harvest-native fragments (twin-written; materialize on
18
- │ first real content, drafted in destination format)
19
- └── ops/ ← untracked capture layer (deleted at stop)
20
- ├── .gitignore ← contains `*` — ops/ never enters git [BS-OPS-01/02]
21
- ├── capture.md ← facilitator's raw append ledger (its ONLY write)
22
- ├── sync-log.md ← paired twin-dispatch log
23
- └── manifest.md ← harvest manifest (created by the stop pipeline)
24
- ```
25
-
26
- On a passed harvest the folder **moves** to `docs/history/brainstorms/{topic}/` (ops/ deleted
27
- first) — the move is the completion marker; the user then deletes or commits it. A folder still
28
- under `docs/research/` is by definition unfinished.
29
-
30
- **Write protocol (one writer per file):** the facilitator appends to `ops/capture.md` and writes
31
- the initial `basis/` copy plus the sync-log *dispatch* lines — nothing else, ever. The twin writes
32
- every other file, at gates. The user may hand-edit anything; the twin diffs each file against its
33
- own last compose before rewriting and folds foreign edits as first-class user signal.
34
-
35
- ## state.md
36
-
37
- ```markdown
38
- # State
39
-
40
- - Status: open | stopping | harvested
41
- - Posture: mid-probe on the sync-layer aspect, three forks open
42
- - Started: 2026-07-08
43
- - Sitting: single | multi
44
- - Last compose: 2026-07-08T14:30Z | never
45
-
46
- ## Aspect map
47
-
48
- - persistence — how session state survives restarts → F1, D3
49
- - onboarding — the first-run experience → thesis
50
- - risks — failure modes and mitigations → none yet
51
- ```
52
-
53
- - `Status` transitions only forward: `open → stopping → harvested` (the twin flips `stopping` when
54
- the stop pipeline begins, `harvested` at the move). [BS-STATE-01]
55
- - The aspect map is derived per idea from the basis and edited by the user. Each aspect carries
56
- `→` refs to the content covering it: fork IDs (`F#`), ledger IDs (`D#`), `thesis`,
57
- `fragment:{name}`, or `none yet` (an open coverage gap — informational mid-session
58
- [BS-ASPECT-01], blocking at the manifest gate [BS-ASPECT-02]). Ref *presence* is the detector's
59
- half; whether the content actually covers the aspect is the twin's.
60
-
61
- ## thesis.md
62
-
63
- ```markdown
64
- # {Topic} — thesis
65
-
66
- Current as of: 2026-07-08T14:30Z
67
-
68
- ## Claim
69
- ## Argument
70
- ## What changed
71
- ```
72
-
73
- Composed prose, rewritten whole at every compose, never appended. The three sections and the
74
- stamp are mandatory [BS-THESIS-01]; the stamp must equal state.md's `Last compose`
75
- [BS-COMPOSE-01]. At the manifest gate, `Last compose` must be later than the newest
76
- `ops/capture.md` entry — a compose over unsynced capture is stale [BS-COMPOSE-02].
77
-
78
- ## forks.md
79
-
80
- ```markdown
81
- ## F3 — Should reads wait for a compose? [open]
82
-
83
- Full self-contained prose in the user's framing.
84
-
85
- - Raised by: user | twin (S4) | probe | harvest
86
- - Closes when: a pick between wait-for-fresh and read-stale-with-warning
87
- ```
88
-
89
- - IDs `F1, F2, …` unique, never reused [BS-FORK-01]. Status marker: `[open]` or
90
- `[parked → {destination}]`. Every fork carries `Closes when:` [BS-FORK-01].
91
- - `Raised by: twin (S#)` must cite a reconciled sync-log entry [BS-SYNC-02].
92
- - **A resolved fork's entry is deleted** by the twin — the resolving ledger entry's `Resolves:`
93
- field is the record. forks.md holds only open and parked forks; a ledger `Resolves: F3`
94
- coexisting with an open F3 is an inconsistency [BS-FORK-02]. Parked forks survive to the
95
- manifest, which must route each one [BS-MANIFEST-01].
96
- - The manifest gate requires zero `[open]` forks [BS-FORK-03].
97
-
98
- ## ledger.md
99
-
100
- ```markdown
101
- ## D4 — Sessions initialize in dump or interview
102
-
103
- - Decision: every session opens in dump or interview; probe and converge are mid-session moves.
104
- - Why: a first-time user cannot be asked to pick a posture they have no vocabulary for.
105
- Declined: free initial posture — observed cold-starts stall in probe; re-evaluate if
106
- session-replay evals show experienced users fighting the default.
107
- - How to apply: the facilitator's start calibration offers only the two entry moves.
108
- - Resolves: F2
109
- ```
110
-
111
- - IDs `D1, D2, …` unique, append-only [BS-LEDGER-01]. `Decision`, `Why`, `How to apply` are
112
- mandatory; `Resolves:` when the commitment closes forks. Amendments and reversals are
113
- annotations plus a new entry ("Reversed by D9" on the old; D9 names what it reverses) — never a
114
- rewrite. Declined options live inside `Why` per [doctrine.md](doctrine.md).
115
-
116
- ## ops/capture.md
117
-
118
- The facilitator's per-turn append — its one write duty. Lowest possible bar: raw, unpolished,
119
- everything session-relevant from *both* sides (user substance and facilitator substance —
120
- diagnoses, informed rejections, definitions, reasoning chains), including residue judged not
121
- worth pillar filing. Format:
122
-
123
- ```markdown
124
- ---
125
- ### T14 · 2026-07-08T14:22Z
126
-
127
- <raw notes for this turn>
128
- ```
129
-
130
- Turn IDs `T#` ascending with an ISO timestamp — the timestamp is what compose-freshness checks
131
- against [BS-COMPOSE-02]. Never distill here; distillation is the twin's job.
132
-
133
- ## ops/sync-log.md
134
-
135
- Paired-written, one pair per twin dispatch:
136
-
137
- ```
138
- S4 | gate: on-demand | dispatched: 2026-07-08T14:25Z | scope: full reconcile + compose
139
- S4 → reconciled | 2026-07-08T14:29Z | 4 edits · 2 new forks · 6 open · untouched: risks
140
- ```
141
-
142
- The **facilitator** writes the dispatch line as it dispatches; the **twin** appends the result
143
- line (`reconciled` or `failed`) on return — no single author holds both sides. Every dispatch
144
- line must acquire a result line; only the newest entry may be unpaired (in flight) [BS-SYNC-01].
145
- A `failed` result is surfaced to the user at the next turn break and re-run at the next gate.
146
-
147
- ## ops/manifest.md
148
-
149
- Created by the stop pipeline; gates the harvest apply:
150
-
151
- ```markdown
152
- # Harvest manifest — {topic}
153
-
154
- Round: 1
155
- Status: proposed | approved | applied
156
-
157
- | # | What | Source | Destination | Action |
158
- |---|------|--------|-------------|--------|
159
- | 1 | consolidated source of truth | thesis + fragments + ledger | docs/features/{x}.md | create |
160
- | 2 | parked fork F7 | forks.md | docs/architecture/open-questions.md | append |
161
-
162
- ## Uselessness test
163
-
164
- - Result: pass | fail
165
- - Cold-reader allowed paths: <the destination docs above>
166
- - Gaps: none | <named gaps → next Round>
167
- ```
168
-
169
- Every parked fork and every fragment must have a row [BS-MANIFEST-01]. `Status: approved` is set
170
- only after the user approves; gap fixes re-enter as a new `Round` with the same approval
171
- requirement. After apply, no destination doc may link into the session folder [BS-HARVEST-01];
172
- the move to history requires a recorded passing uselessness test [BS-USELESS-01].
173
-
174
- ## Detector
175
-
176
- `node scripts/detector.mjs <session-folder> [--gate=session|manifest|move] [--json]`
177
-
178
- - `session` (default) — the always-on integrity rules; run at any gate.
179
- - `manifest` — adds the convergence gates (zero open forks, full aspect coverage, fresh compose);
180
- the twin may not construct a manifest while any of these fail.
181
- - `move` — adds the post-apply gates (manifest applied, uselessness pass recorded, no links into
182
- scaffolding); the folder may not move while any fail.
183
-
184
- Exit code 0 = clean, 1 = violations (listed with rule IDs), 2 = unusable input. Rules degrade
185
- gracefully: no git context ⇒ BS-OPS-02 reports `skipped`, never blocks. Rule IDs are stable —
186
- consumers (the twin, the evals, the maintenance census) key on them.
@@ -1,460 +0,0 @@
1
- #!/usr/bin/env node
2
- // skills/brainstorm/scripts/detector.mjs - mechanical integrity checks for a brainstorm session folder.
3
- // Contract: references/session-schema.md. Rule IDs are stable; consumers key on them.
4
- // Usage: node detector.mjs <session-folder> [--gate=session|manifest|move] [--json]
5
-
6
- import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
7
- import { join, basename, resolve } from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import { execFileSync } from 'node:child_process';
10
-
11
- const GATES = ['session', 'manifest', 'move'];
12
-
13
- // ---------- shared parsing ----------
14
-
15
- function readIf(path) {
16
- return existsSync(path) ? readFileSync(path, 'utf8') : null;
17
- }
18
-
19
- function parseState(text) {
20
- if (text == null) return null;
21
- const field = (name) => {
22
- const m = text.match(new RegExp(`^- ${name}:\\s*(.+)$`, 'm'));
23
- return m ? m[1].trim() : null;
24
- };
25
- const aspects = [];
26
- const mapSection = text.split(/^## Aspect map\s*$/m)[1];
27
- if (mapSection != null) {
28
- for (const line of mapSection.split('\n')) {
29
- const m = line.match(/^- (.+?) — (.+?) → (.+)$/);
30
- if (m) {
31
- aspects.push({
32
- name: m[1].trim(),
33
- refs: m[3].split(',').map((r) => r.trim()).filter(Boolean),
34
- });
35
- }
36
- }
37
- }
38
- return {
39
- status: field('Status'),
40
- posture: field('Posture'),
41
- started: field('Started'),
42
- sitting: field('Sitting'),
43
- lastCompose: field('Last compose'),
44
- hasAspectSection: mapSection != null,
45
- aspects,
46
- };
47
- }
48
-
49
- // Fork entries: "## F3 — title [open]" / "[parked → destination]"
50
- function parseForks(text) {
51
- if (text == null) return [];
52
- const forks = [];
53
- const re = /^## (F\d+) — (.*?)\s*\[(open|parked → .+?)\]\s*$/gm;
54
- let m;
55
- while ((m = re.exec(text)) !== null) {
56
- const start = m.index + m[0].length;
57
- const next = text.indexOf('\n## ', start);
58
- const body = text.slice(start, next === -1 ? undefined : next);
59
- forks.push({
60
- id: m[1],
61
- title: m[2],
62
- state: m[3] === 'open' ? 'open' : 'parked',
63
- destination: m[3].startsWith('parked') ? m[3].replace('parked →', '').trim() : null,
64
- closesWhen: /^- Closes when:[ \t]*\S/m.test(body),
65
- raisedBy: (body.match(/^- Raised by:\s*(.+)$/m) || [])[1] || null,
66
- });
67
- }
68
- return forks;
69
- }
70
-
71
- // Ledger entries: "## D4 — title"
72
- function parseLedger(text) {
73
- if (text == null) return [];
74
- const entries = [];
75
- const re = /^## (D\d+) — (.+)$/gm;
76
- let m;
77
- while ((m = re.exec(text)) !== null) {
78
- const start = m.index + m[0].length;
79
- const next = text.indexOf('\n## ', start);
80
- const body = text.slice(start, next === -1 ? undefined : next);
81
- entries.push({
82
- id: m[1],
83
- title: m[2],
84
- hasDecision: /^- Decision:[ \t]*\S/m.test(body),
85
- hasWhy: /^- Why:[ \t]*\S/m.test(body),
86
- hasHow: /^- How to apply:[ \t]*\S/m.test(body),
87
- resolves: [...body.matchAll(/^- Resolves:\s*(.+)$/gm)]
88
- .flatMap((r) => r[1].match(/F\d+/g) || []),
89
- });
90
- }
91
- return entries;
92
- }
93
-
94
- // Sync log: dispatch "S4 | gate: x | dispatched: ts | scope: y"
95
- // result "S4 → reconciled|failed | ts | report"
96
- function parseSyncLog(text) {
97
- if (text == null) return { dispatches: [], results: [] };
98
- const dispatches = [];
99
- const results = [];
100
- for (const line of text.split('\n')) {
101
- let m = line.match(/^(S\d+) \| gate: (\S+) \| dispatched: (\S+)/);
102
- if (m) { dispatches.push({ id: m[1], gate: m[2], at: m[3], line }); continue; }
103
- m = line.match(/^(S\d+) → (reconciled|failed)\b/);
104
- if (m) results.push({ id: m[1], outcome: m[2] });
105
- }
106
- return { dispatches, results };
107
- }
108
-
109
- // Capture: "### T14 · 2026-07-08T14:22Z"
110
- function parseCapture(text) {
111
- if (text == null) return [];
112
- return [...text.matchAll(/^### (T\d+) · (\S+)\s*$/gm)].map((m) => ({ id: m[1], at: m[2] }));
113
- }
114
-
115
- function parseManifest(text) {
116
- if (text == null) return null;
117
- const rows = [...text.matchAll(/^\|\s*\d+\s*\|(.+?)\|(.+?)\|(.+?)\|(.+?)\|\s*$/gm)].map((m) => ({
118
- what: m[1].trim(), source: m[2].trim(), destination: m[3].trim(), action: m[4].trim(),
119
- }));
120
- const status = (text.match(/^Status:\s*(\S+)/m) || [])[1] || null;
121
- const uSection = text.split(/^## Uselessness test\s*$/m)[1] || null;
122
- return {
123
- status,
124
- rows,
125
- uselessness: uSection == null ? null : {
126
- result: (uSection.match(/^- Result:\s*(\S+)/m) || [])[1] || null,
127
- gaps: (uSection.match(/^- Gaps:\s*(.+)$/m) || [])[1] || null,
128
- },
129
- };
130
- }
131
-
132
- // ---------- rule registry ----------
133
- // Each rule: { id, gate, check(ctx) -> [] | [messages] | 'skipped' }
134
-
135
- const RULES = [
136
- {
137
- id: 'BS-STATE-01',
138
- gate: 'session',
139
- title: 'state.md present and valid',
140
- check: ({ state }) => {
141
- if (state == null) return ['state.md missing'];
142
- const out = [];
143
- if (!['open', 'stopping', 'harvested'].includes(state.status)) out.push(`Status invalid: ${state.status}`);
144
- if (!state.started) out.push('Started missing');
145
- if (!['single', 'multi'].includes(state.sitting)) out.push(`Sitting invalid: ${state.sitting}`);
146
- if (!state.lastCompose) out.push('Last compose missing');
147
- if (!state.posture) out.push('Posture missing');
148
- return out;
149
- },
150
- },
151
- {
152
- id: 'BS-STATE-02',
153
- gate: 'session',
154
- title: 'aspect map present with at least one aspect',
155
- check: ({ state }) => {
156
- if (state == null) return ['state.md missing'];
157
- if (!state.hasAspectSection) return ['no "## Aspect map" section'];
158
- if (state.aspects.length === 0) return ['aspect map has no parseable aspects'];
159
- return [];
160
- },
161
- },
162
- {
163
- id: 'BS-ASPECT-01',
164
- gate: 'session',
165
- title: 'aspect refs resolve',
166
- check: ({ state, forks, ledger, folder }) => {
167
- if (state == null) return [];
168
- const forkIds = new Set(forks.map((f) => f.id));
169
- const ledgerIds = new Set(ledger.map((d) => d.id));
170
- const resolvedForkIds = new Set(ledger.flatMap((d) => d.resolves));
171
- const out = [];
172
- for (const a of state.aspects) {
173
- for (const ref of a.refs) {
174
- if (ref === 'none yet' || ref === 'thesis') continue;
175
- if (/^F\d+$/.test(ref)) {
176
- // a fork ref may point at an open fork or one already resolved into the ledger
177
- if (!forkIds.has(ref) && !resolvedForkIds.has(ref)) out.push(`aspect "${a.name}" → ${ref}: no such fork (open or resolved)`);
178
- } else if (/^D\d+$/.test(ref)) {
179
- if (!ledgerIds.has(ref)) out.push(`aspect "${a.name}" → ${ref}: no such ledger entry`);
180
- } else if (ref.startsWith('fragment:')) {
181
- const name = ref.slice('fragment:'.length);
182
- if (!existsSync(join(folder, 'fragments', `${name}.md`))) out.push(`aspect "${a.name}" → ${ref}: fragments/${name}.md missing`);
183
- } else {
184
- out.push(`aspect "${a.name}" → unrecognized ref "${ref}"`);
185
- }
186
- }
187
- }
188
- return out;
189
- },
190
- },
191
- {
192
- id: 'BS-ASPECT-02',
193
- gate: 'manifest',
194
- title: 'no uncovered aspects at the manifest gate',
195
- check: ({ state }) => {
196
- if (state == null) return [];
197
- return state.aspects
198
- .filter((a) => a.refs.length === 0 || a.refs.includes('none yet'))
199
- .map((a) => `aspect "${a.name}" has no content`);
200
- },
201
- },
202
- {
203
- id: 'BS-THESIS-01',
204
- gate: 'session',
205
- title: 'thesis has the mandated shape and stamp',
206
- check: ({ thesis }) => {
207
- if (thesis == null) return ['thesis.md missing'];
208
- const out = [];
209
- if (!/^Current as of:\s*\S/m.test(thesis)) out.push('Current as of: stamp missing');
210
- for (const s of ['## Claim', '## Argument', '## What changed']) {
211
- if (!thesis.includes(s)) out.push(`section "${s}" missing`);
212
- }
213
- return out;
214
- },
215
- },
216
- {
217
- id: 'BS-COMPOSE-01',
218
- gate: 'session',
219
- title: 'thesis stamp matches state.md Last compose',
220
- check: ({ state, thesis }) => {
221
- if (state == null || thesis == null || state.lastCompose === 'never') return [];
222
- const stamp = (thesis.match(/^Current as of:\s*(\S+)/m) || [])[1];
223
- return stamp === state.lastCompose ? []
224
- : [`thesis stamp ${stamp} ≠ state Last compose ${state.lastCompose}`];
225
- },
226
- },
227
- {
228
- id: 'BS-COMPOSE-02',
229
- gate: 'manifest',
230
- title: 'compose fresher than the newest capture entry',
231
- check: ({ state, capture }) => {
232
- if (state == null || capture.length === 0) return [];
233
- if (state.lastCompose === 'never') return ['no compose has ever run'];
234
- const newest = capture[capture.length - 1].at;
235
- return state.lastCompose >= newest ? []
236
- : [`last compose ${state.lastCompose} predates newest capture ${newest}`];
237
- },
238
- },
239
- {
240
- id: 'BS-FORK-01',
241
- gate: 'session',
242
- title: 'forks well-formed (unique IDs, close conditions, valid state)',
243
- check: ({ forks }) => {
244
- const out = [];
245
- const seen = new Set();
246
- for (const f of forks) {
247
- if (seen.has(f.id)) out.push(`duplicate fork id ${f.id}`);
248
- seen.add(f.id);
249
- if (!f.closesWhen) out.push(`${f.id} has no "Closes when:"`);
250
- if (f.state === 'parked' && !f.destination) out.push(`${f.id} parked without destination`);
251
- }
252
- return out;
253
- },
254
- },
255
- {
256
- id: 'BS-FORK-02',
257
- gate: 'session',
258
- title: 'no fork both resolved in the ledger and still open',
259
- check: ({ forks, ledger }) => {
260
- const open = new Set(forks.filter((f) => f.state === 'open').map((f) => f.id));
261
- const out = [];
262
- for (const d of ledger) {
263
- for (const f of d.resolves) {
264
- if (open.has(f)) out.push(`${d.id} resolves ${f} but ${f} is still open in forks.md`);
265
- }
266
- }
267
- return out;
268
- },
269
- },
270
- {
271
- id: 'BS-FORK-03',
272
- gate: 'manifest',
273
- title: 'zero open forks at the manifest gate',
274
- check: ({ forks }) => forks.filter((f) => f.state === 'open').map((f) => `${f.id} is open`),
275
- },
276
- {
277
- id: 'BS-LEDGER-01',
278
- gate: 'session',
279
- title: 'ledger entries carry the mandatory fields, IDs unique',
280
- check: ({ ledger }) => {
281
- const out = [];
282
- const seen = new Set();
283
- for (const d of ledger) {
284
- if (seen.has(d.id)) out.push(`duplicate ledger id ${d.id}`);
285
- seen.add(d.id);
286
- if (!d.hasDecision) out.push(`${d.id} missing Decision`);
287
- if (!d.hasWhy) out.push(`${d.id} missing Why`);
288
- if (!d.hasHow) out.push(`${d.id} missing How to apply`);
289
- }
290
- return out;
291
- },
292
- },
293
- {
294
- id: 'BS-OPS-01',
295
- gate: 'session',
296
- title: 'ops/ hygiene: .gitignore present and covering',
297
- check: ({ folder }) => {
298
- const gi = readIf(join(folder, 'ops', '.gitignore'));
299
- if (gi == null) return ['ops/.gitignore missing'];
300
- return gi.split('\n').some((l) => l.trim() === '*') ? [] : ['ops/.gitignore does not contain "*"'];
301
- },
302
- },
303
- {
304
- id: 'BS-OPS-02',
305
- gate: 'session',
306
- title: 'ops/ never committed',
307
- check: ({ folder }) => {
308
- try {
309
- const tracked = execFileSync('git', ['ls-files', '--', 'ops'], {
310
- cwd: folder, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
311
- }).trim();
312
- return tracked === '' ? [] : [`ops/ files tracked by git: ${tracked.split('\n').join(', ')}`];
313
- } catch {
314
- return 'skipped'; // no git context — degrade, never block
315
- }
316
- },
317
- },
318
- {
319
- id: 'BS-SYNC-01',
320
- gate: 'session',
321
- title: 'sync-log pairing: every dispatch has a result (newest may be in flight)',
322
- check: ({ sync }) => {
323
- const resulted = new Set(sync.results.map((r) => r.id));
324
- const unpaired = sync.dispatches.filter((d) => !resulted.has(d.id));
325
- if (unpaired.length === 0) return [];
326
- const last = sync.dispatches[sync.dispatches.length - 1];
327
- if (unpaired.length === 1 && unpaired[0].id === last.id) return []; // in flight
328
- return unpaired.map((d) => `${d.id} dispatched but never resulted`);
329
- },
330
- },
331
- {
332
- id: 'BS-SYNC-02',
333
- gate: 'session',
334
- title: 'twin attributions cite a reconciled sync entry',
335
- check: ({ forks, sync }) => {
336
- const reconciled = new Set(sync.results.filter((r) => r.outcome === 'reconciled').map((r) => r.id));
337
- const out = [];
338
- for (const f of forks) {
339
- if (f.raisedBy == null) continue;
340
- const m = f.raisedBy.match(/^twin\s*\((S\d+)\)/);
341
- if (f.raisedBy.startsWith('twin') && !m) { out.push(`${f.id} raised by twin without a sync id`); continue; }
342
- if (m && !reconciled.has(m[1])) out.push(`${f.id} cites ${m[1]} which never reconciled`);
343
- }
344
- return out;
345
- },
346
- },
347
- {
348
- id: 'BS-MANIFEST-01',
349
- gate: 'manifest',
350
- title: 'manifest routes every parked fork and every fragment',
351
- check: ({ forks, manifest, folder }) => {
352
- if (manifest == null) return ['ops/manifest.md missing'];
353
- const out = [];
354
- const text = manifest.rows.map((r) => `${r.what} ${r.source}`).join('\n');
355
- for (const f of forks.filter((x) => x.state === 'parked')) {
356
- if (!text.includes(f.id)) out.push(`parked ${f.id} has no manifest row`);
357
- }
358
- const fragDir = join(folder, 'fragments');
359
- if (existsSync(fragDir)) {
360
- for (const frag of readdirSync(fragDir).filter((n) => n.endsWith('.md'))) {
361
- if (!text.includes(frag) && !text.includes(basename(frag, '.md'))) {
362
- out.push(`fragments/${frag} has no manifest row`);
363
- }
364
- }
365
- }
366
- return out;
367
- },
368
- },
369
- {
370
- id: 'BS-HARVEST-01',
371
- gate: 'move',
372
- title: 'no harvested destination links into the session folder',
373
- check: ({ manifest, folder }) => {
374
- if (manifest == null) return ['ops/manifest.md missing'];
375
- const slug = basename(resolve(folder));
376
- const out = [];
377
- for (const row of manifest.rows) {
378
- const dest = row.destination;
379
- if (!dest || !dest.endsWith('.md')) continue;
380
- const destPath = resolve(folder, '..', '..', '..', dest); // repo root = 3 up from docs/research/{slug}
381
- const text = readIf(destPath) ?? readIf(resolve(dest));
382
- if (text == null) { out.push(`destination ${dest} not found on disk`); continue; }
383
- if (text.includes(slug)) out.push(`${dest} links into the session folder (${slug})`);
384
- }
385
- return out;
386
- },
387
- },
388
- {
389
- id: 'BS-USELESS-01',
390
- gate: 'move',
391
- title: 'recorded passing uselessness test with no gaps',
392
- check: ({ manifest }) => {
393
- if (manifest?.uselessness == null) return ['manifest has no "## Uselessness test" section'];
394
- const out = [];
395
- if (manifest.uselessness.result !== 'pass') out.push(`result is "${manifest.uselessness.result}"`);
396
- if (manifest.uselessness.gaps !== 'none') out.push(`gaps recorded: ${manifest.uselessness.gaps}`);
397
- if (manifest.status !== 'applied') out.push(`manifest status is "${manifest.status}", not applied`);
398
- return out;
399
- },
400
- },
401
- ];
402
-
403
- // ---------- runner ----------
404
-
405
- export function runDetector(folder, gate = 'session') {
406
- const ctx = {
407
- folder,
408
- state: parseState(readIf(join(folder, 'state.md'))),
409
- thesis: readIf(join(folder, 'thesis.md')),
410
- forks: parseForks(readIf(join(folder, 'forks.md'))),
411
- ledger: parseLedger(readIf(join(folder, 'ledger.md'))),
412
- capture: parseCapture(readIf(join(folder, 'ops', 'capture.md'))),
413
- sync: parseSyncLog(readIf(join(folder, 'ops', 'sync-log.md'))),
414
- manifest: parseManifest(readIf(join(folder, 'ops', 'manifest.md'))),
415
- };
416
- const activeGates = GATES.slice(0, GATES.indexOf(gate) + 1); // gates are cumulative
417
- const results = [];
418
- for (const rule of RULES) {
419
- if (!activeGates.includes(rule.gate)) continue;
420
- const res = rule.check(ctx);
421
- if (res === 'skipped') results.push({ id: rule.id, title: rule.title, status: 'skipped', messages: [] });
422
- else results.push({ id: rule.id, title: rule.title, status: res.length === 0 ? 'pass' : 'fail', messages: res });
423
- }
424
- return results;
425
- }
426
-
427
- function main() {
428
- const args = process.argv.slice(2);
429
- const folder = args.find((a) => !a.startsWith('--'));
430
- const gate = (args.find((a) => a.startsWith('--gate=')) || '--gate=session').split('=')[1];
431
- const asJson = args.includes('--json');
432
-
433
- if (!folder || !existsSync(folder) || !statSync(folder).isDirectory()) {
434
- console.error('usage: node detector.mjs <session-folder> [--gate=session|manifest|move] [--json]');
435
- process.exit(2);
436
- }
437
- if (!GATES.includes(gate)) {
438
- console.error(`unknown gate "${gate}" — one of: ${GATES.join(', ')}`);
439
- process.exit(2);
440
- }
441
-
442
- const results = runDetector(resolve(folder), gate);
443
- const failed = results.filter((r) => r.status === 'fail');
444
-
445
- if (asJson) {
446
- console.log(JSON.stringify({ folder: resolve(folder), gate, results }, null, 2));
447
- } else {
448
- for (const r of results) {
449
- const mark = r.status === 'pass' ? '✓' : r.status === 'skipped' ? '~' : '✗';
450
- console.log(`${mark} ${r.id} — ${r.title}`);
451
- for (const m of r.messages) console.log(` ${m}`);
452
- }
453
- console.log(`\n${failed.length === 0 ? 'clean' : `${failed.length} rule(s) failing`} at gate "${gate}"`);
454
- }
455
- process.exit(failed.length === 0 ? 0 : 1);
456
- }
457
-
458
- if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
459
- main();
460
- }