@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,312 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Security Trifecta Tests (Phase 87-01)
5
+ * ======================================
6
+ *
7
+ * BSL 1.1. Copyright (c) Mindrian 2026.
8
+ *
9
+ * Proves SEC-01 + SEC-02 + SEC-03 defences in brain-client.cjs and
10
+ * intelligence-cascade.cjs. Runs inside the feynman harness.
11
+ *
12
+ * SEC-01: sanitizeCypherInput() strips every non-whitelist character before
13
+ * a user-origin string reaches a Cypher interpolation site.
14
+ * SEC-02: checkFilePermissions() refuses .env files with any group/world bit
15
+ * set (mode & 0o077 != 0). Must allow 0600, reject 0644.
16
+ * SEC-03: intelligence-cascade.cjs uses HSI_TIMEOUT_MS = 30000 at every
17
+ * HSI-path spawn site in the shared `_runCascadeSteps` helper
18
+ * (classify, check-deps, compute-hsi, detect-reverse-salients,
19
+ * hsi-to-graph, compute-state). After Phase 87-03 cascade deduplication
20
+ * this is 6 unique sites inside the single helper (down from 12
21
+ * duplicated sites across runCascade + queueCascade pre-refactor).
22
+ * The invariant that matters is structural: zero `timeout: 5000`
23
+ * sites remain; every HSI-path spawn reads HSI_TIMEOUT_MS; the
24
+ * 1 intentional `timeout: 15000` site (presentation) stays intact.
25
+ *
26
+ * Exit 0 on pass, 1 on any failure.
27
+ * Run: node lib/memory/security-trifecta.test.cjs
28
+ */
29
+
30
+ const assert = require('node:assert/strict');
31
+ const fs = require('node:fs');
32
+ const os = require('node:os');
33
+ const path = require('node:path');
34
+
35
+ const brainClient = require('../core/brain-client.cjs');
36
+ const { sanitizeCypherInput, checkFilePermissions } = brainClient._test || {};
37
+
38
+ let passed = 0;
39
+ let failed = 0;
40
+
41
+ function test(name, fn) {
42
+ try {
43
+ fn();
44
+ process.stdout.write(' ok ' + name + '\n');
45
+ passed++;
46
+ } catch (err) {
47
+ process.stdout.write(' FAIL ' + name + '\n');
48
+ process.stdout.write(' ' + (err.message || err) + '\n');
49
+ failed++;
50
+ }
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // SEC-01: sanitizeCypherInput
55
+ // ---------------------------------------------------------------------------
56
+
57
+ test('sanitizeCypherInput exported via module.exports._test', () => {
58
+ assert.equal(typeof sanitizeCypherInput, 'function');
59
+ });
60
+
61
+ test('sanitizeCypherInput passes clean alphanumerics through', () => {
62
+ assert.equal(sanitizeCypherInput('hello'), 'hello');
63
+ assert.equal(sanitizeCypherInput('Framework 42'), 'Framework 42');
64
+ });
65
+
66
+ test('sanitizeCypherInput strips Cypher injection payload', () => {
67
+ // Classic injection attempt: quotes, semicolons, comment, backtick,
68
+ // newline, curly braces, dollar sign, backslash. Spaces are in the
69
+ // whitelist (CONTEXT.md: [a-zA-Z0-9 ._-]) so they survive; the danger
70
+ // characters are the ones that must go.
71
+ const payload = 'DROP TABLE; --"`\n${var}';
72
+ const result = sanitizeCypherInput(payload);
73
+ assert.equal(result, 'DROP TABLE --var');
74
+ assert.ok(!result.includes(';'), 'semicolon must be stripped');
75
+ assert.ok(!result.includes('"'), 'double quote must be stripped');
76
+ assert.ok(!result.includes('`'), 'backtick must be stripped');
77
+ assert.ok(!result.includes('\n'), 'newline must be stripped');
78
+ assert.ok(!result.includes('$'), 'dollar sign must be stripped');
79
+ assert.ok(!result.includes('{'), 'open brace must be stripped');
80
+ });
81
+
82
+ test('sanitizeCypherInput strips every forbidden metachar', () => {
83
+ const hostile = 'a"b\'c`d{e}f$g\\h';
84
+ const out = sanitizeCypherInput(hostile);
85
+ assert.ok(!out.includes('"'), 'double quote survived');
86
+ assert.ok(!out.includes("'"), 'single quote survived');
87
+ assert.ok(!out.includes('`'), 'backtick survived');
88
+ assert.ok(!out.includes('{'), 'open brace survived');
89
+ assert.ok(!out.includes('}'), 'close brace survived');
90
+ assert.ok(!out.includes('$'), 'dollar sign survived');
91
+ assert.ok(!out.includes('\\'), 'backslash survived');
92
+ });
93
+
94
+ test('sanitizeCypherInput preserves whitelist: letters, digits, space, dot, underscore, dash', () => {
95
+ const ok = 'Name-with_underscore.and-dash 42';
96
+ assert.equal(sanitizeCypherInput(ok), ok);
97
+ });
98
+
99
+ test('sanitizeCypherInput handles null/undefined/non-string without crashing', () => {
100
+ assert.equal(sanitizeCypherInput(null), '');
101
+ assert.equal(sanitizeCypherInput(undefined), '');
102
+ assert.equal(sanitizeCypherInput(42), '42');
103
+ assert.equal(sanitizeCypherInput(false), 'false');
104
+ });
105
+
106
+ test('sanitizeCypherInput newline stripped (no Cypher comment injection)', () => {
107
+ const out = sanitizeCypherInput('abc\n// injected\ndef');
108
+ assert.ok(!out.includes('\n'));
109
+ assert.ok(!out.includes('/'));
110
+ });
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // SEC-02: checkFilePermissions
114
+ // ---------------------------------------------------------------------------
115
+
116
+ test('checkFilePermissions exported via module.exports._test', () => {
117
+ assert.equal(typeof checkFilePermissions, 'function');
118
+ });
119
+
120
+ // Linux/macOS-only semantics. On Windows the helper returns true (documented).
121
+ const isWin = process.platform === 'win32';
122
+
123
+ test('checkFilePermissions accepts 0600', () => {
124
+ if (isWin) {
125
+ process.stdout.write(' (skipped on win32)\n');
126
+ return;
127
+ }
128
+ const tmpFile = path.join(os.tmpdir(), `sec-trifecta-0600-${Date.now()}.env`);
129
+ fs.writeFileSync(tmpFile, 'MINDRIAN_BRAIN_KEY=test\n');
130
+ fs.chmodSync(tmpFile, 0o600);
131
+ try {
132
+ assert.equal(checkFilePermissions(tmpFile), true);
133
+ } finally {
134
+ try { fs.unlinkSync(tmpFile); } catch (_e) {}
135
+ }
136
+ });
137
+
138
+ test('checkFilePermissions rejects 0644 (group-readable)', () => {
139
+ if (isWin) {
140
+ process.stdout.write(' (skipped on win32)\n');
141
+ return;
142
+ }
143
+ const tmpFile = path.join(os.tmpdir(), `sec-trifecta-0644-${Date.now()}.env`);
144
+ fs.writeFileSync(tmpFile, 'MINDRIAN_BRAIN_KEY=test\n');
145
+ fs.chmodSync(tmpFile, 0o644);
146
+ try {
147
+ assert.equal(checkFilePermissions(tmpFile), false);
148
+ } finally {
149
+ try { fs.unlinkSync(tmpFile); } catch (_e) {}
150
+ }
151
+ });
152
+
153
+ test('checkFilePermissions rejects 0664 (group-writable too)', () => {
154
+ if (isWin) {
155
+ process.stdout.write(' (skipped on win32)\n');
156
+ return;
157
+ }
158
+ const tmpFile = path.join(os.tmpdir(), `sec-trifecta-0664-${Date.now()}.env`);
159
+ fs.writeFileSync(tmpFile, 'MINDRIAN_BRAIN_KEY=test\n');
160
+ fs.chmodSync(tmpFile, 0o664);
161
+ try {
162
+ assert.equal(checkFilePermissions(tmpFile), false);
163
+ } finally {
164
+ try { fs.unlinkSync(tmpFile); } catch (_e) {}
165
+ }
166
+ });
167
+
168
+ test('checkFilePermissions accepts 0400 (read-only owner)', () => {
169
+ if (isWin) {
170
+ process.stdout.write(' (skipped on win32)\n');
171
+ return;
172
+ }
173
+ const tmpFile = path.join(os.tmpdir(), `sec-trifecta-0400-${Date.now()}.env`);
174
+ fs.writeFileSync(tmpFile, 'MINDRIAN_BRAIN_KEY=test\n');
175
+ fs.chmodSync(tmpFile, 0o400);
176
+ try {
177
+ assert.equal(checkFilePermissions(tmpFile), true);
178
+ } finally {
179
+ try {
180
+ fs.chmodSync(tmpFile, 0o600);
181
+ fs.unlinkSync(tmpFile);
182
+ } catch (_e) {}
183
+ }
184
+ });
185
+
186
+ test('checkFilePermissions returns false for nonexistent file', () => {
187
+ const missing = path.join(os.tmpdir(), `sec-trifecta-missing-${Date.now()}.env`);
188
+ assert.equal(checkFilePermissions(missing), false);
189
+ });
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // SEC-03: HSI_TIMEOUT_MS is 30000 at every HSI-path cascade spawn site.
193
+ // Phase 87-03 cascade deduplication: the 12 duplicated sites collapsed to 6
194
+ // unique sites inside the shared _runCascadeSteps helper. The semantic
195
+ // invariant is preserved: zero timeout:5000 sites remain, every HSI-path
196
+ // spawn reads HSI_TIMEOUT_MS, the 1 intentional 15000ms site is intact.
197
+ // ---------------------------------------------------------------------------
198
+
199
+ test('intelligence-cascade.cjs defines HSI_TIMEOUT_MS = 30000', () => {
200
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
201
+ const src = fs.readFileSync(cascadePath, 'utf8');
202
+ assert.match(
203
+ src,
204
+ /const\s+HSI_TIMEOUT_MS\s*=\s*30000/,
205
+ 'HSI_TIMEOUT_MS = 30000 constant not found'
206
+ );
207
+ });
208
+
209
+ test('intelligence-cascade.cjs uses HSI_TIMEOUT_MS at exactly 6 unique spawn sites (post 87-03 dedup)', () => {
210
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
211
+ const src = fs.readFileSync(cascadePath, 'utf8');
212
+ const matches = src.match(/timeout:\s*HSI_TIMEOUT_MS/g) || [];
213
+ assert.equal(
214
+ matches.length,
215
+ 6,
216
+ `expected 6 timeout: HSI_TIMEOUT_MS sites after cascade dedup, got ${matches.length}`
217
+ );
218
+ });
219
+
220
+ test('intelligence-cascade.cjs has zero remaining timeout: 5000 sites', () => {
221
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
222
+ const src = fs.readFileSync(cascadePath, 'utf8');
223
+ const matches = src.match(/timeout:\s*5000/g) || [];
224
+ assert.equal(
225
+ matches.length,
226
+ 0,
227
+ `expected 0 remaining timeout: 5000 sites, got ${matches.length}`
228
+ );
229
+ });
230
+
231
+ test('intelligence-cascade.cjs preserves 1 intentional timeout: 15000 site (post 87-03 dedup)', () => {
232
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
233
+ const src = fs.readFileSync(cascadePath, 'utf8');
234
+ const matches = src.match(/timeout:\s*15000/g) || [];
235
+ assert.equal(
236
+ matches.length,
237
+ 1,
238
+ `expected 1 preserved timeout: 15000 site after cascade dedup, got ${matches.length}`
239
+ );
240
+ });
241
+
242
+ // Phase 87-03 structural invariants: the shared helper exists and is called
243
+ // by both public entry points.
244
+ test('intelligence-cascade.cjs defines _runCascadeSteps shared helper (87-03)', () => {
245
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
246
+ const src = fs.readFileSync(cascadePath, 'utf8');
247
+ const defMatches = src.match(/async function _runCascadeSteps/g) || [];
248
+ assert.equal(
249
+ defMatches.length,
250
+ 1,
251
+ `expected 1 _runCascadeSteps definition, got ${defMatches.length}`
252
+ );
253
+ });
254
+
255
+ test('intelligence-cascade.cjs: runCascade + queueCascade both call _runCascadeSteps (87-03)', () => {
256
+ const cascadePath = path.resolve(__dirname, '..', 'core', 'intelligence-cascade.cjs');
257
+ const src = fs.readFileSync(cascadePath, 'utf8');
258
+ // 1 definition + 2 call sites = at least 3 occurrences of "_runCascadeSteps("
259
+ const matches = src.match(/_runCascadeSteps\(/g) || [];
260
+ assert.ok(
261
+ matches.length >= 3,
262
+ `expected >= 3 _runCascadeSteps occurrences (1 def + 2 callers), got ${matches.length}`
263
+ );
264
+ });
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Integration cross-check: brain-client.cjs uses sanitizeCypherInput at 8 sites
268
+ // and zero legacy .replace(/"/g, '\\"') patterns remain.
269
+ // ---------------------------------------------------------------------------
270
+
271
+ test('brain-client.cjs calls sanitizeCypherInput at 8 sites (plus definition)', () => {
272
+ const brainPath = path.resolve(__dirname, '..', 'core', 'brain-client.cjs');
273
+ const src = fs.readFileSync(brainPath, 'utf8');
274
+ const matches = src.match(/sanitizeCypherInput\s*\(/g) || [];
275
+ // 1 definition + 8 call sites = 9 (+1 _test export ref = 10).
276
+ assert.ok(
277
+ matches.length >= 9,
278
+ `expected >= 9 sanitizeCypherInput occurrences, got ${matches.length}`
279
+ );
280
+ });
281
+
282
+ test('brain-client.cjs has zero legacy .replace(/"/g, ...) injection patterns', () => {
283
+ const brainPath = path.resolve(__dirname, '..', 'core', 'brain-client.cjs');
284
+ const src = fs.readFileSync(brainPath, 'utf8');
285
+ const matches = src.match(/\.replace\(\/"\/g/g) || [];
286
+ assert.equal(
287
+ matches.length,
288
+ 0,
289
+ `expected 0 legacy double-quote escape patterns, got ${matches.length}`
290
+ );
291
+ });
292
+
293
+ test('brain-client.cjs guards .env reads with checkFilePermissions', () => {
294
+ const brainPath = path.resolve(__dirname, '..', 'core', 'brain-client.cjs');
295
+ const src = fs.readFileSync(brainPath, 'utf8');
296
+ const matches = src.match(/checkFilePermissions\s*\(/g) || [];
297
+ // 1 definition + 2 getApiKey call sites (cwd .env + ~/.mindrian.env) = 3.
298
+ assert.ok(
299
+ matches.length >= 3,
300
+ `expected >= 3 checkFilePermissions occurrences, got ${matches.length}`
301
+ );
302
+ });
303
+
304
+ // ---------------------------------------------------------------------------
305
+
306
+ process.stdout.write('\n');
307
+ process.stdout.write(`${passed} passed, ${failed} failed\n`);
308
+ if (failed > 0) {
309
+ process.stdout.write('FAIL\n');
310
+ process.exit(1);
311
+ }
312
+ process.stdout.write('PASS\n');
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
6
+ *
7
+ * Phase 90-03 Task 2 -- session-start BRAIN staleness injection tests
8
+ * ====================================================================
9
+ * Integration tests that spawn real bash scripts/session-start with
10
+ * BRAIN.md fixtures, verifying that:
11
+ * - Brain annotations surface in TRIPLE_CONTEXT per section
12
+ * - Stale sections enqueue for regen (brain-derivation-queue.json)
13
+ * - Brain-offline suppresses enqueue (hold for when back online)
14
+ * - BRAIN_STALENESS_SKIP=1 restores pre-90-03 byte-identical output
15
+ *
16
+ * Harness: mirrors lib/memory/session-start-triple-injection.test.cjs.
17
+ * We seed a MindrianRooms fixture via MINDRIAN_ROOMS_HOME env + basename
18
+ * match, spawn bash session-start, and parse additionalContext.
19
+ *
20
+ * Brain-client availability is controlled via MINDRIAN_BRAIN_KEY env:
21
+ * - unset -> brain-client.isAvailable() === false (offline)
22
+ * - set to any non-empty string -> isAvailable() === true
23
+ * (See lib/core/brain-client.cjs lines 121-155 for the contract.)
24
+ *
25
+ * Pure Node built-ins. Three-surface: same hook fires on CLI, Desktop
26
+ * MCP lifecycle emits equivalent.
27
+ */
28
+
29
+ const assert = require('node:assert/strict');
30
+ const fs = require('node:fs');
31
+ const os = require('node:os');
32
+ const path = require('node:path');
33
+ const crypto = require('node:crypto');
34
+ const { spawnSync } = require('node:child_process');
35
+
36
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
37
+ const SESSION_START = path.join(REPO_ROOT, 'scripts', 'session-start');
38
+
39
+ // ---------- Tmp workspace helpers ----------
40
+
41
+ const TMPDIRS = [];
42
+ function mkTmp(prefix) {
43
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
44
+ TMPDIRS.push(d);
45
+ return d;
46
+ }
47
+ process.on('exit', function () {
48
+ for (const d of TMPDIRS) {
49
+ try { fs.rmSync(d, { recursive: true, force: true }); } catch (_) {}
50
+ }
51
+ });
52
+
53
+ function sha256Hex(s) {
54
+ return 'sha256:' + crypto.createHash('sha256')
55
+ .update(String(s == null ? '' : s).normalize('NFC'))
56
+ .digest('hex');
57
+ }
58
+
59
+ /**
60
+ * Seed a Strategy-0b compatible MindrianRooms fixture.
61
+ */
62
+ function seedRoom(tmp, sections) {
63
+ const roomsHome = path.join(tmp, 'rooms-home');
64
+ const roomBase = 'venture';
65
+ const roomDir = path.join(roomsHome, roomBase);
66
+ fs.mkdirSync(roomDir, { recursive: true });
67
+ fs.writeFileSync(path.join(roomDir, '.room-root'), '');
68
+ fs.mkdirSync(path.join(roomDir, '.mindrian'), { recursive: true });
69
+ const workDir = path.join(tmp, 'work', roomBase);
70
+ fs.mkdirSync(workDir, { recursive: true });
71
+ for (const s of sections) {
72
+ const dir = path.join(roomDir, s);
73
+ fs.mkdirSync(dir, { recursive: true });
74
+ fs.writeFileSync(
75
+ path.join(dir, 'ROOM.md'),
76
+ '# ' + s + '\n\nIdentity text for ' + s + '.\n'
77
+ );
78
+ }
79
+ return { roomDir, roomsHome, workDir };
80
+ }
81
+
82
+ function writeMinto(sectionDir, governingThought) {
83
+ const now = new Date().toISOString();
84
+ const body = [
85
+ '---',
86
+ 'schema_version: 88',
87
+ 'governing_thought: "' + governingThought + '"',
88
+ 'last_generated_at: "' + now + '"',
89
+ 'last_artifact_write_seen_at: "' + now + '"',
90
+ 'reasoning_health_score: 0.75',
91
+ 'flagged_weaknesses: []',
92
+ 'decision_log: []',
93
+ '---',
94
+ '',
95
+ '# Governing Thought',
96
+ '',
97
+ governingThought,
98
+ '',
99
+ '# Claim 1',
100
+ '',
101
+ 'Supporting claim.',
102
+ '',
103
+ ].join('\n');
104
+ fs.writeFileSync(path.join(sectionDir, 'MINTO.md'), body);
105
+ }
106
+
107
+ function writeBrainMd(sectionDir, opts) {
108
+ opts = opts || {};
109
+ const gt = opts.governing_thought == null ? 'default governing thought' : opts.governing_thought;
110
+ const gtHash = opts.governing_thought_hash != null ? opts.governing_thought_hash : sha256Hex(gt);
111
+ const now = opts.brain_generated_at != null ? opts.brain_generated_at : new Date().toISOString();
112
+ const bgv = opts.brain_graph_version != null ? opts.brain_graph_version : 1;
113
+ const staleness = opts.staleness || 'fresh';
114
+ const section = opts.section || path.basename(sectionDir);
115
+ const body = [
116
+ '---',
117
+ 'section: ' + section,
118
+ 'brain_generated_at: "' + now + '"',
119
+ 'brain_graph_version: ' + bgv,
120
+ 'governing_thought_hash: "' + gtHash + '"',
121
+ 'staleness: ' + staleness,
122
+ 'author: brain',
123
+ '---',
124
+ '',
125
+ '## Pattern Matches',
126
+ '',
127
+ '(no signal)',
128
+ '',
129
+ ].join('\n');
130
+ fs.writeFileSync(path.join(sectionDir, 'BRAIN.md'), body);
131
+ }
132
+
133
+ function invokeSessionStart(fx, extraEnv) {
134
+ // Override HOME to a harmless path so brain-client.getApiKey() does NOT
135
+ // find a real ~/.mindrian.env file and leak availability into the test.
136
+ // (getApiKey falls back to <HOME>/.mindrian.env; pointing HOME at a tmp
137
+ // dir guarantees that fallback is empty.)
138
+ const safeHome = path.join(fx.workDir, '_home');
139
+ try { fs.mkdirSync(safeHome, { recursive: true }); } catch (_e) {}
140
+ const env = Object.assign({}, process.env, {
141
+ PWD: fx.workDir,
142
+ HOME: safeHome,
143
+ MINDRIAN_ROOMS_HOME: fx.roomsHome,
144
+ MINDRIAN_ROOMS_ROOT: fx.roomsHome,
145
+ CLAUDE_PLUGIN_ROOT: REPO_ROOT,
146
+ SESSION_START_NODE_PREFLIGHT_SKIP: '1',
147
+ }, extraEnv || {});
148
+ // Explicit handling: if caller set empty string, remove from env so
149
+ // process.env.MINDRIAN_BRAIN_KEY is undefined (not "") in the spawned
150
+ // node subprocess. Node treats empty-string env vars as defined.
151
+ if (env.MINDRIAN_BRAIN_KEY === '') delete env.MINDRIAN_BRAIN_KEY;
152
+ return spawnSync('bash', [SESSION_START], {
153
+ cwd: fx.workDir,
154
+ env,
155
+ stdio: ['ignore', 'pipe', 'pipe'],
156
+ timeout: 15000,
157
+ });
158
+ }
159
+
160
+ function extractAdditionalContext(stdout) {
161
+ const raw = String(stdout || '');
162
+ let m = /"additionalContext":\s*"([\s\S]*?)(?<!\\)"/m.exec(raw);
163
+ if (m) return m[1];
164
+ m = /"additional_context":\s*"([\s\S]*?)(?<!\\)"/m.exec(raw);
165
+ if (m) return m[1];
166
+ return '';
167
+ }
168
+
169
+ // ---------- Test runner ----------
170
+
171
+ const tests = [];
172
+ function test(name, fn) { tests.push({ name, fn }); }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Test 1: Mixed fixture -- 1 fresh, 1 stale (hash mismatch), 1 absent.
176
+ // With Brain online, session-start produces TRIPLE_CONTEXT containing
177
+ // Brain annotation lines for fresh + stale (absent suppressed when no
178
+ // BRAIN.md exists for that section; or explicit "absent" line).
179
+ // ---------------------------------------------------------------------------
180
+ test('Test 1: 3-section mix (fresh / stale / absent) -> Brain annotations in TRIPLE_CONTEXT', () => {
181
+ const tmp = mkTmp('ss-90-03-t1-');
182
+ const fx = seedRoom(tmp, ['market-analysis', 'problem-definition', 'business-model']);
183
+
184
+ // market-analysis: fresh MINTO + fresh BRAIN (hash match, recent)
185
+ writeMinto(path.join(fx.roomDir, 'market-analysis'), 'TAM analysis stable');
186
+ writeBrainMd(path.join(fx.roomDir, 'market-analysis'), {
187
+ governing_thought: 'TAM analysis stable',
188
+ brain_graph_version: 1,
189
+ brain_generated_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
190
+ });
191
+
192
+ // problem-definition: stale BRAIN (hash mismatch -- MINTO changed)
193
+ writeMinto(path.join(fx.roomDir, 'problem-definition'), 'NEW problem framing');
194
+ writeBrainMd(path.join(fx.roomDir, 'problem-definition'), {
195
+ governing_thought: 'OLD problem framing',
196
+ brain_graph_version: 1,
197
+ brain_generated_at: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
198
+ });
199
+
200
+ // business-model: MINTO exists, no BRAIN.md
201
+ writeMinto(path.join(fx.roomDir, 'business-model'), 'subscription model');
202
+
203
+ // Simulate Brain online (any non-empty API key).
204
+ const res = invokeSessionStart(fx, { MINDRIAN_BRAIN_KEY: 'test-key-online' });
205
+ assert.equal(res.status, 0, 'session-start exits 0: ' + (res.stderr || ''));
206
+
207
+ const ctx = extractAdditionalContext(String(res.stdout));
208
+ assert.ok(ctx.length > 0, 'additional_context non-empty');
209
+ assert.ok(ctx.indexOf('ACTIVE ROOM MEMORY') !== -1, 'TRIPLE_CONTEXT header present');
210
+ // Fresh annotation
211
+ assert.ok(
212
+ /Brain derivation: fresh/.test(ctx),
213
+ 'at least one "Brain derivation: fresh" line expected'
214
+ );
215
+ // Stale annotation with governing_thought changed reason
216
+ assert.ok(
217
+ /Brain derivation: stale.*governing_thought/.test(ctx),
218
+ 'stale (governing_thought changed) annotation present'
219
+ );
220
+ });
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Test 2: Brain offline -- stale sections marked, NO enqueue fires.
224
+ // ---------------------------------------------------------------------------
225
+ test('Test 2: Brain offline -> stale annotations present, queue NOT populated', () => {
226
+ const tmp = mkTmp('ss-90-03-t2-');
227
+ const fx = seedRoom(tmp, ['market-analysis']);
228
+
229
+ writeMinto(path.join(fx.roomDir, 'market-analysis'), 'NEW thought');
230
+ writeBrainMd(path.join(fx.roomDir, 'market-analysis'), {
231
+ governing_thought: 'OLD thought',
232
+ brain_graph_version: 1,
233
+ brain_generated_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
234
+ });
235
+
236
+ // Brain offline: remove any API key env.
237
+ const res = invokeSessionStart(fx, { MINDRIAN_BRAIN_KEY: '' });
238
+ assert.equal(res.status, 0, 'session-start exits 0: ' + (res.stderr || ''));
239
+ const ctx = extractAdditionalContext(String(res.stdout));
240
+
241
+ // Stale annotation should surface even when offline.
242
+ assert.ok(
243
+ /Brain derivation: stale/.test(ctx),
244
+ 'stale annotation present even offline'
245
+ );
246
+
247
+ // Queue file should either not exist OR have zero entries (Brain offline
248
+ // -> recommended_action=enqueue_when_brain_online -> enqueue skipped).
249
+ const queuePath = path.join(fx.roomDir, '.mindrian', 'brain-derivation-queue.json');
250
+ if (fs.existsSync(queuePath)) {
251
+ const q = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
252
+ assert.equal(q.entries.length, 0,
253
+ 'Brain offline -> queue must stay empty (got ' + q.entries.length + ' entries)');
254
+ }
255
+ });
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Test 3: Brain online + stale -> enqueue fires; wall-clock < 5000ms.
259
+ // ---------------------------------------------------------------------------
260
+ test('Test 3: Brain online + stale -> enqueue lands, session-start < 5000ms', () => {
261
+ const tmp = mkTmp('ss-90-03-t3-');
262
+ const fx = seedRoom(tmp, ['market-analysis']);
263
+
264
+ writeMinto(path.join(fx.roomDir, 'market-analysis'), 'NEW thought');
265
+ writeBrainMd(path.join(fx.roomDir, 'market-analysis'), {
266
+ governing_thought: 'OLD thought',
267
+ brain_graph_version: 1,
268
+ brain_generated_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
269
+ });
270
+
271
+ // Warm up node cold start (WSL2 fs).
272
+ invokeSessionStart(fx, { MINDRIAN_BRAIN_KEY: 'warmup' });
273
+
274
+ // Clear any enqueue from warmup.
275
+ const queuePath = path.join(fx.roomDir, '.mindrian', 'brain-derivation-queue.json');
276
+ if (fs.existsSync(queuePath)) {
277
+ fs.writeFileSync(queuePath, JSON.stringify({ version: 1, entries: [] }));
278
+ }
279
+
280
+ const t0 = Date.now();
281
+ const res = invokeSessionStart(fx, { MINDRIAN_BRAIN_KEY: 'test-key-online' });
282
+ const elapsed = Date.now() - t0;
283
+ assert.equal(res.status, 0, 'exit 0');
284
+ assert.ok(elapsed < 5000, 'session-start wall-clock ' + elapsed + 'ms (ceiling 5000)');
285
+
286
+ // Queue should have the stale section enqueued.
287
+ assert.ok(fs.existsSync(queuePath), 'queue file should exist after Brain-online run');
288
+ const q = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
289
+ assert.ok(q.entries.length >= 1, 'queue should have at least one entry (got ' + q.entries.length + ')');
290
+ const sections = q.entries.map(function (e) { return e.section; });
291
+ assert.ok(sections.indexOf('market-analysis') !== -1, 'market-analysis section enqueued');
292
+ });
293
+
294
+ // ---------------------------------------------------------------------------
295
+ // Test 4: BRAIN_STALENESS_SKIP=1 feature flag -> Brain annotations suppressed.
296
+ // ---------------------------------------------------------------------------
297
+ test('Test 4: BRAIN_STALENESS_SKIP=1 -> no Brain lines in TRIPLE_CONTEXT', () => {
298
+ const tmp = mkTmp('ss-90-03-t4-');
299
+ const fx = seedRoom(tmp, ['market-analysis']);
300
+
301
+ writeMinto(path.join(fx.roomDir, 'market-analysis'), 'Active thought');
302
+ writeBrainMd(path.join(fx.roomDir, 'market-analysis'), {
303
+ governing_thought: 'Active thought',
304
+ brain_graph_version: 1,
305
+ brain_generated_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
306
+ });
307
+
308
+ const res = invokeSessionStart(fx, {
309
+ MINDRIAN_BRAIN_KEY: 'test-key-online',
310
+ BRAIN_STALENESS_SKIP: '1',
311
+ });
312
+ assert.equal(res.status, 0, 'exit 0');
313
+ const ctx = extractAdditionalContext(String(res.stdout));
314
+ assert.ok(ctx.indexOf('ACTIVE ROOM MEMORY') !== -1, 'TRIPLE_CONTEXT still emitted');
315
+ assert.ok(
316
+ ctx.indexOf('Brain derivation:') === -1,
317
+ 'Brain derivation lines SUPPRESSED when BRAIN_STALENESS_SKIP=1'
318
+ );
319
+ });
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // Test 5: Backward-compat -- room with NO BRAIN.md files anywhere does not
323
+ // emit "Brain derivation: absent" lines (keeps output byte-stable for
324
+ // rooms that have never run a derivation).
325
+ // ---------------------------------------------------------------------------
326
+ test('Test 5: No BRAIN.md anywhere -> no "Brain derivation" lines (backward-compat)', () => {
327
+ const tmp = mkTmp('ss-90-03-t5-');
328
+ const fx = seedRoom(tmp, ['market-analysis', 'problem-definition']);
329
+ writeMinto(path.join(fx.roomDir, 'market-analysis'), 'Thought 1');
330
+ writeMinto(path.join(fx.roomDir, 'problem-definition'), 'Thought 2');
331
+ // NO BRAIN.md files.
332
+
333
+ const res = invokeSessionStart(fx, { MINDRIAN_BRAIN_KEY: 'test-key-online' });
334
+ assert.equal(res.status, 0, 'exit 0');
335
+ const ctx = extractAdditionalContext(String(res.stdout));
336
+ assert.ok(ctx.indexOf('ACTIVE ROOM MEMORY') !== -1, 'TRIPLE_CONTEXT emitted');
337
+ assert.ok(
338
+ ctx.indexOf('Brain derivation:') === -1,
339
+ 'no Brain lines when room has zero BRAIN.md (backward-compat)'
340
+ );
341
+ });
342
+
343
+ // ---------------------------------------------------------------------------
344
+ // Runner
345
+ // ---------------------------------------------------------------------------
346
+
347
+ let passed = 0;
348
+ let failed = 0;
349
+
350
+ for (const t of tests) {
351
+ try {
352
+ t.fn();
353
+ passed++;
354
+ process.stdout.write(' pass: ' + t.name + '\n');
355
+ } catch (err) {
356
+ failed++;
357
+ process.stderr.write(' FAIL: ' + t.name + '\n');
358
+ process.stderr.write(' ' + (err && err.stack ? err.stack : err) + '\n');
359
+ }
360
+ }
361
+
362
+ process.stdout.write('\nsession-start-brain-staleness: ' + passed + '/' + (passed + failed) + ' passed\n');
363
+ if (failed > 0) process.exit(1);