@mindrian_os/install 1.13.0-beta.11

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 (597) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +9 -0
  3. package/CHANGELOG.md +3333 -0
  4. package/LICENSE +123 -0
  5. package/README.md +673 -0
  6. package/agents/brain-query.md +80 -0
  7. package/agents/framework-runner.md +237 -0
  8. package/agents/grading.md +188 -0
  9. package/agents/investor.md +128 -0
  10. package/agents/larry-extended.md +135 -0
  11. package/agents/opportunity-scanner.md +91 -0
  12. package/agents/persona-analyst.md +132 -0
  13. package/agents/research.md +89 -0
  14. package/agents/reverse-salient-agent.md +27 -0
  15. package/bin/cli.js +142 -0
  16. package/bin/mindrian-mcp-server.cjs +182 -0
  17. package/bin/mindrian-tools.cjs +765 -0
  18. package/commands/act.md +439 -0
  19. package/commands/admin.md +404 -0
  20. package/commands/analyze-needs.md +42 -0
  21. package/commands/analyze-systems.md +39 -0
  22. package/commands/analyze-timing.md +42 -0
  23. package/commands/auto-explore.md +64 -0
  24. package/commands/beautiful-question.md +40 -0
  25. package/commands/brain-derive.md +78 -0
  26. package/commands/build-knowledge.md +42 -0
  27. package/commands/build-thesis.md +46 -0
  28. package/commands/causal.md +234 -0
  29. package/commands/challenge-assumptions.md +33 -0
  30. package/commands/compare-ventures.md +83 -0
  31. package/commands/dashboard.md +110 -0
  32. package/commands/deep-grade.md +82 -0
  33. package/commands/diagnose.md +58 -0
  34. package/commands/diagnostics.md +151 -0
  35. package/commands/doctor.md +151 -0
  36. package/commands/dominant-designs.md +40 -0
  37. package/commands/explain-decision.md +87 -0
  38. package/commands/explore-domains.md +42 -0
  39. package/commands/explore-futures.md +40 -0
  40. package/commands/explore-trends.md +42 -0
  41. package/commands/export.md +103 -0
  42. package/commands/file-meeting.md +724 -0
  43. package/commands/find-analogies.md +188 -0
  44. package/commands/find-bottlenecks.md +62 -0
  45. package/commands/find-connections.md +76 -0
  46. package/commands/funding.md +81 -0
  47. package/commands/grade.md +203 -0
  48. package/commands/graph.md +128 -0
  49. package/commands/hat-briefing.md +125 -0
  50. package/commands/heal.md +196 -0
  51. package/commands/help.md +399 -0
  52. package/commands/hmi-status.md +172 -0
  53. package/commands/jtbd.md +241 -0
  54. package/commands/leadership.md +73 -0
  55. package/commands/lean-canvas.md +40 -0
  56. package/commands/macro-trends.md +40 -0
  57. package/commands/map-unknowns.md +40 -0
  58. package/commands/memory.md +173 -0
  59. package/commands/models.md +175 -0
  60. package/commands/mos-reason.md +285 -0
  61. package/commands/mullins.md +120 -0
  62. package/commands/new-project.md +481 -0
  63. package/commands/onboard.md +434 -0
  64. package/commands/operator.md +149 -0
  65. package/commands/opportunities.md +144 -0
  66. package/commands/organize.md +497 -0
  67. package/commands/persona.md +198 -0
  68. package/commands/pipeline.md +112 -0
  69. package/commands/present.md +91 -0
  70. package/commands/publish.md +201 -0
  71. package/commands/query.md +124 -0
  72. package/commands/radar.md +72 -0
  73. package/commands/reanalyze.md +91 -0
  74. package/commands/research.md +196 -0
  75. package/commands/room.md +352 -0
  76. package/commands/rooms.md +598 -0
  77. package/commands/root-cause.md +40 -0
  78. package/commands/rs-experts.md +85 -0
  79. package/commands/rs-explain.md +100 -0
  80. package/commands/rs-fetch.md +94 -0
  81. package/commands/rs-thesis.md +85 -0
  82. package/commands/scenario-plan.md +40 -0
  83. package/commands/scheduled-tasks.md +285 -0
  84. package/commands/score-innovation.md +43 -0
  85. package/commands/scout.md +239 -0
  86. package/commands/setup.md +618 -0
  87. package/commands/snapshot.md +147 -0
  88. package/commands/speakers.md +84 -0
  89. package/commands/splash.md +28 -0
  90. package/commands/status.md +75 -0
  91. package/commands/structure-argument.md +42 -0
  92. package/commands/suggest-next.md +80 -0
  93. package/commands/systems-thinking.md +40 -0
  94. package/commands/think-hats.md +42 -0
  95. package/commands/update.md +181 -0
  96. package/commands/user-needs.md +40 -0
  97. package/commands/validate.md +40 -0
  98. package/commands/value-proposition.md +61 -0
  99. package/commands/vault.md +180 -0
  100. package/commands/visualize.md +52 -0
  101. package/commands/whitespace.md +507 -0
  102. package/commands/wiki.md +69 -0
  103. package/hooks/hooks.json +381 -0
  104. package/hooks/run-hook.cmd +64 -0
  105. package/lib/__init__.py +0 -0
  106. package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  107. package/lib/agents/auto-explore-agent.cjs +1043 -0
  108. package/lib/agents/reverse-salient-agent.cjs +679 -0
  109. package/lib/agents/tension-hook-agent.cjs +544 -0
  110. package/lib/brain/ROOM.md +44 -0
  111. package/lib/brain/chain-recommender.cjs +301 -0
  112. package/lib/chat/chat-context.js +185 -0
  113. package/lib/chat/chat-panel.js +721 -0
  114. package/lib/chat/fabric-chat.cjs +288 -0
  115. package/lib/chat/generative-tools.js +219 -0
  116. package/lib/conversation/ROOM.md +39 -0
  117. package/lib/conversation/classifier-rules.json +38 -0
  118. package/lib/conversation/classifier.cjs +264 -0
  119. package/lib/conversation/operator.cjs +287 -0
  120. package/lib/copy/115-spec-strings.cjs +55 -0
  121. package/lib/core/__init__.py +0 -0
  122. package/lib/core/__nav-stub.cjs +14 -0
  123. package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
  125. package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
  126. package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
  127. package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
  128. package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
  129. package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
  130. package/lib/core/artifact-id.cjs +148 -0
  131. package/lib/core/asset-ops.cjs +151 -0
  132. package/lib/core/auto-commit-throttle.cjs +129 -0
  133. package/lib/core/bearer-token.cjs +199 -0
  134. package/lib/core/brain-client.cjs +865 -0
  135. package/lib/core/brain-derivation-prompts.cjs +326 -0
  136. package/lib/core/brain-derivation-queue.cjs +431 -0
  137. package/lib/core/brain-derivation.cjs +580 -0
  138. package/lib/core/brain-md-schema.cjs +528 -0
  139. package/lib/core/brain-md-staleness.cjs +357 -0
  140. package/lib/core/brain-response-sanitize.cjs +188 -0
  141. package/lib/core/bridge-writer.cjs +477 -0
  142. package/lib/core/chat-context-builder.cjs +253 -0
  143. package/lib/core/cross-room-aggregator.cjs +762 -0
  144. package/lib/core/daily-briefing.cjs +438 -0
  145. package/lib/core/decision-capture.cjs +618 -0
  146. package/lib/core/deep-links.cjs +82 -0
  147. package/lib/core/dispatch-optimizer.cjs +354 -0
  148. package/lib/core/dual-path-detector.cjs +84 -0
  149. package/lib/core/dual-path-detector.test.cjs +334 -0
  150. package/lib/core/exports-log.cjs +79 -0
  151. package/lib/core/feynman-minto-invariants.cjs +605 -0
  152. package/lib/core/folder-memory-async.cjs +338 -0
  153. package/lib/core/folder-memory-shared.cjs +890 -0
  154. package/lib/core/folder-memory.cjs +416 -0
  155. package/lib/core/framework-chain-composer.cjs +411 -0
  156. package/lib/core/frontmatter-schemas.cjs +330 -0
  157. package/lib/core/git-ops.cjs +141 -0
  158. package/lib/core/graph-ops.cjs +258 -0
  159. package/lib/core/hat-persistence.cjs +362 -0
  160. package/lib/core/index.cjs +60 -0
  161. package/lib/core/integration-registry.cjs +232 -0
  162. package/lib/core/intelligence-cascade.cjs +661 -0
  163. package/lib/core/lazygraph-ops.cjs +1057 -0
  164. package/lib/core/lru-cache.cjs +139 -0
  165. package/lib/core/mcp-profiles.cjs +182 -0
  166. package/lib/core/meeting-ops.cjs +54 -0
  167. package/lib/core/memory-ops.cjs +600 -0
  168. package/lib/core/migrations/ROOM.md +33 -0
  169. package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
  170. package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
  171. package/lib/core/model-profiles.cjs +246 -0
  172. package/lib/core/mullins-scaffold.cjs +160 -0
  173. package/lib/core/nav-dial.cjs +316 -0
  174. package/lib/core/navigation/ROOM.md +15 -0
  175. package/lib/core/navigation/explanation.cjs +43 -0
  176. package/lib/core/navigation/focus.cjs +135 -0
  177. package/lib/core/navigation/ingestion.cjs +82 -0
  178. package/lib/core/navigation/insights.cjs +350 -0
  179. package/lib/core/navigation/memory-events.cjs +118 -0
  180. package/lib/core/navigation/neighborhood.cjs +78 -0
  181. package/lib/core/navigation/packet.cjs +182 -0
  182. package/lib/core/navigation/room-home.cjs +127 -0
  183. package/lib/core/navigation/transitions.cjs +82 -0
  184. package/lib/core/navigation-engine-shared.cjs +242 -0
  185. package/lib/core/navigation-engine.cjs +664 -0
  186. package/lib/core/navigation.cjs +60 -0
  187. package/lib/core/nl-graph-queries.cjs +164 -0
  188. package/lib/core/offer-presenter.cjs +406 -0
  189. package/lib/core/opportunity-extractor.cjs +183 -0
  190. package/lib/core/opportunity-ops.cjs +1371 -0
  191. package/lib/core/persona-ops.cjs +537 -0
  192. package/lib/core/persona-taxonomy.cjs +190 -0
  193. package/lib/core/platform-gates.cjs +120 -0
  194. package/lib/core/platform.cjs +257 -0
  195. package/lib/core/proactive-intelligence.cjs +528 -0
  196. package/lib/core/problem-type-router.cjs +315 -0
  197. package/lib/core/reasoning-ops.cjs +639 -0
  198. package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
  199. package/lib/core/room-classifier-strict-mode.cjs +229 -0
  200. package/lib/core/room-db.cjs +127 -0
  201. package/lib/core/room-ops-async.cjs +92 -0
  202. package/lib/core/room-ops-shared.cjs +64 -0
  203. package/lib/core/room-ops-sync.cjs +70 -0
  204. package/lib/core/room-ops.cjs +32 -0
  205. package/lib/core/room-type-detector.cjs +386 -0
  206. package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
  207. package/lib/core/rs-brain-substrate.cjs +570 -0
  208. package/lib/core/rs-breakthrough-scorer.cjs +255 -0
  209. package/lib/core/rs-canon-violations.cjs +82 -0
  210. package/lib/core/rs-chain-feeder.cjs +343 -0
  211. package/lib/core/rs-commercial-assessor.cjs +280 -0
  212. package/lib/core/rs-differential-scorer.cjs +376 -0
  213. package/lib/core/rs-domain-analyzer.cjs +385 -0
  214. package/lib/core/rs-egress-prompts.cjs +113 -0
  215. package/lib/core/rs-egress-telemetry.cjs +225 -0
  216. package/lib/core/rs-egress-violations.cjs +53 -0
  217. package/lib/core/rs-expert-mapper.cjs +467 -0
  218. package/lib/core/rs-fetcher-academic.cjs +697 -0
  219. package/lib/core/rs-fetcher-experts.cjs +314 -0
  220. package/lib/core/rs-fetcher-industry.cjs +731 -0
  221. package/lib/core/rs-fetcher-patents.cjs +564 -0
  222. package/lib/core/rs-innovation-classifier.cjs +194 -0
  223. package/lib/core/rs-mind-map.cjs +656 -0
  224. package/lib/core/rs-neo4j-writer.cjs +388 -0
  225. package/lib/core/rs-nl-to-query.cjs +425 -0
  226. package/lib/core/rs-pinecone-bridge.cjs +303 -0
  227. package/lib/core/rs-preprocessor.cjs +350 -0
  228. package/lib/core/rs-query-matrix.cjs +316 -0
  229. package/lib/core/rs-query-to-text.cjs +438 -0
  230. package/lib/core/rs-sqlite-mirror.cjs +443 -0
  231. package/lib/core/rs-thesis-generator.cjs +188 -0
  232. package/lib/core/rs_cache.py +479 -0
  233. package/lib/core/rs_corpus.py +468 -0
  234. package/lib/core/rs_hybrid.py +586 -0
  235. package/lib/core/rs_math.py +287 -0
  236. package/lib/core/rs_rooms.py +193 -0
  237. package/lib/core/scheduled-scanner.cjs +463 -0
  238. package/lib/core/scratchpad-ops.cjs +201 -0
  239. package/lib/core/section-8-trace-schema.cjs +138 -0
  240. package/lib/core/section-registry.cjs +111 -0
  241. package/lib/core/session-state.cjs +144 -0
  242. package/lib/core/shallow-doc-parser.cjs +174 -0
  243. package/lib/core/shallow-doc-parser.test.cjs +226 -0
  244. package/lib/core/skill-activation-router.cjs +284 -0
  245. package/lib/core/state-ops.cjs +46 -0
  246. package/lib/core/statusline-cache.cjs +266 -0
  247. package/lib/core/token-estimator.cjs +348 -0
  248. package/lib/core/user-archetype.cjs +239 -0
  249. package/lib/core/user-md-ops.cjs +524 -0
  250. package/lib/core/visual-ops.cjs +624 -0
  251. package/lib/core/write-lock.cjs +149 -0
  252. package/lib/graph/canvas-graph.js +467 -0
  253. package/lib/graph/constellation-config.cjs +299 -0
  254. package/lib/graph/graph-detail-panel.js +165 -0
  255. package/lib/hmi/ROOM.md +47 -0
  256. package/lib/hmi/across-session-memory.cjs +604 -0
  257. package/lib/hmi/cross-room-memory.cjs +575 -0
  258. package/lib/hmi/decoy-tier.cjs +395 -0
  259. package/lib/hmi/jtbd-classifier.cjs +219 -0
  260. package/lib/hmi/jtbd-state.cjs +199 -0
  261. package/lib/hmi/jtbd-taxonomy.json +392 -0
  262. package/lib/hmi/selector-dispatcher.cjs +546 -0
  263. package/lib/hmi/selector-telemetry.cjs +263 -0
  264. package/lib/hmi/shape-f0-renderer.cjs +139 -0
  265. package/lib/hmi/shape-f1-fallback.cjs +80 -0
  266. package/lib/hmi/shape-f1-renderer.cjs +138 -0
  267. package/lib/hmi/shape-f2-renderer.cjs +132 -0
  268. package/lib/hmi/shape-f3-renderer.cjs +66 -0
  269. package/lib/hmi/shape-f4-renderer.cjs +72 -0
  270. package/lib/hmi/shape-f5-renderer.cjs +155 -0
  271. package/lib/hmi/shape-f6-plan-review-renderer.cjs +312 -0
  272. package/lib/hmi/shape-f6-renderer.cjs +144 -0
  273. package/lib/hmi/shape-g-renderer.cjs +219 -0
  274. package/lib/hmi/shape-h-renderer.cjs +222 -0
  275. package/lib/hmi/tier-check.cjs +63 -0
  276. package/lib/import/PRECONDITIONS.md +41 -0
  277. package/lib/import/branding.cjs +210 -0
  278. package/lib/import/branding.test.cjs +235 -0
  279. package/lib/import/classifications-sync.cjs +104 -0
  280. package/lib/import/classifications-sync.test.cjs +129 -0
  281. package/lib/import/enricher.cjs +296 -0
  282. package/lib/import/enricher.test.cjs +273 -0
  283. package/lib/import/integration.test.cjs +376 -0
  284. package/lib/import/manifest.cjs +129 -0
  285. package/lib/import/manifest.schema.json +185 -0
  286. package/lib/import/manifest.test.cjs +123 -0
  287. package/lib/import/meeting-detector.cjs +92 -0
  288. package/lib/import/meeting-detector.test.cjs +100 -0
  289. package/lib/import/person-detector.cjs +229 -0
  290. package/lib/import/person-detector.test.cjs +149 -0
  291. package/lib/import/report.cjs +186 -0
  292. package/lib/import/report.test.cjs +186 -0
  293. package/lib/import/room-md-scaffolder.cjs +49 -0
  294. package/lib/import/router.cjs +224 -0
  295. package/lib/import/router.test.cjs +356 -0
  296. package/lib/import/run-all-tests.cjs +36 -0
  297. package/lib/import/smoke-test.cjs +213 -0
  298. package/lib/import/smoke-test.test.cjs +148 -0
  299. package/lib/import/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
  300. package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
  301. package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
  302. package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
  303. package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
  304. package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
  305. package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
  306. package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
  307. package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
  308. package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -0
  309. package/lib/import/undo.test.cjs +199 -0
  310. package/lib/import/vault-scanner.cjs +105 -0
  311. package/lib/import/vault-scanner.test.cjs +67 -0
  312. package/lib/mcp/app-html/dashboard.html +316 -0
  313. package/lib/mcp/app-html/graph.html +428 -0
  314. package/lib/mcp/app-html/mindrian-platform.html +1841 -0
  315. package/lib/mcp/app-html/wiki.html +383 -0
  316. package/lib/mcp/app-views.cjs +322 -0
  317. package/lib/mcp/brain-router.cjs +418 -0
  318. package/lib/mcp/capability-registry.cjs +62 -0
  319. package/lib/mcp/larry-context.cjs +46 -0
  320. package/lib/mcp/larry-server-instructions.md +114 -0
  321. package/lib/mcp/pipeline-state.cjs +275 -0
  322. package/lib/mcp/prompts.cjs +302 -0
  323. package/lib/mcp/resources.cjs +227 -0
  324. package/lib/mcp/session-catchup.cjs +327 -0
  325. package/lib/mcp/surface-detect.cjs +75 -0
  326. package/lib/mcp/tool-router.cjs +1034 -0
  327. package/lib/memory/aaak-compress.cjs +403 -0
  328. package/lib/memory/aaak-compress.test.cjs +288 -0
  329. package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
  330. package/lib/memory/bearer-token.test.cjs +315 -0
  331. package/lib/memory/brain-cache-lru.test.cjs +259 -0
  332. package/lib/memory/brain-client-query-shape.test.cjs +160 -0
  333. package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
  334. package/lib/memory/brain-derivation-queue.test.cjs +539 -0
  335. package/lib/memory/brain-derivation.test.cjs +634 -0
  336. package/lib/memory/brain-derive-command.test.cjs +534 -0
  337. package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
  338. package/lib/memory/brain-md-schema.test.cjs +467 -0
  339. package/lib/memory/brain-md-staleness.test.cjs +525 -0
  340. package/lib/memory/brain-server-resolution.test.cjs +314 -0
  341. package/lib/memory/chain-recommender.test.cjs +233 -0
  342. package/lib/memory/chat-context.test.cjs +128 -0
  343. package/lib/memory/command-registry.test.cjs +220 -0
  344. package/lib/memory/cross-room-aggregator.test.cjs +909 -0
  345. package/lib/memory/dashboard-server.test.cjs +256 -0
  346. package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
  347. package/lib/memory/decision-capture.test.cjs +632 -0
  348. package/lib/memory/decision-capture.worker.cjs +70 -0
  349. package/lib/memory/explain-decision-command.test.cjs +521 -0
  350. package/lib/memory/explain-decision-footer.test.cjs +316 -0
  351. package/lib/memory/explored-materials-store.cjs +392 -0
  352. package/lib/memory/feynman-minto-guardian.test.cjs +736 -0
  353. package/lib/memory/feynman-minto-invariants.test.cjs +511 -0
  354. package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
  355. package/lib/memory/feynman-prompts.cjs +151 -0
  356. package/lib/memory/feynman-prompts.test.cjs +96 -0
  357. package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
  358. package/lib/memory/folder-memory.test.cjs +503 -0
  359. package/lib/memory/framework-chain-composer.test.cjs +515 -0
  360. package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
  361. package/lib/memory/heal-command.test.cjs +604 -0
  362. package/lib/memory/index-artifact-transaction.test.cjs +333 -0
  363. package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
  364. package/lib/memory/mcp-input-validation.test.cjs +240 -0
  365. package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
  366. package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
  367. package/lib/memory/minto-debouncer.test.cjs +407 -0
  368. package/lib/memory/minto-debouncer.worker.cjs +46 -0
  369. package/lib/memory/minto-migration-v88.test.cjs +265 -0
  370. package/lib/memory/minto-schema-v88.test.cjs +390 -0
  371. package/lib/memory/mos-status-renderer.test.cjs +631 -0
  372. package/lib/memory/narrative-schema.cjs +376 -0
  373. package/lib/memory/narrative-schema.test.cjs +209 -0
  374. package/lib/memory/nav-dial.test.cjs +414 -0
  375. package/lib/memory/navigation-engine-core.test.cjs +722 -0
  376. package/lib/memory/navigation-invariants.test.cjs +483 -0
  377. package/lib/memory/offer-presenter.test.cjs +554 -0
  378. package/lib/memory/on-stop-snapshot.test.cjs +404 -0
  379. package/lib/memory/pending-tension-store.cjs +373 -0
  380. package/lib/memory/post-compact-reinjection.test.cjs +854 -0
  381. package/lib/memory/post-write-triple.test.cjs +317 -0
  382. package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
  383. package/lib/memory/problem-type-router.test.cjs +656 -0
  384. package/lib/memory/query-efficiency-telemetry.test.cjs +370 -0
  385. package/lib/memory/recompile-room-references.test.cjs +392 -0
  386. package/lib/memory/recompile-room-references.worker.cjs +42 -0
  387. package/lib/memory/record-decision-dual-write.test.cjs +454 -0
  388. package/lib/memory/room-classifier-strict-mode.test.cjs +417 -0
  389. package/lib/memory/room-minto-hook.test.cjs +398 -0
  390. package/lib/memory/rs-discovery-engine.test.cjs +323 -0
  391. package/lib/memory/run-feynman-tests.cjs +1247 -0
  392. package/lib/memory/security-trifecta.test.cjs +312 -0
  393. package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
  394. package/lib/memory/session-start-triple-injection.test.cjs +514 -0
  395. package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
  396. package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
  397. package/lib/memory/skill-activation-router.test.cjs +419 -0
  398. package/lib/memory/stamp-artifact-write.test.cjs +304 -0
  399. package/lib/memory/statusline-active-room.test.cjs +315 -0
  400. package/lib/memory/statusline-minto-segment.test.cjs +292 -0
  401. package/lib/memory/sync-async-entry-points.test.cjs +204 -0
  402. package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
  403. package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
  404. package/lib/memory/test-rs-brain-substrate.cjs +636 -0
  405. package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
  406. package/lib/memory/test-rs-canon-violations.cjs +218 -0
  407. package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
  408. package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
  409. package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
  410. package/lib/memory/test-rs-differential-scorer.cjs +480 -0
  411. package/lib/memory/test-rs-discovery-engine.cjs +603 -0
  412. package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
  413. package/lib/memory/test-rs-egress-primitives.cjs +420 -0
  414. package/lib/memory/test-rs-expert-mapper.cjs +547 -0
  415. package/lib/memory/test-rs-explain-command.cjs +443 -0
  416. package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
  417. package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
  418. package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
  419. package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
  420. package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
  421. package/lib/memory/test-rs-mind-map.cjs +646 -0
  422. package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
  423. package/lib/memory/test-rs-nl-to-query.cjs +449 -0
  424. package/lib/memory/test-rs-pinecone-bridge.cjs +277 -0
  425. package/lib/memory/test-rs-preprocessor.cjs +433 -0
  426. package/lib/memory/test-rs-query-matrix.cjs +391 -0
  427. package/lib/memory/test-rs-query-to-text.cjs +551 -0
  428. package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
  429. package/lib/memory/test-rs-thesis-generator.cjs +360 -0
  430. package/lib/memory/triple-context-formatter.cjs +473 -0
  431. package/lib/memory/triple-context-formatter.test.cjs +442 -0
  432. package/lib/memory/user-md-persona.test.cjs +565 -0
  433. package/lib/memory/userpromptsubmit-integration.test.cjs +690 -0
  434. package/lib/memory/validators/README.md +157 -0
  435. package/lib/memory/validators/brain-md-invariants.cjs +475 -0
  436. package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
  437. package/lib/memory/validators/external-academic-invariants.cjs +249 -0
  438. package/lib/memory/validators/external-industry-invariants.cjs +271 -0
  439. package/lib/memory/validators/external-patents-invariants.cjs +266 -0
  440. package/lib/memory/validators/minto-invariants.cjs +62 -0
  441. package/lib/memory/validators/navigation-invariants.cjs +340 -0
  442. package/lib/memory/validators/queue-health.cjs +95 -0
  443. package/lib/memory/validators/snapshot-integrity.cjs +129 -0
  444. package/lib/memory/validators/stale-lifecycle.cjs +116 -0
  445. package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
  446. package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
  447. package/lib/memory/write-lock-atomic.test.cjs +137 -0
  448. package/lib/memory/write-lock-atomic.worker.cjs +55 -0
  449. package/lib/parity/check-parity.cjs +83 -0
  450. package/lib/presentation/presentation-server.cjs +101 -0
  451. package/lib/presentation/presentation-watcher.cjs +123 -0
  452. package/lib/quickview/hub-server.cjs +719 -0
  453. package/lib/quickview/server.cjs +533 -0
  454. package/lib/render/JTBD-PALETTES.md +145 -0
  455. package/lib/render/ROOM.md +59 -0
  456. package/lib/render/render-v2.cjs +486 -0
  457. package/lib/render/render-v2.test.cjs +267 -0
  458. package/lib/render/render.cjs +65 -0
  459. package/lib/state/ROOM.md +46 -0
  460. package/lib/state/state-md-parser.cjs +215 -0
  461. package/lib/statusline/ROOM.md +38 -0
  462. package/lib/statusline/banner-suppression.cjs +50 -0
  463. package/lib/statusline/surface-detect.cjs +85 -0
  464. package/lib/update-bootstrap.sh.template +145 -0
  465. package/lib/vault/frontmatter-schema.cjs +297 -0
  466. package/lib/vault/room-scanner.cjs +352 -0
  467. package/lib/vault/wikilink-builder.cjs +231 -0
  468. package/lib/vault/wikilink-builder.test.cjs +182 -0
  469. package/lib/wiki/graph-links.cjs +281 -0
  470. package/lib/wiki/page-renderer.cjs +229 -0
  471. package/lib/wiki/wiki-chat.cjs +81 -0
  472. package/lib/wiki/wiki-layout.cjs +1459 -0
  473. package/lib/wiki/wiki-search.cjs +142 -0
  474. package/lib/wiki/wiki-server.cjs +678 -0
  475. package/lib/wiki/wiki-watcher.cjs +105 -0
  476. package/lib/workflow/ROOM.md +47 -0
  477. package/lib/workflow/command-resolver.cjs +155 -0
  478. package/lib/workflow/command-resolver.test.cjs +235 -0
  479. package/package.json +44 -0
  480. package/pipelines/analogy/01-decompose.md +80 -0
  481. package/pipelines/analogy/02-abstract.md +87 -0
  482. package/pipelines/analogy/03-search.md +135 -0
  483. package/pipelines/analogy/04-transfer.md +101 -0
  484. package/pipelines/analogy/05-validate.md +106 -0
  485. package/pipelines/analogy/CHAIN.md +56 -0
  486. package/pipelines/discovery/01-explore-domains.md +44 -0
  487. package/pipelines/discovery/02-think-hats.md +50 -0
  488. package/pipelines/discovery/03-analyze-needs.md +54 -0
  489. package/pipelines/discovery/CHAIN.md +37 -0
  490. package/pipelines/thesis/01-structure-argument.md +45 -0
  491. package/pipelines/thesis/02-challenge-assumptions.md +48 -0
  492. package/pipelines/thesis/03-build-thesis.md +54 -0
  493. package/pipelines/thesis/CHAIN.md +37 -0
  494. package/references/brain/causal-directives.md +91 -0
  495. package/references/brain/causal-enrichment.cypher +165 -0
  496. package/references/brain/command-triggers-schema.md +226 -0
  497. package/references/brain/graph-architecture.md +317 -0
  498. package/references/brain/query-patterns.md +460 -0
  499. package/references/brain/room-hierarchy-schema.md +218 -0
  500. package/references/brain/schema.md +76 -0
  501. package/references/capability-radar/capabilities-index.md +241 -0
  502. package/references/capability-radar/changelog-cache.md +81 -0
  503. package/references/causal/causal-schema.md +103 -0
  504. package/references/design/email-template-standard.md +155 -0
  505. package/references/design/graph-visualization-standard.md +178 -0
  506. package/references/document-generation.md +179 -0
  507. package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
  508. package/references/import-config.md +141 -0
  509. package/references/integrations/detection-patterns.md +101 -0
  510. package/references/meeting/artifact-template.md +377 -0
  511. package/references/meeting/cross-meeting-intelligence.md +216 -0
  512. package/references/meeting/cross-relationship-patterns.md +202 -0
  513. package/references/meeting/live-join-interface.md +244 -0
  514. package/references/meeting/section-mapping.md +192 -0
  515. package/references/meeting/segment-classification.md +258 -0
  516. package/references/meeting/speaker-profile-template.md +219 -0
  517. package/references/meeting/summary-template.md +348 -0
  518. package/references/meeting/transcript-patterns.md +226 -0
  519. package/references/methodology/analyze-needs.md +135 -0
  520. package/references/methodology/analyze-systems.md +121 -0
  521. package/references/methodology/analyze-timing.md +149 -0
  522. package/references/methodology/beautiful-question.md +109 -0
  523. package/references/methodology/build-knowledge.md +161 -0
  524. package/references/methodology/build-thesis.md +237 -0
  525. package/references/methodology/challenge-assumptions.md +127 -0
  526. package/references/methodology/diagnose.md +169 -0
  527. package/references/methodology/dominant-designs.md +212 -0
  528. package/references/methodology/explore-domains.md +147 -0
  529. package/references/methodology/explore-futures.md +163 -0
  530. package/references/methodology/explore-trends.md +129 -0
  531. package/references/methodology/find-bottlenecks.md +131 -0
  532. package/references/methodology/grade.md +211 -0
  533. package/references/methodology/index.md +97 -0
  534. package/references/methodology/leadership.md +200 -0
  535. package/references/methodology/lean-canvas.md +116 -0
  536. package/references/methodology/macro-trends.md +192 -0
  537. package/references/methodology/map-unknowns.md +137 -0
  538. package/references/methodology/mullins-7-domains.md +104 -0
  539. package/references/methodology/problem-types.md +65 -0
  540. package/references/methodology/root-cause.md +178 -0
  541. package/references/methodology/sapphire-encoding.md +355 -0
  542. package/references/methodology/scenario-plan.md +178 -0
  543. package/references/methodology/score-innovation.md +154 -0
  544. package/references/methodology/structure-argument.md +158 -0
  545. package/references/methodology/systems-thinking.md +159 -0
  546. package/references/methodology/think-hats.md +147 -0
  547. package/references/methodology/triz-matrix.json +751 -0
  548. package/references/methodology/triz-principles.md +501 -0
  549. package/references/methodology/user-needs.md +199 -0
  550. package/references/methodology/validate.md +163 -0
  551. package/references/methodology/value-proposition.md +244 -0
  552. package/references/opportunities/funding-lifecycle.md +103 -0
  553. package/references/opportunities/grant-api-patterns.md +99 -0
  554. package/references/opportunities/opportunity-template.md +84 -0
  555. package/references/personality/assessment-philosophy.md +72 -0
  556. package/references/personality/lexicon.md +100 -0
  557. package/references/personality/persona-chains.md +56 -0
  558. package/references/personality/pws-lexicon-full.md +499 -0
  559. package/references/personality/voice-dna.md +156 -0
  560. package/references/personas/hat-perspectives.md +76 -0
  561. package/references/personas/persona-template.md +63 -0
  562. package/references/pipeline/act-output-contract.md +88 -0
  563. package/references/pipeline/chains-index.md +39 -0
  564. package/references/pws-profile-generation.md +79 -0
  565. package/references/reasoning/reasoning-schema.md +143 -0
  566. package/references/reasoning/reasoning-template.md +68 -0
  567. package/references/reasoning/run-template.md +38 -0
  568. package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
  569. package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
  570. package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
  571. package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
  572. package/references/taxonomy/TAXONOMY.md +192 -0
  573. package/references/templates/MINTO.md +36 -0
  574. package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
  575. package/references/vault-kit/README.md +35 -0
  576. package/references/vault-kit/app.json +12 -0
  577. package/references/vault-kit/appearance.json +12 -0
  578. package/references/vault-kit/graph.json +35 -0
  579. package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
  580. package/references/vault-kit/templates/new-artifact.md +37 -0
  581. package/references/vault-kit/templates/new-meeting-note.md +35 -0
  582. package/references/vault-kit/templates/new-team-profile.md +29 -0
  583. package/references/vault-kit/templates/new-xref.md +35 -0
  584. package/references/visual/symbol-system.md +151 -0
  585. package/skills/MOSDeckEngine/SKILL.md +325 -0
  586. package/skills/brain-connector/SKILL.md +114 -0
  587. package/skills/context-engine/SKILL.md +147 -0
  588. package/skills/conversation-mode/SKILL.md +102 -0
  589. package/skills/larry-personality/SKILL.md +219 -0
  590. package/skills/larry-personality/framework-chains.md +92 -0
  591. package/skills/larry-personality/mode-engine.md +185 -0
  592. package/skills/mullins-scaffold/SKILL.md +61 -0
  593. package/skills/mullins-scaffold/scaffold.json +146 -0
  594. package/skills/pws-methodology/SKILL.md +49 -0
  595. package/skills/room-passive/SKILL.md +165 -0
  596. package/skills/room-proactive/SKILL.md +250 -0
  597. package/skills/ui-system/SKILL.md +277 -0
@@ -0,0 +1,736 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
4
+ *
5
+ * Phase 88-13 -- Feynman-MINTO guardian tests
6
+ * ============================================
7
+ * 16 tests: 10 core + 3 registry + 3 lifecycle validators.
8
+ *
9
+ * Core (1-10):
10
+ * 1. session-start: critical violation enqueues debouncer regen, exits 0.
11
+ * 2. session-start: all-valid rooms produce no enqueue, exit 0.
12
+ * 3. on-stop: invariant violations write .mindrian/invariant-report.json.
13
+ * 4. pre-commit: error severity exits 2, stderr carries violation text.
14
+ * 5. pre-commit: valid MINTO exits 0.
15
+ * 6. clean-tmp: stale tmp > 60s deleted, fresh tmp preserved.
16
+ * 7. clean-tmp: no tmp files -> no-op exit 0.
17
+ * 8. advisory-not-blocking at runtime: session-start + on-stop NEVER exit 2.
18
+ * 9. performance: 50-section fixture in session-start mode under 2000ms.
19
+ * 10. missing lib/core/feynman-minto-invariants.cjs (simulate via hidden
20
+ * validators dir) -> guardian exits 0, skips validation.
21
+ *
22
+ * Registry (11-13):
23
+ * 11. Registry load: minto-invariants + fixture test-warning validator
24
+ * both register; aggregated violations tagged with validator ids.
25
+ * 12. Registry fail-open: malformed validator (missing validate export)
26
+ * logged to stderr, skipped, other validators still run.
27
+ * 13. Registry id collision: two validators same id -> first wins,
28
+ * second skipped, stderr warns.
29
+ *
30
+ * Lifecycle validators (14-16):
31
+ * 14. snapshot-integrity: missing section -> warning; empty snapshot
32
+ * with sections on disk -> error; missing file -> no violation;
33
+ * full snapshot -> no violation.
34
+ * 15. queue-health: <500 entries healthy; 500-999 warning; >=1000 error
35
+ * with exactly ONE aggregate violation per invocation.
36
+ * 16. stale-lifecycle: ghost (stale.json lists valid MINTO) -> warning;
37
+ * real staleness -> no new violation; on-stop mode prunes ghosts
38
+ * atomically from stale.json.
39
+ */
40
+
41
+ 'use strict';
42
+
43
+ const assert = require('node:assert/strict');
44
+ const fs = require('node:fs');
45
+ const os = require('node:os');
46
+ const path = require('node:path');
47
+ const { spawnSync } = require('node:child_process');
48
+
49
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
50
+ const GUARDIAN = path.join(REPO_ROOT, 'scripts', 'feynman-minto-guardian.cjs');
51
+
52
+ const TMPDIRS = [];
53
+ function mkTmp(prefix) {
54
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
55
+ TMPDIRS.push(d);
56
+ return d;
57
+ }
58
+ process.on('exit', () => {
59
+ for (const d of TMPDIRS) {
60
+ try { fs.rmSync(d, { recursive: true, force: true }); } catch (_) {}
61
+ }
62
+ });
63
+
64
+ /**
65
+ * Seed a minimal room with .mindrian and zero+ sections each having ROOM.md.
66
+ * Returns { roomDir }. Set writeMintoOpts[sectionName] = { stale: bool,
67
+ * valid: bool, governing: string } to produce a MINTO.md per section.
68
+ */
69
+ function seedRoom(tmp, sections, mintoOpts) {
70
+ const roomDir = path.join(tmp, 'venture');
71
+ fs.mkdirSync(roomDir, { recursive: true });
72
+ fs.writeFileSync(path.join(roomDir, '.room-root'), '');
73
+ fs.mkdirSync(path.join(roomDir, '.mindrian'), { recursive: true });
74
+ for (const s of sections) {
75
+ const dir = path.join(roomDir, s);
76
+ fs.mkdirSync(dir, { recursive: true });
77
+ fs.writeFileSync(path.join(dir, 'ROOM.md'), '# ' + s + '\n\nIdentity.\n');
78
+ if (mintoOpts && mintoOpts[s] === 'skip') continue;
79
+ if (mintoOpts && typeof mintoOpts[s] === 'object') {
80
+ writeMinto(dir, mintoOpts[s]);
81
+ }
82
+ }
83
+ return { roomDir };
84
+ }
85
+
86
+ function writeMinto(sectionDir, opts) {
87
+ const o = opts || {};
88
+ const last = o.stale ? '1970-01-01T00:00:00Z' : new Date().toISOString();
89
+ const gt = o.governing || 'Governing thought placeholder.';
90
+ if (o.invalid === 'missing_frontmatter') {
91
+ fs.writeFileSync(path.join(sectionDir, 'MINTO.md'), '# broken\n');
92
+ return;
93
+ }
94
+ if (o.invalid === 'bad_schema') {
95
+ // Missing governing_thought + schema_version (both critical/error)
96
+ fs.writeFileSync(
97
+ path.join(sectionDir, 'MINTO.md'),
98
+ '---\nsome_field: "value"\n---\n\nbody\n'
99
+ );
100
+ return;
101
+ }
102
+ if (o.invalid === 'critical_tmp_marker') {
103
+ // Orphan FEYNMINTO_TMP marker triggers atomicity/critical per 88-00-B.
104
+ fs.writeFileSync(
105
+ path.join(sectionDir, 'MINTO.md'),
106
+ '---\nschema_version: "1.0"\ngoverning_thought: "placeholder"\n---\n# FEYNMINTO_TMP\nBody.\n'
107
+ );
108
+ return;
109
+ }
110
+ if (o.invalid === 'zero_bytes') {
111
+ // Zero-byte file triggers existence/critical per 88-00-B.
112
+ fs.writeFileSync(path.join(sectionDir, 'MINTO.md'), '');
113
+ return;
114
+ }
115
+ const frontmatter = [
116
+ '---',
117
+ 'schema_version: "1.0"',
118
+ 'governing_thought: "' + gt + '"',
119
+ 'last_generated_at: "' + last + '"',
120
+ 'last_artifact_write_seen_at: "' + last + '"',
121
+ 'reasoning_health_score: 0.75',
122
+ '---',
123
+ '',
124
+ '# Body',
125
+ '',
126
+ 'Content.',
127
+ '',
128
+ ].join('\n');
129
+ fs.writeFileSync(path.join(sectionDir, 'MINTO.md'), frontmatter);
130
+ }
131
+
132
+ function runGuardian(mode, roomDir, extraEnv, extraArgs) {
133
+ const args = [GUARDIAN, mode, roomDir];
134
+ if (Array.isArray(extraArgs)) for (const a of extraArgs) args.push(a);
135
+ return spawnSync(process.execPath, args, {
136
+ env: Object.assign({}, process.env, extraEnv || {}),
137
+ stdio: ['ignore', 'pipe', 'pipe'],
138
+ timeout: 10000,
139
+ });
140
+ }
141
+
142
+ const tests = [];
143
+ function test(name, fn) { tests.push({ name, fn }); }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Test 1: session-start critical -> enqueue regen
147
+ // ---------------------------------------------------------------------------
148
+ test('Test 1: session-start mode enqueues regen on critical violation', () => {
149
+ const tmp = mkTmp('gm-t1-');
150
+ const fx = seedRoom(tmp, ['market-analysis'], {
151
+ 'market-analysis': { invalid: 'critical_tmp_marker' },
152
+ });
153
+ const res = runGuardian('session-start', fx.roomDir);
154
+ assert.equal(res.status, 0, 'session-start exits 0; stderr=' + res.stderr);
155
+ const queuePath = path.join(fx.roomDir, '.mindrian', 'minto-queue.json');
156
+ assert.ok(fs.existsSync(queuePath), 'queue file must exist after enqueue');
157
+ const q = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
158
+ assert.ok(Array.isArray(q.entries), 'entries is array');
159
+ const sections = q.entries.map(function (e) { return e.section; });
160
+ assert.ok(
161
+ sections.indexOf('market-analysis') !== -1,
162
+ 'market-analysis enqueued; got ' + JSON.stringify(sections)
163
+ );
164
+ });
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Test 2: session-start all-valid -> no enqueue
168
+ // ---------------------------------------------------------------------------
169
+ test('Test 2: session-start with all-valid MINTOs produces no enqueue', () => {
170
+ const tmp = mkTmp('gm-t2-');
171
+ const fx = seedRoom(tmp, ['problem-definition'], {
172
+ 'problem-definition': { governing: 'All clear.' },
173
+ });
174
+ const res = runGuardian('session-start', fx.roomDir);
175
+ assert.equal(res.status, 0, 'exit 0');
176
+ const queuePath = path.join(fx.roomDir, '.mindrian', 'minto-queue.json');
177
+ if (fs.existsSync(queuePath)) {
178
+ const q = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
179
+ // queue may exist but should have zero entries for this section
180
+ const names = (q.entries || []).map(function (e) { return e.section; });
181
+ assert.ok(
182
+ names.indexOf('problem-definition') === -1,
183
+ 'problem-definition NOT enqueued; got ' + JSON.stringify(names)
184
+ );
185
+ }
186
+ });
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Test 3: on-stop -> writes invariant-report.json
190
+ // ---------------------------------------------------------------------------
191
+ test('Test 3: on-stop writes .mindrian/invariant-report.json on violations', () => {
192
+ const tmp = mkTmp('gm-t3-');
193
+ const fx = seedRoom(tmp, ['solution-design'], {
194
+ 'solution-design': { invalid: 'missing_frontmatter' },
195
+ });
196
+ const res = runGuardian('on-stop', fx.roomDir);
197
+ assert.equal(res.status, 0, 'on-stop exits 0 (advisory)');
198
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
199
+ assert.ok(
200
+ fs.existsSync(reportPath),
201
+ 'invariant-report.json exists after on-stop'
202
+ );
203
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
204
+ assert.equal(report.version, 1);
205
+ assert.equal(report.kind, 'on-stop');
206
+ assert.ok(report.sections && typeof report.sections === 'object');
207
+ assert.ok(
208
+ report.sections['solution-design'],
209
+ 'solution-design entry in report'
210
+ );
211
+ assert.ok(
212
+ Array.isArray(report.sections['solution-design'].violations) &&
213
+ report.sections['solution-design'].violations.length > 0,
214
+ 'violations non-empty'
215
+ );
216
+ });
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Test 4: pre-commit error severity -> exit 2
220
+ // ---------------------------------------------------------------------------
221
+ test('Test 4: pre-commit blocks on error/critical severity, exit 2', () => {
222
+ const tmp = mkTmp('gm-t4-');
223
+ const fx = seedRoom(tmp, ['market-analysis'], {
224
+ 'market-analysis': { invalid: 'bad_schema' },
225
+ });
226
+ // pre-commit reads `git diff --cached --name-only`. We simulate by setting
227
+ // GUARDIAN_PRECOMMIT_STAGED env var (fallback used when no git). Guardian
228
+ // must honor this env for testability -- see spec step 5 (pre-commit).
229
+ const res = runGuardian('pre-commit', fx.roomDir, {
230
+ GUARDIAN_PRECOMMIT_STAGED: 'market-analysis/artifact-01.md',
231
+ });
232
+ assert.equal(res.status, 2, 'pre-commit exits 2 on violation; stderr=' + res.stderr);
233
+ const stderr = (res.stderr || '').toString();
234
+ assert.ok(
235
+ stderr.length > 0,
236
+ 'stderr carries violation message (got empty)'
237
+ );
238
+ });
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Test 5: pre-commit valid MINTO -> exit 0
242
+ // ---------------------------------------------------------------------------
243
+ test('Test 5: pre-commit exits 0 when MINTO is valid', () => {
244
+ const tmp = mkTmp('gm-t5-');
245
+ const fx = seedRoom(tmp, ['market-analysis'], {
246
+ 'market-analysis': { governing: 'Clean.' },
247
+ });
248
+ const res = runGuardian('pre-commit', fx.roomDir, {
249
+ GUARDIAN_PRECOMMIT_STAGED: 'market-analysis/artifact-01.md',
250
+ });
251
+ assert.equal(res.status, 0, 'pre-commit exits 0; stderr=' + res.stderr);
252
+ });
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // Test 6: clean-tmp removes stale > 60s tmp files, preserves fresh ones
256
+ // ---------------------------------------------------------------------------
257
+ test('Test 6: clean-tmp removes stale tmp > 60s, preserves fresh tmp', () => {
258
+ const tmp = mkTmp('gm-t6-');
259
+ const fx = seedRoom(tmp, ['competitive-analysis'], {
260
+ 'competitive-analysis': { governing: 'Competitive.' },
261
+ });
262
+ const secDir = path.join(fx.roomDir, 'competitive-analysis');
263
+ const stale = path.join(secDir, 'MINTO.md.tmp.12345.minto');
264
+ const fresh = path.join(secDir, 'MINTO.md.tmp.67890.minto');
265
+ fs.writeFileSync(stale, 'stale content');
266
+ fs.writeFileSync(fresh, 'fresh content');
267
+ // Backdate stale to 120s ago
268
+ const past = Date.now() - 120 * 1000;
269
+ fs.utimesSync(stale, past / 1000, past / 1000);
270
+ // fresh stays now
271
+ const res = runGuardian('clean-tmp', fx.roomDir);
272
+ assert.equal(res.status, 0, 'clean-tmp exits 0');
273
+ assert.ok(!fs.existsSync(stale), 'stale tmp deleted');
274
+ assert.ok(fs.existsSync(fresh), 'fresh tmp preserved');
275
+ });
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Test 7: clean-tmp no tmp files -> exit 0 no-op
279
+ // ---------------------------------------------------------------------------
280
+ test('Test 7: clean-tmp no-op when no tmp files present', () => {
281
+ const tmp = mkTmp('gm-t7-');
282
+ const fx = seedRoom(tmp, ['team-execution'], {
283
+ 'team-execution': { governing: 'Team clean.' },
284
+ });
285
+ const res = runGuardian('clean-tmp', fx.roomDir);
286
+ assert.equal(res.status, 0, 'clean-tmp no-op exits 0');
287
+ });
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // Test 8: advisory-not-blocking at runtime (session-start + on-stop never 2)
291
+ // ---------------------------------------------------------------------------
292
+ test('Test 8: session-start + on-stop never exit 2 on critical violations', () => {
293
+ const tmp = mkTmp('gm-t8-');
294
+ const fx = seedRoom(tmp, ['a', 'b', 'c'], {
295
+ a: { invalid: 'bad_schema' },
296
+ b: { invalid: 'missing_frontmatter' },
297
+ c: { invalid: 'missing_frontmatter' },
298
+ });
299
+ const r1 = runGuardian('session-start', fx.roomDir);
300
+ assert.ok(r1.status === 0, 'session-start exits 0 despite criticals; got ' + r1.status);
301
+ const r2 = runGuardian('on-stop', fx.roomDir);
302
+ assert.ok(r2.status === 0, 'on-stop exits 0 despite criticals; got ' + r2.status);
303
+ });
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Test 9: 50-section fixture session-start under 2000ms
307
+ // ---------------------------------------------------------------------------
308
+ test('Test 9: 50-section fixture session-start completes under 2000ms', () => {
309
+ const tmp = mkTmp('gm-t9-');
310
+ const sections = [];
311
+ const opts = {};
312
+ for (let i = 0; i < 50; i++) {
313
+ const s = 'sec-' + String(i).padStart(2, '0');
314
+ sections.push(s);
315
+ opts[s] = { governing: 'All good ' + i + '.' };
316
+ }
317
+ const fx = seedRoom(tmp, sections, opts);
318
+ const t0 = Date.now();
319
+ const res = runGuardian('session-start', fx.roomDir);
320
+ const elapsed = Date.now() - t0;
321
+ assert.equal(res.status, 0, 'exit 0');
322
+ assert.ok(
323
+ elapsed < 2000,
324
+ '50-section session-start under 2000ms; got ' + elapsed + 'ms'
325
+ );
326
+ });
327
+
328
+ // ---------------------------------------------------------------------------
329
+ // Test 10: missing invariants module (simulated via override env) -> exit 0
330
+ // ---------------------------------------------------------------------------
331
+ test('Test 10: missing invariants module: guardian logs + exits 0', () => {
332
+ const tmp = mkTmp('gm-t10-');
333
+ const fx = seedRoom(tmp, ['problem-definition'], {
334
+ 'problem-definition': { governing: 'Clean.' },
335
+ });
336
+ // Point validator registry at an empty directory so minto-invariants does
337
+ // not load. Guardian should still exit 0 (no validators = no violations).
338
+ const emptyDir = path.join(tmp, 'empty-validators');
339
+ fs.mkdirSync(emptyDir);
340
+ const res = runGuardian('session-start', fx.roomDir, {
341
+ GUARDIAN_VALIDATORS_DIR: emptyDir,
342
+ });
343
+ assert.equal(res.status, 0, 'guardian exits 0 when invariants absent');
344
+ });
345
+
346
+ // ---------------------------------------------------------------------------
347
+ // Test 11: registry loads seed validators + fixture test-warning
348
+ // ---------------------------------------------------------------------------
349
+ test('Test 11: registry aggregates violations from multiple validators', () => {
350
+ const tmp = mkTmp('gm-t11-');
351
+ const fx = seedRoom(tmp, ['market-analysis'], {
352
+ 'market-analysis': { invalid: 'bad_schema' },
353
+ });
354
+ // Create a fixture validators dir containing the real minto-invariants
355
+ // plus a test-warning validator. Guardian aggregates both.
356
+ const vdir = path.join(tmp, 'validators');
357
+ fs.mkdirSync(vdir);
358
+ // Seed validator 1: copy minto-invariants.cjs content (point at absolute
359
+ // invariants path so the copy can resolve it).
360
+ const MIV = path.join(REPO_ROOT, 'lib', 'memory', 'validators', 'minto-invariants.cjs');
361
+ // Test assumes guardian ships minto-invariants.cjs in the real validators
362
+ // dir. We load it here via absolute path so the test fixture uses the
363
+ // same code.
364
+ fs.writeFileSync(
365
+ path.join(vdir, '01-minto-invariants.cjs'),
366
+ "module.exports = require(" + JSON.stringify(MIV) + ");\n"
367
+ );
368
+ fs.writeFileSync(
369
+ path.join(vdir, '02-test-warning.cjs'),
370
+ "'use strict';\n" +
371
+ "module.exports = {\n" +
372
+ " id: 'test-warning',\n" +
373
+ " severity_map: { critical:3, error:2, warning:1, info:0 },\n" +
374
+ " validate: function(sectionDir) {\n" +
375
+ " return { severity: 'warning', violations: [{\n" +
376
+ " validator: 'test-warning',\n" +
377
+ " category: 'test',\n" +
378
+ " severity: 'warning',\n" +
379
+ " message: 'synthetic test warning for section ' + require('path').basename(sectionDir)\n" +
380
+ " }] };\n" +
381
+ " }\n" +
382
+ "};\n"
383
+ );
384
+ const res = runGuardian('on-stop', fx.roomDir, {
385
+ GUARDIAN_VALIDATORS_DIR: vdir,
386
+ });
387
+ assert.equal(res.status, 0, 'on-stop exits 0');
388
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
389
+ assert.ok(fs.existsSync(reportPath), 'report exists');
390
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
391
+ const viol = report.sections['market-analysis'].violations;
392
+ const ids = viol.map(function (v) { return v.validator; });
393
+ assert.ok(
394
+ ids.indexOf('minto-invariants') !== -1,
395
+ 'minto-invariants present; got ' + JSON.stringify(ids)
396
+ );
397
+ assert.ok(
398
+ ids.indexOf('test-warning') !== -1,
399
+ 'test-warning present; got ' + JSON.stringify(ids)
400
+ );
401
+ });
402
+
403
+ // ---------------------------------------------------------------------------
404
+ // Test 12: registry fail-open on malformed validator
405
+ // ---------------------------------------------------------------------------
406
+ test('Test 12: malformed validator logged + skipped, guardian exits 0', () => {
407
+ const tmp = mkTmp('gm-t12-');
408
+ const fx = seedRoom(tmp, ['market-analysis'], {
409
+ 'market-analysis': { governing: 'Clean.' },
410
+ });
411
+ const vdir = path.join(tmp, 'validators');
412
+ fs.mkdirSync(vdir);
413
+ const MIV = path.join(REPO_ROOT, 'lib', 'memory', 'validators', 'minto-invariants.cjs');
414
+ fs.writeFileSync(
415
+ path.join(vdir, '01-minto-invariants.cjs'),
416
+ "module.exports = require(" + JSON.stringify(MIV) + ");\n"
417
+ );
418
+ // Missing validate export
419
+ fs.writeFileSync(
420
+ path.join(vdir, '02-bad-missing-validate.cjs'),
421
+ "module.exports = { id: 'bad-missing-validate', severity_map: {} };\n"
422
+ );
423
+ // Throws at require time
424
+ fs.writeFileSync(
425
+ path.join(vdir, '03-bad-throws-at-require.cjs'),
426
+ "throw new Error('boom at require time');\n"
427
+ );
428
+ const res = runGuardian('session-start', fx.roomDir, {
429
+ GUARDIAN_VALIDATORS_DIR: vdir,
430
+ });
431
+ assert.equal(res.status, 0, 'exits 0 despite malformed validators');
432
+ const stderr = (res.stderr || '').toString();
433
+ assert.ok(
434
+ stderr.indexOf('bad-missing-validate') !== -1 ||
435
+ stderr.indexOf('02-bad-missing-validate') !== -1,
436
+ 'stderr warns about missing validate; got: ' + stderr
437
+ );
438
+ assert.ok(
439
+ stderr.indexOf('bad-throws-at-require') !== -1 ||
440
+ stderr.indexOf('03-bad-throws-at-require') !== -1,
441
+ 'stderr warns about require-time throw; got: ' + stderr
442
+ );
443
+ });
444
+
445
+ // ---------------------------------------------------------------------------
446
+ // Test 13: id collision -> first wins, second skipped, stderr warns
447
+ // ---------------------------------------------------------------------------
448
+ test('Test 13: id collision: first-loaded wins, duplicate skipped + warned', () => {
449
+ const tmp = mkTmp('gm-t13-');
450
+ const fx = seedRoom(tmp, ['market-analysis'], {
451
+ 'market-analysis': { governing: 'Clean.' },
452
+ });
453
+ const vdir = path.join(tmp, 'validators');
454
+ fs.mkdirSync(vdir);
455
+ fs.writeFileSync(
456
+ path.join(vdir, '01-a.cjs'),
457
+ "module.exports = { id: 'dup', severity_map: {}, validate: function(){ return {severity:null, violations:[]}; } };\n"
458
+ );
459
+ fs.writeFileSync(
460
+ path.join(vdir, '02-b.cjs'),
461
+ "module.exports = { id: 'dup', severity_map: {}, validate: function(){ return {severity:null, violations:[]}; } };\n"
462
+ );
463
+ const res = runGuardian('session-start', fx.roomDir, {
464
+ GUARDIAN_VALIDATORS_DIR: vdir,
465
+ });
466
+ assert.equal(res.status, 0, 'exits 0 despite id collision');
467
+ const stderr = (res.stderr || '').toString();
468
+ assert.ok(
469
+ stderr.indexOf('dup') !== -1 || stderr.indexOf('collision') !== -1,
470
+ 'stderr warns about id collision; got: ' + stderr
471
+ );
472
+ });
473
+
474
+ // ---------------------------------------------------------------------------
475
+ // Test 14: snapshot-integrity validator (partial + empty + healthy + missing)
476
+ // ---------------------------------------------------------------------------
477
+ test('Test 14: snapshot-integrity detects partial/empty/healthy/missing', () => {
478
+ // Test 14a: missing snapshot -> no violation
479
+ {
480
+ const tmp = mkTmp('gm-t14a-');
481
+ const fx = seedRoom(tmp, ['a'], { a: { governing: 'clean' } });
482
+ const res = runGuardian('on-stop', fx.roomDir);
483
+ assert.equal(res.status, 0);
484
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
485
+ // Missing snapshot file is healthy first-session; no violations from
486
+ // snapshot-integrity. Report may or may not exist depending on other
487
+ // validators' output (likely none for a clean MINTO).
488
+ if (fs.existsSync(reportPath)) {
489
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
490
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
491
+ const snap = all.filter(function (v) { return v.validator === 'snapshot-integrity'; });
492
+ assert.equal(snap.length, 0, 'no snapshot-integrity violations for missing snapshot');
493
+ }
494
+ }
495
+ // Test 14b: partial snapshot -> warning per missing section
496
+ {
497
+ const tmp = mkTmp('gm-t14b-');
498
+ const fx = seedRoom(tmp, ['a', 'b', 'c'], {
499
+ a: { governing: 'a' }, b: { governing: 'b' }, c: { governing: 'c' },
500
+ });
501
+ const snap = {
502
+ version: 1,
503
+ sections: { a: {}, b: {} }, // c missing
504
+ };
505
+ fs.writeFileSync(
506
+ path.join(fx.roomDir, '.mindrian', 'session-snapshot.json'),
507
+ JSON.stringify(snap)
508
+ );
509
+ const res = runGuardian('on-stop', fx.roomDir);
510
+ assert.equal(res.status, 0);
511
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
512
+ assert.ok(fs.existsSync(reportPath), 'report written');
513
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
514
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
515
+ const snapV = all.filter(function (v) { return v.validator === 'snapshot-integrity'; });
516
+ assert.ok(
517
+ snapV.length >= 1,
518
+ 'at least one snapshot-integrity violation; got ' + snapV.length
519
+ );
520
+ assert.ok(
521
+ snapV.some(function (v) { return v.action_hint === 'partial_snapshot_detected'; }),
522
+ 'action_hint includes partial_snapshot_detected'
523
+ );
524
+ }
525
+ // Test 14c: empty snapshot with non-empty room -> error
526
+ {
527
+ const tmp = mkTmp('gm-t14c-');
528
+ const fx = seedRoom(tmp, ['a', 'b'], {
529
+ a: { governing: 'a' }, b: { governing: 'b' },
530
+ });
531
+ fs.writeFileSync(
532
+ path.join(fx.roomDir, '.mindrian', 'session-snapshot.json'),
533
+ JSON.stringify({ version: 1, sections: {} })
534
+ );
535
+ const res = runGuardian('on-stop', fx.roomDir);
536
+ assert.equal(res.status, 0);
537
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
538
+ assert.ok(fs.existsSync(reportPath));
539
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
540
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
541
+ const errs = all.filter(function (v) {
542
+ return v.validator === 'snapshot-integrity' && v.severity === 'error';
543
+ });
544
+ assert.ok(errs.length >= 1, 'error-severity snapshot violation emitted');
545
+ }
546
+ // Test 14d: full snapshot -> no snapshot-integrity violation
547
+ {
548
+ const tmp = mkTmp('gm-t14d-');
549
+ const fx = seedRoom(tmp, ['a', 'b'], {
550
+ a: { governing: 'a' }, b: { governing: 'b' },
551
+ });
552
+ fs.writeFileSync(
553
+ path.join(fx.roomDir, '.mindrian', 'session-snapshot.json'),
554
+ JSON.stringify({ version: 1, sections: { a: {}, b: {} } })
555
+ );
556
+ const res = runGuardian('on-stop', fx.roomDir);
557
+ assert.equal(res.status, 0);
558
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
559
+ if (fs.existsSync(reportPath)) {
560
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
561
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
562
+ const snap = all.filter(function (v) { return v.validator === 'snapshot-integrity'; });
563
+ assert.equal(snap.length, 0, 'no snapshot-integrity violation on full snapshot');
564
+ }
565
+ }
566
+ });
567
+
568
+ // ---------------------------------------------------------------------------
569
+ // Test 15: queue-health ceilings
570
+ // ---------------------------------------------------------------------------
571
+ test('Test 15: queue-health: under/500/1001 threshold emits single violation', () => {
572
+ function makeEntries(n) {
573
+ const out = [];
574
+ for (let i = 0; i < n; i++) {
575
+ out.push({
576
+ section: 'section-' + i,
577
+ enqueued_at: new Date().toISOString(),
578
+ reason: 'test',
579
+ attempts: 0,
580
+ });
581
+ }
582
+ return out;
583
+ }
584
+ function getQueueHealthViolations(fx) {
585
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
586
+ if (!fs.existsSync(reportPath)) return [];
587
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
588
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
589
+ return all.filter(function (v) { return v.validator === 'queue-health'; });
590
+ }
591
+ // Under 500 -> healthy
592
+ {
593
+ const tmp = mkTmp('gm-t15a-');
594
+ const fx = seedRoom(tmp, ['a'], { a: { governing: 'a' } });
595
+ fs.writeFileSync(
596
+ path.join(fx.roomDir, '.mindrian', 'minto-queue.json'),
597
+ JSON.stringify({ version: 1, entries: makeEntries(10) })
598
+ );
599
+ const res = runGuardian('on-stop', fx.roomDir);
600
+ assert.equal(res.status, 0);
601
+ const v = getQueueHealthViolations(fx);
602
+ assert.equal(v.length, 0, 'under 500: no queue-health violation');
603
+ }
604
+ // 500 -> warning
605
+ {
606
+ const tmp = mkTmp('gm-t15b-');
607
+ const fx = seedRoom(tmp, ['a'], { a: { governing: 'a' } });
608
+ fs.writeFileSync(
609
+ path.join(fx.roomDir, '.mindrian', 'minto-queue.json'),
610
+ JSON.stringify({ version: 1, entries: makeEntries(500) })
611
+ );
612
+ const res = runGuardian('on-stop', fx.roomDir);
613
+ assert.equal(res.status, 0);
614
+ const v = getQueueHealthViolations(fx);
615
+ assert.equal(v.length, 1, 'exactly one aggregate violation at 500');
616
+ assert.equal(v[0].severity, 'warning');
617
+ }
618
+ // 1001 -> error with action_hint halt_enqueue_until_drained
619
+ {
620
+ const tmp = mkTmp('gm-t15c-');
621
+ const fx = seedRoom(tmp, ['a'], { a: { governing: 'a' } });
622
+ fs.writeFileSync(
623
+ path.join(fx.roomDir, '.mindrian', 'minto-queue.json'),
624
+ JSON.stringify({ version: 1, entries: makeEntries(1001) })
625
+ );
626
+ const res = runGuardian('on-stop', fx.roomDir);
627
+ assert.equal(res.status, 0);
628
+ const v = getQueueHealthViolations(fx);
629
+ assert.equal(v.length, 1, 'exactly one aggregate violation at 1001');
630
+ assert.equal(v[0].severity, 'error');
631
+ assert.equal(v[0].action_hint, 'halt_enqueue_until_drained');
632
+ }
633
+ });
634
+
635
+ // ---------------------------------------------------------------------------
636
+ // Test 16: stale-lifecycle ghost pruning
637
+ // ---------------------------------------------------------------------------
638
+ test('Test 16: stale-lifecycle flags ghosts + on-stop prunes stale.json', () => {
639
+ // Test 16a: MINTO valid but listed as stale -> ghost warning
640
+ {
641
+ const tmp = mkTmp('gm-t16a-');
642
+ const fx = seedRoom(tmp, ['market-analysis'], {
643
+ 'market-analysis': { governing: 'Valid now.' },
644
+ });
645
+ fs.writeFileSync(
646
+ path.join(fx.roomDir, '.mindrian', 'minto-stale.json'),
647
+ JSON.stringify({
648
+ version: 1,
649
+ at: new Date().toISOString(),
650
+ // Use invariant_violation as reason -- that is an invariants-owned
651
+ // reason, so the stale-lifecycle validator re-validates and detects
652
+ // the ghost. Reasons owned by folder-memory (never_generated,
653
+ // missing_timestamps, artifacts_newer_than_minto) are intentionally
654
+ // NOT candidates for ghost detection via re-validation, see the
655
+ // validator source.
656
+ sections: [{ section: 'market-analysis', reason: 'invariant_violation' }],
657
+ })
658
+ );
659
+ const res = runGuardian('on-stop', fx.roomDir);
660
+ assert.equal(res.status, 0);
661
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
662
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
663
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
664
+ const sl = all.filter(function (v) { return v.validator === 'stale-lifecycle'; });
665
+ assert.ok(sl.length >= 1, 'stale-lifecycle emits ghost warning');
666
+ assert.ok(
667
+ sl.some(function (v) { return v.category === 'stale_ghost'; }),
668
+ 'category is stale_ghost'
669
+ );
670
+ // After on-stop run, stale.json should have market-analysis pruned
671
+ const stalePath = path.join(fx.roomDir, '.mindrian', 'minto-stale.json');
672
+ const s = JSON.parse(fs.readFileSync(stalePath, 'utf8'));
673
+ const sections = (s.sections || []).map(function (e) { return e.section; });
674
+ assert.ok(
675
+ sections.indexOf('market-analysis') === -1,
676
+ 'ghost section pruned from stale.json; remaining=' + JSON.stringify(sections)
677
+ );
678
+ }
679
+ // Test 16b: MINTO actually stale -> no new violation (real staleness)
680
+ {
681
+ const tmp = mkTmp('gm-t16b-');
682
+ const fx = seedRoom(tmp, ['x'], {
683
+ 'x': { invalid: 'missing_frontmatter' }, // actually invalid
684
+ });
685
+ fs.writeFileSync(
686
+ path.join(fx.roomDir, '.mindrian', 'minto-stale.json'),
687
+ JSON.stringify({
688
+ version: 1,
689
+ at: new Date().toISOString(),
690
+ sections: [{ section: 'x', reason: 'parse_failed' }],
691
+ })
692
+ );
693
+ const res = runGuardian('on-stop', fx.roomDir);
694
+ assert.equal(res.status, 0);
695
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
696
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
697
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
698
+ const sl = all.filter(function (v) {
699
+ return v.validator === 'stale-lifecycle' && v.category === 'stale_ghost';
700
+ });
701
+ assert.equal(sl.length, 0, 'no ghost violation when MINTO really is invalid');
702
+ }
703
+ // Test 16c: missing stale.json -> no violation
704
+ {
705
+ const tmp = mkTmp('gm-t16c-');
706
+ const fx = seedRoom(tmp, ['a'], { a: { governing: 'a' } });
707
+ const res = runGuardian('on-stop', fx.roomDir);
708
+ assert.equal(res.status, 0);
709
+ const reportPath = path.join(fx.roomDir, '.mindrian', 'invariant-report.json');
710
+ if (fs.existsSync(reportPath)) {
711
+ const r = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
712
+ const all = Object.values(r.sections || {}).flatMap(function (s) { return s.violations || []; });
713
+ const sl = all.filter(function (v) { return v.validator === 'stale-lifecycle'; });
714
+ assert.equal(sl.length, 0, 'no stale-lifecycle violation when stale.json missing');
715
+ }
716
+ }
717
+ });
718
+
719
+ // ---------------------------------------------------------------------------
720
+ // Runner
721
+ // ---------------------------------------------------------------------------
722
+
723
+ let failed = 0;
724
+ for (const t of tests) {
725
+ try {
726
+ t.fn();
727
+ process.stdout.write('PASS ' + t.name + '\n');
728
+ } catch (e) {
729
+ failed += 1;
730
+ process.stderr.write('FAIL ' + t.name + '\n ' + (e && e.stack || e) + '\n');
731
+ }
732
+ }
733
+ process.stdout.write(
734
+ '\nguardian tests: ' + (tests.length - failed) + '/' + tests.length + ' passed\n'
735
+ );
736
+ process.exit(failed === 0 ? 0 : 1);