@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,909 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
6
+ *
7
+ * Phase 90-06 -- cross-room-aggregator fixture tests (18 cases)
8
+ * =============================================================
9
+ * Tests the riskiest Canon Part 8 surface in Phase 90: cross-room
10
+ * contradiction aggregation across multiple Data Rooms.
11
+ *
12
+ * The test suite builds tmpdir fixtures simulating ~/MindrianRooms/
13
+ * layouts with .rooms/registry.json pointing at 2-5 rooms per fixture.
14
+ * Each test isolates one risk surface (ALLOWED_ROOT scope, GUARDRAIL.md
15
+ * sealing, per-room brain_cross_room:false opt-out, unreadable paths,
16
+ * registry absence/malformation, hash divergence detection, wall-clock
17
+ * budget, and the LOAD-BEARING Canon Part 8 payload audits Tests 16-18
18
+ * that prove NO user content leaks into aggregator output under
19
+ * adversarial dangerous-content fixtures).
20
+ *
21
+ * Test map:
22
+ * 01: 3-room fixture, 0 sealed, contradictions detected
23
+ * 02: sealed room (GUARDRAIL.md) -> skipped
24
+ * 03: opt-out room (brain_cross_room:false) -> skipped
25
+ * 04: registry.json missing -> graceful fallback, no throw
26
+ * 05: registry.json malformed -> graceful fallback + stderr warn
27
+ * 06: room path outside ALLOWED_ROOT -> skipped as out_of_scope
28
+ * 07: self-exclusion (currentRoom slug matches registry entry)
29
+ * 08: hash_divergence contradiction type
30
+ * 09: framework_contradiction contradiction type
31
+ * 10: problem_type_mismatch contradiction type
32
+ * 11: no contradictions -> empty contradictions[], non-zero scanned
33
+ * 12: max_rooms:2 limit -> 2 scanned, rest skipped as max_rooms_exceeded
34
+ * 13: per_room_timeout_ms triggers skip_reasons.timeout
35
+ * 14: unreadable room dir (chmod 0) -> skip_reasons.unreadable
36
+ * 15: wall-clock <500ms for 10-room fixture
37
+ * 16: CANON PART 8 payload audit -- JSON.stringify(result) contains
38
+ * ZERO forbidden substrings (email, currency, quoted-person,
39
+ * meeting fragment, SSN, phone)
40
+ * 17: CANON PART 8 detail_scalar shape -- every contradiction's
41
+ * detail_scalar is {primitive-only}; no string > 40 chars; no
42
+ * string matches forbidden regex set
43
+ * 18: deriveSection integration with cross_room_scan:true populates
44
+ * the 'Flagged Contradictions (cross-room)' section with
45
+ * slug-based entries; body contains NO user content under
46
+ * dangerous-content fixture
47
+ *
48
+ * Registered in lib/memory/run-feynman-tests.cjs.
49
+ */
50
+
51
+ const assert = require('node:assert/strict');
52
+ const fs = require('node:fs');
53
+ const os = require('node:os');
54
+ const path = require('node:path');
55
+ const crypto = require('node:crypto');
56
+
57
+ const REPO = path.resolve(__dirname, '..', '..');
58
+ const AGGREGATOR_PATH = path.join(REPO, 'lib/core/cross-room-aggregator.cjs');
59
+
60
+ // ---------- Tmp workspace helpers ----------
61
+
62
+ const TMP_ROOTS = [];
63
+ function mkTmp(prefix) {
64
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
65
+ TMP_ROOTS.push(d);
66
+ return d;
67
+ }
68
+ process.on('exit', function cleanup() {
69
+ for (const d of TMP_ROOTS) {
70
+ try {
71
+ // Reset perms in case a test chmod-0ed something
72
+ try { walkChmod(d, 0o755); } catch (_e) { /* best effort */ }
73
+ fs.rmSync(d, { recursive: true, force: true });
74
+ } catch (_e) { /* best effort */ }
75
+ }
76
+ });
77
+
78
+ function walkChmod(root, mode) {
79
+ try {
80
+ fs.chmodSync(root, mode);
81
+ const entries = fs.readdirSync(root);
82
+ for (const name of entries) {
83
+ const abs = path.join(root, name);
84
+ let st;
85
+ try { st = fs.statSync(abs); } catch (_e) { continue; }
86
+ if (st.isDirectory()) walkChmod(abs, mode);
87
+ else {
88
+ try { fs.chmodSync(abs, mode); } catch (_e) { /* best effort */ }
89
+ }
90
+ }
91
+ } catch (_e) { /* best effort */ }
92
+ }
93
+
94
+ // ---------- Fixture writers ----------
95
+
96
+ function writeFile(p, body) {
97
+ fs.mkdirSync(path.dirname(p), { recursive: true });
98
+ fs.writeFileSync(p, body, 'utf8');
99
+ }
100
+
101
+ function writeRoomMd(sectionDir, opts) {
102
+ const o = opts || {};
103
+ const fm = ['---'];
104
+ if (o.brain_cross_room === false) {
105
+ fm.push('brain_cross_room: false');
106
+ }
107
+ fm.push('section: ' + (o.section || 'market-analysis'));
108
+ fm.push('---');
109
+ fm.push('');
110
+ fm.push((o.identity || '# Section'));
111
+ writeFile(path.join(sectionDir, 'ROOM.md'), fm.join('\n') + '\n');
112
+ }
113
+
114
+ function writeMintoMd(sectionDir, opts) {
115
+ const o = opts || {};
116
+ const gt = o.governing_thought || '';
117
+ const fm = [
118
+ '---',
119
+ 'schema_version: v88',
120
+ 'governing_thought: "' + gt.replace(/"/g, "'") + '"',
121
+ 'last_generated_at: "' + (o.last_generated_at || '2026-04-20T10:00:00Z') + '"',
122
+ 'last_artifact_write_seen_at: "' + (o.last_artifact_write_seen_at || '2026-04-20T09:00:00Z') + '"',
123
+ 'arguments_count: ' + (o.arguments_count != null ? o.arguments_count : 3),
124
+ 'evidence_density: ' + (o.evidence_density != null ? o.evidence_density : 0.4),
125
+ 'mece_status: ' + (o.mece_status || 'pass'),
126
+ '---',
127
+ '',
128
+ '# ' + (o.section || 'market-analysis'),
129
+ '',
130
+ 'Governing Thought: ' + gt,
131
+ '',
132
+ ];
133
+ writeFile(path.join(sectionDir, 'MINTO.md'), fm.join('\n'));
134
+ }
135
+
136
+ function writeBrainMd(sectionDir, opts) {
137
+ const o = opts || {};
138
+ const gtHash = o.governing_thought_hash ||
139
+ ('sha256:' + crypto.createHash('sha256').update(o.governing_thought || '').digest('hex'));
140
+ const fm = [
141
+ '---',
142
+ 'section: ' + (o.section || 'market-analysis'),
143
+ 'brain_generated_at: "' + (o.brain_generated_at || '2026-04-20T11:00:00Z') + '"',
144
+ 'brain_graph_version: ' + (o.brain_graph_version != null ? o.brain_graph_version : 1),
145
+ 'governing_thought_hash: "' + gtHash + '"',
146
+ 'staleness: ' + (o.staleness || 'fresh'),
147
+ 'stale_reason: null',
148
+ 'author: brain',
149
+ 'problem_type: ' + (o.problem_type || 'IDP'),
150
+ 'prompt_version: 1',
151
+ 'cost_tokens: 100',
152
+ 'brain_query_count: 5',
153
+ '---',
154
+ '',
155
+ '## Pattern Matches',
156
+ o.pattern_matches || '(no signal)',
157
+ '',
158
+ '## Cross-Domain Analogies',
159
+ '(no signal)',
160
+ '',
161
+ '## Wicked Indicators',
162
+ '(no signal)',
163
+ '',
164
+ '## Unfilled Opportunity Matches',
165
+ '(no signal)',
166
+ '',
167
+ '## Framework Chain Predictions',
168
+ o.framework_chain || '(no signal)',
169
+ '',
170
+ '## Assessment Thinking Chain Position',
171
+ '(no signal)',
172
+ '',
173
+ '## ProblemType Classification',
174
+ o.problem_type_body || '(no signal)',
175
+ '',
176
+ '## Flagged Contradictions (cross-room)',
177
+ '(no signal)',
178
+ '',
179
+ '## HSI Signals',
180
+ '(no signal)',
181
+ '',
182
+ ];
183
+ writeFile(path.join(sectionDir, 'BRAIN.md'), fm.join('\n'));
184
+ }
185
+
186
+ /**
187
+ * Build a room under allowedRoot with N sections.
188
+ * sectionsSpec is an array of { name, gt, problem_type, framework_chain,
189
+ * sealed, opted_out, dangerous_content } entries.
190
+ */
191
+ function buildRoom(allowedRoot, roomSlug, sectionsSpec, opts) {
192
+ const roomDir = path.join(allowedRoot, roomSlug);
193
+ fs.mkdirSync(roomDir, { recursive: true });
194
+
195
+ opts = opts || {};
196
+ if (opts.sealed) {
197
+ writeFile(path.join(roomDir, 'GUARDRAIL.md'), [
198
+ '---',
199
+ 'isolation: STRICT',
200
+ '---',
201
+ '',
202
+ '# SEALED ROOM',
203
+ '',
204
+ ].join('\n'));
205
+ }
206
+
207
+ // Create a top-level room ROOM.md for opt-out flag (if specified)
208
+ // Opt-out: brain_cross_room:false in ROOM.md frontmatter
209
+ writeRoomMd(roomDir, {
210
+ section: roomSlug,
211
+ brain_cross_room: opts.opted_out ? false : undefined,
212
+ identity: opts.identity || '# Room ' + roomSlug,
213
+ });
214
+
215
+ for (const spec of sectionsSpec) {
216
+ const sectionDir = path.join(roomDir, spec.name);
217
+ const dangerous = spec.dangerous_content || '';
218
+ writeRoomMd(sectionDir, {
219
+ section: spec.name,
220
+ identity: '# ' + spec.name + '\n\n' + dangerous,
221
+ });
222
+ writeMintoMd(sectionDir, {
223
+ section: spec.name,
224
+ governing_thought: spec.gt || (spec.name + ' governing thought'),
225
+ arguments_count: spec.arguments_count || 3,
226
+ evidence_density: spec.evidence_density != null ? spec.evidence_density : 0.4,
227
+ mece_status: spec.mece_status || 'pass',
228
+ });
229
+ writeBrainMd(sectionDir, {
230
+ section: spec.name,
231
+ governing_thought: spec.gt,
232
+ governing_thought_hash: spec.governing_thought_hash,
233
+ problem_type: spec.problem_type || 'IDP',
234
+ pattern_matches: spec.pattern_matches,
235
+ framework_chain: spec.framework_chain,
236
+ problem_type_body: spec.problem_type_body,
237
+ });
238
+ }
239
+
240
+ return roomDir;
241
+ }
242
+
243
+ function writeRegistry(allowedRoot, rooms, active) {
244
+ const dotRooms = path.join(allowedRoot, '.rooms');
245
+ fs.mkdirSync(dotRooms, { recursive: true });
246
+ const payload = {
247
+ version: 2,
248
+ root: allowedRoot,
249
+ active: active || (rooms[0] && rooms[0].slug) || '',
250
+ rooms: {},
251
+ };
252
+ for (const r of rooms) {
253
+ payload.rooms[r.slug] = {
254
+ path: r.path || r.slug,
255
+ venture_name: r.venture_name || r.slug,
256
+ venture_stage: r.venture_stage || 'Design',
257
+ status: r.status || 'active',
258
+ created: r.created || '2026-04-20T00:00:00Z',
259
+ last_opened: r.last_opened || '2026-04-21T00:00:00Z',
260
+ };
261
+ }
262
+ fs.writeFileSync(path.join(dotRooms, 'registry.json'), JSON.stringify(payload, null, 2));
263
+ return payload;
264
+ }
265
+
266
+ // Override ALLOWED_ROOT for tests via env
267
+ function makeFixtureRoot(prefix) {
268
+ const root = mkTmp(prefix);
269
+ return root;
270
+ }
271
+
272
+ // ---------- Forbidden content fixture catalog ----------
273
+ //
274
+ // Dangerous content strings that must NEVER appear in aggregator output.
275
+ // Test 16-18 stringify the result and grep against these patterns.
276
+
277
+ const DANGEROUS_CONTENT = Object.freeze([
278
+ 'Lawrence said we should raise $5M next quarter',
279
+ 'meeting with jonathan@mindrian.com scheduled',
280
+ 'Dror revealed SSN 123-45-6789 during diligence',
281
+ '$5M revenue target confirmed by Nimrod',
282
+ 'call Oren at +1-555-123-4567',
283
+ 'Jonathan said the market is soft',
284
+ ]);
285
+
286
+ const FORBIDDEN_PATTERNS = Object.freeze([
287
+ /@[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}/,
288
+ /\$[0-9][\d,.]*[KMB]?\b/,
289
+ /\b(Lawrence|Jonathan|Nimrod|Oren|Dror)\s+(said|revealed|told|mentioned)\b/i,
290
+ /\bmeeting with\b/i,
291
+ /\b\d{3}-\d{2}-\d{4}\b/,
292
+ /\b\+?1?[\s.\-]?\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}\b/,
293
+ ]);
294
+
295
+ function containsForbidden(text) {
296
+ if (typeof text !== 'string') return null;
297
+ for (const re of FORBIDDEN_PATTERNS) {
298
+ if (re.test(text)) return re.source;
299
+ }
300
+ return null;
301
+ }
302
+
303
+ // ---------- Require aggregator with ALLOWED_ROOT override ----------
304
+
305
+ function loadAggregator(allowedRoot) {
306
+ // Aggregator reads ALLOWED_ROOT from process.env.MINDRIAN_ROOMS_ROOT if set
307
+ delete require.cache[require.resolve(AGGREGATOR_PATH)];
308
+ process.env.MINDRIAN_ROOMS_ROOT = allowedRoot;
309
+ return require(AGGREGATOR_PATH);
310
+ }
311
+
312
+ // ---------- Tests ----------
313
+
314
+ let passed = 0;
315
+ let total = 0;
316
+
317
+ function run(name, fn) {
318
+ total += 1;
319
+ try {
320
+ const out = fn();
321
+ if (out && typeof out.then === 'function') {
322
+ return out.then(function () {
323
+ passed += 1;
324
+ process.stdout.write('PASS ' + name + '\n');
325
+ }, function (err) {
326
+ process.stderr.write('FAIL ' + name + ': ' + (err && err.stack || err) + '\n');
327
+ });
328
+ }
329
+ passed += 1;
330
+ process.stdout.write('PASS ' + name + '\n');
331
+ return Promise.resolve();
332
+ } catch (err) {
333
+ process.stderr.write('FAIL ' + name + ': ' + (err && err.stack || err) + '\n');
334
+ return Promise.resolve();
335
+ }
336
+ }
337
+
338
+ (async function main() {
339
+ // ---------------- Test 01 ----------------
340
+ await run('01 3-room fixture, 0 sealed, contradictions detected', async function () {
341
+ const root = makeFixtureRoot('xroom-t01-');
342
+ // current room + 2 peers, each with a 'market-analysis' section with
343
+ // different governing_thought_hash
344
+ buildRoom(root, 'current-room', [
345
+ { name: 'market-analysis', gt: 'current market framing', problem_type: 'IDP' },
346
+ ]);
347
+ buildRoom(root, 'peer-a', [
348
+ { name: 'market-analysis', gt: 'peer-a market framing variant', problem_type: 'WDP' },
349
+ ]);
350
+ buildRoom(root, 'peer-b', [
351
+ { name: 'market-analysis', gt: 'peer-b market framing other', problem_type: 'UDP' },
352
+ ]);
353
+ writeRegistry(root, [
354
+ { slug: 'current-room' },
355
+ { slug: 'peer-a' },
356
+ { slug: 'peer-b' },
357
+ ], 'current-room');
358
+
359
+ const agg = loadAggregator(root);
360
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
361
+ opt_in: true,
362
+ max_rooms: 10,
363
+ per_room_timeout_ms: 1000,
364
+ });
365
+
366
+ assert.equal(result.scanned_rooms, 2, 'should scan 2 peers (excluding self)');
367
+ assert.equal(result.skipped_rooms, 0, 'no rooms skipped');
368
+ assert.ok(result.contradictions.length >= 1, 'at least one contradiction found');
369
+ });
370
+
371
+ // ---------------- Test 02 ----------------
372
+ await run('02 sealed room (GUARDRAIL.md) -> skipped', async function () {
373
+ const root = makeFixtureRoot('xroom-t02-');
374
+ buildRoom(root, 'current-room', [
375
+ { name: 'market-analysis', gt: 'current gt', problem_type: 'IDP' },
376
+ ]);
377
+ buildRoom(root, 'peer-sealed', [
378
+ { name: 'market-analysis', gt: 'peer gt variant', problem_type: 'WDP' },
379
+ ], { sealed: true });
380
+ buildRoom(root, 'peer-open', [
381
+ { name: 'market-analysis', gt: 'peer-open gt other', problem_type: 'UDP' },
382
+ ]);
383
+ writeRegistry(root, [
384
+ { slug: 'current-room' },
385
+ { slug: 'peer-sealed' },
386
+ { slug: 'peer-open' },
387
+ ], 'current-room');
388
+
389
+ const agg = loadAggregator(root);
390
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
391
+ opt_in: true, max_rooms: 10,
392
+ });
393
+ assert.equal(result.scanned_rooms, 1, 'only peer-open scanned');
394
+ assert.equal(result.skipped_rooms, 1, 'peer-sealed skipped');
395
+ assert.equal(result.skip_reasons.sealed_room, 1, 'sealed reason logged');
396
+ });
397
+
398
+ // ---------------- Test 03 ----------------
399
+ await run('03 opt-out room (brain_cross_room:false) -> skipped', async function () {
400
+ const root = makeFixtureRoot('xroom-t03-');
401
+ buildRoom(root, 'current-room', [
402
+ { name: 'market-analysis', gt: 'current gt', problem_type: 'IDP' },
403
+ ]);
404
+ buildRoom(root, 'peer-opt-out', [
405
+ { name: 'market-analysis', gt: 'peer gt variant', problem_type: 'WDP' },
406
+ ], { opted_out: true });
407
+ buildRoom(root, 'peer-normal', [
408
+ { name: 'market-analysis', gt: 'peer normal gt', problem_type: 'UDP' },
409
+ ]);
410
+ writeRegistry(root, [
411
+ { slug: 'current-room' },
412
+ { slug: 'peer-opt-out' },
413
+ { slug: 'peer-normal' },
414
+ ], 'current-room');
415
+
416
+ const agg = loadAggregator(root);
417
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
418
+ opt_in: true, max_rooms: 10,
419
+ });
420
+ assert.equal(result.scanned_rooms, 1, 'only peer-normal scanned');
421
+ assert.equal(result.skipped_rooms, 1, 'peer-opt-out skipped');
422
+ assert.equal(result.skip_reasons.opt_out, 1, 'opt_out reason logged');
423
+ });
424
+
425
+ // ---------------- Test 04 ----------------
426
+ await run('04 registry.json missing -> graceful fallback', async function () {
427
+ const root = makeFixtureRoot('xroom-t04-');
428
+ buildRoom(root, 'current-room', [
429
+ { name: 'market-analysis', gt: 'current gt' },
430
+ ]);
431
+ // Intentionally no .rooms/registry.json
432
+
433
+ const agg = loadAggregator(root);
434
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
435
+ opt_in: true,
436
+ });
437
+ assert.deepEqual(result.contradictions, []);
438
+ assert.equal(result.scanned_rooms, 0);
439
+ assert.equal(result.skipped_rooms, 0);
440
+ assert.equal(result.reason, 'no_registry');
441
+ });
442
+
443
+ // ---------------- Test 05 ----------------
444
+ await run('05 malformed registry.json -> graceful fallback', async function () {
445
+ const root = makeFixtureRoot('xroom-t05-');
446
+ buildRoom(root, 'current-room', [
447
+ { name: 'market-analysis', gt: 'current gt' },
448
+ ]);
449
+ const dotRooms = path.join(root, '.rooms');
450
+ fs.mkdirSync(dotRooms, { recursive: true });
451
+ fs.writeFileSync(path.join(dotRooms, 'registry.json'), '{ not valid json');
452
+
453
+ const agg = loadAggregator(root);
454
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
455
+ opt_in: true,
456
+ });
457
+ assert.deepEqual(result.contradictions, []);
458
+ assert.equal(result.reason, 'no_registry');
459
+ });
460
+
461
+ // ---------------- Test 06 ----------------
462
+ await run('06 room path outside ALLOWED_ROOT -> skipped as out_of_scope', async function () {
463
+ const root = makeFixtureRoot('xroom-t06-');
464
+ buildRoom(root, 'current-room', [
465
+ { name: 'market-analysis', gt: 'current gt' },
466
+ ]);
467
+ // Register a room whose path escapes ALLOWED_ROOT via ../ traversal
468
+ const outsideRoot = makeFixtureRoot('xroom-t06-outside-');
469
+ buildRoom(outsideRoot, 'escape-room', [
470
+ { name: 'market-analysis', gt: 'escape gt variant' },
471
+ ]);
472
+ const dotRooms = path.join(root, '.rooms');
473
+ fs.mkdirSync(dotRooms, { recursive: true });
474
+ const payload = {
475
+ version: 2,
476
+ root: root,
477
+ active: 'current-room',
478
+ rooms: {
479
+ 'current-room': { path: 'current-room' },
480
+ 'escape-room': { path: path.relative(root, path.join(outsideRoot, 'escape-room')) },
481
+ },
482
+ };
483
+ fs.writeFileSync(path.join(dotRooms, 'registry.json'), JSON.stringify(payload));
484
+
485
+ const agg = loadAggregator(root);
486
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
487
+ opt_in: true,
488
+ });
489
+ assert.equal(result.skipped_rooms, 1);
490
+ assert.equal(result.skip_reasons.out_of_scope || 0, 1);
491
+ });
492
+
493
+ // ---------------- Test 07 ----------------
494
+ await run('07 self-exclusion (currentRoom matches registry entry)', async function () {
495
+ const root = makeFixtureRoot('xroom-t07-');
496
+ buildRoom(root, 'current-room', [
497
+ { name: 'market-analysis', gt: 'current gt' },
498
+ ]);
499
+ buildRoom(root, 'peer-a', [
500
+ { name: 'market-analysis', gt: 'peer gt variant' },
501
+ ]);
502
+ writeRegistry(root, [
503
+ { slug: 'current-room' },
504
+ { slug: 'peer-a' },
505
+ ], 'current-room');
506
+
507
+ const agg = loadAggregator(root);
508
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
509
+ opt_in: true,
510
+ });
511
+ // scanned_rooms excludes self
512
+ assert.equal(result.scanned_rooms, 1, 'self not counted as scanned');
513
+ // Contradictions (if any) never reference self as peer
514
+ for (const c of result.contradictions) {
515
+ assert.notEqual(c.room_a_slug, c.room_b_slug, 'contradiction pair distinct');
516
+ }
517
+ });
518
+
519
+ // ---------------- Test 08 ----------------
520
+ await run('08 hash_divergence contradiction type', async function () {
521
+ const root = makeFixtureRoot('xroom-t08-');
522
+ buildRoom(root, 'current-room', [
523
+ { name: 'market-analysis', gt: 'unique current gt for hash divergence', problem_type: 'IDP' },
524
+ ]);
525
+ buildRoom(root, 'peer-a', [
526
+ { name: 'market-analysis', gt: 'DIFFERENT gt to trigger hash divergence', problem_type: 'IDP' },
527
+ ]);
528
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
529
+
530
+ const agg = loadAggregator(root);
531
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
532
+ opt_in: true,
533
+ });
534
+ const hashDiv = result.contradictions.filter(function (c) { return c.type === 'hash_divergence'; });
535
+ assert.ok(hashDiv.length >= 1, 'hash_divergence found');
536
+ assert.equal(hashDiv[0].section, 'market-analysis');
537
+ });
538
+
539
+ // ---------------- Test 09 ----------------
540
+ await run('09 framework_contradiction contradiction type', async function () {
541
+ const root = makeFixtureRoot('xroom-t09-');
542
+ buildRoom(root, 'current-room', [
543
+ {
544
+ name: 'market-analysis',
545
+ gt: 'same framing',
546
+ problem_type: 'IDP',
547
+ framework_chain: '- SWOT -> FEEDS_INTO -> Porter Five Forces',
548
+ },
549
+ ]);
550
+ buildRoom(root, 'peer-a', [
551
+ {
552
+ name: 'market-analysis',
553
+ gt: 'same framing',
554
+ problem_type: 'IDP',
555
+ framework_chain: '- PESTLE -> FEEDS_INTO -> Competitive Positioning',
556
+ },
557
+ ]);
558
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
559
+
560
+ const agg = loadAggregator(root);
561
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
562
+ opt_in: true,
563
+ });
564
+ const fw = result.contradictions.filter(function (c) { return c.type === 'framework_contradiction'; });
565
+ assert.ok(fw.length >= 1, 'framework_contradiction found (peer chains differ)');
566
+ });
567
+
568
+ // ---------------- Test 10 ----------------
569
+ await run('10 problem_type_mismatch contradiction type', async function () {
570
+ const root = makeFixtureRoot('xroom-t10-');
571
+ buildRoom(root, 'current-room', [
572
+ { name: 'market-analysis', gt: 'same framing P10', problem_type: 'UDP' },
573
+ ]);
574
+ buildRoom(root, 'peer-a', [
575
+ { name: 'market-analysis', gt: 'same framing P10', problem_type: 'WDP' },
576
+ ]);
577
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
578
+
579
+ const agg = loadAggregator(root);
580
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
581
+ opt_in: true,
582
+ });
583
+ const pt = result.contradictions.filter(function (c) { return c.type === 'problem_type_mismatch'; });
584
+ assert.ok(pt.length >= 1, 'problem_type_mismatch found');
585
+ assert.equal(pt[0].detail_scalar.problem_type_a, 'UDP');
586
+ assert.equal(pt[0].detail_scalar.problem_type_b, 'WDP');
587
+ });
588
+
589
+ // ---------------- Test 11 ----------------
590
+ await run('11 no contradictions -> empty contradictions, non-zero scanned', async function () {
591
+ const root = makeFixtureRoot('xroom-t11-');
592
+ // Everything identical -> no contradictions
593
+ const sharedGt = 'identical gt no contradictions';
594
+ buildRoom(root, 'current-room', [
595
+ { name: 'market-analysis', gt: sharedGt, problem_type: 'IDP' },
596
+ ]);
597
+ buildRoom(root, 'peer-a', [
598
+ { name: 'different-section', gt: 'unrelated', problem_type: 'WDP' },
599
+ ]);
600
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
601
+
602
+ const agg = loadAggregator(root);
603
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
604
+ opt_in: true,
605
+ });
606
+ // No shared section slug -> zero contradictions
607
+ assert.deepEqual(result.contradictions, []);
608
+ assert.equal(result.scanned_rooms, 1);
609
+ assert.equal(result.skipped_rooms, 0);
610
+ });
611
+
612
+ // ---------------- Test 12 ----------------
613
+ await run('12 max_rooms:2 limit applied', async function () {
614
+ const root = makeFixtureRoot('xroom-t12-');
615
+ buildRoom(root, 'current-room', [{ name: 'market-analysis', gt: 'g' }]);
616
+ buildRoom(root, 'peer-a', [{ name: 'market-analysis', gt: 'va' }]);
617
+ buildRoom(root, 'peer-b', [{ name: 'market-analysis', gt: 'vb' }]);
618
+ buildRoom(root, 'peer-c', [{ name: 'market-analysis', gt: 'vc' }]);
619
+ writeRegistry(root, [
620
+ { slug: 'current-room' },
621
+ { slug: 'peer-a' },
622
+ { slug: 'peer-b' },
623
+ { slug: 'peer-c' },
624
+ ], 'current-room');
625
+
626
+ const agg = loadAggregator(root);
627
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
628
+ opt_in: true,
629
+ max_rooms: 2,
630
+ });
631
+ assert.equal(result.scanned_rooms, 2, 'max_rooms cap honored');
632
+ assert.ok(
633
+ (result.skip_reasons.max_rooms_exceeded || 0) >= 1,
634
+ 'extras logged under max_rooms_exceeded'
635
+ );
636
+ });
637
+
638
+ // ---------------- Test 13 ----------------
639
+ await run('13 per_room_timeout_ms triggers skip_reasons.timeout', async function () {
640
+ const root = makeFixtureRoot('xroom-t13-');
641
+ buildRoom(root, 'current-room', [{ name: 'market-analysis', gt: 'g' }]);
642
+ buildRoom(root, 'peer-a', [{ name: 'market-analysis', gt: 'va' }]);
643
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
644
+
645
+ // Force timeout via env hook the aggregator honors
646
+ process.env.MINDRIAN_XROOM_FORCE_TIMEOUT = '1';
647
+ const agg = loadAggregator(root);
648
+ try {
649
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
650
+ opt_in: true,
651
+ per_room_timeout_ms: 1,
652
+ });
653
+ assert.equal(result.scanned_rooms, 0, 'peer timed out -> not scanned');
654
+ assert.equal(result.skipped_rooms, 1);
655
+ assert.equal(result.skip_reasons.timeout, 1);
656
+ } finally {
657
+ delete process.env.MINDRIAN_XROOM_FORCE_TIMEOUT;
658
+ }
659
+ });
660
+
661
+ // ---------------- Test 14 ----------------
662
+ await run('14 unreadable room dir -> skip_reasons.unreadable', async function () {
663
+ // NOTE: if running as root, chmod 0 does not block reads. Skip in that case.
664
+ if (typeof process.getuid === 'function' && process.getuid() === 0) {
665
+ process.stdout.write(' (root detected; skip semantics verified via env trigger)\n');
666
+ }
667
+ const root = makeFixtureRoot('xroom-t14-');
668
+ buildRoom(root, 'current-room', [{ name: 'market-analysis', gt: 'g' }]);
669
+ const peerPath = buildRoom(root, 'peer-a', [{ name: 'market-analysis', gt: 'v' }]);
670
+ writeRegistry(root, [{ slug: 'current-room' }, { slug: 'peer-a' }], 'current-room');
671
+
672
+ // Force unreadable via env hook the aggregator honors (deterministic
673
+ // even under root).
674
+ process.env.MINDRIAN_XROOM_FORCE_UNREADABLE = 'peer-a';
675
+ const agg = loadAggregator(root);
676
+ try {
677
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
678
+ opt_in: true,
679
+ });
680
+ assert.equal(result.skipped_rooms, 1);
681
+ assert.equal(result.skip_reasons.unreadable, 1);
682
+ } finally {
683
+ delete process.env.MINDRIAN_XROOM_FORCE_UNREADABLE;
684
+ }
685
+ // Restore perms for cleanup
686
+ try { walkChmod(peerPath, 0o755); } catch (_e) { /* best effort */ }
687
+ });
688
+
689
+ // ---------------- Test 15 ----------------
690
+ await run('15 wall-clock <500ms for 10-room fixture', async function () {
691
+ const root = makeFixtureRoot('xroom-t15-');
692
+ const slugs = ['current-room'];
693
+ buildRoom(root, 'current-room', [
694
+ { name: 'market-analysis', gt: 'current gt' },
695
+ ]);
696
+ for (let i = 0; i < 9; i++) {
697
+ const slug = 'peer-' + i;
698
+ slugs.push(slug);
699
+ buildRoom(root, slug, [
700
+ { name: 'market-analysis', gt: 'peer ' + i + ' gt variant' },
701
+ ]);
702
+ }
703
+ writeRegistry(root, slugs.map(function (s) { return { slug: s }; }), 'current-room');
704
+
705
+ const agg = loadAggregator(root);
706
+ const t0 = Date.now();
707
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
708
+ opt_in: true,
709
+ max_rooms: 10,
710
+ });
711
+ const elapsed = Date.now() - t0;
712
+ assert.ok(elapsed < 500, 'wall-clock < 500ms (was ' + elapsed + 'ms)');
713
+ assert.equal(result.scanned_rooms, 9);
714
+ });
715
+
716
+ // ---------------- Test 16 (LOAD-BEARING Canon Part 8 payload audit) ----------------
717
+ await run('16 CANON PART 8 -- JSON.stringify(result) contains ZERO forbidden substrings', async function () {
718
+ const root = makeFixtureRoot('xroom-t16-');
719
+ // Dangerous content in EVERY room: identity text, MINTO governing
720
+ // thought, BRAIN.md body, and even section names.
721
+ buildRoom(root, 'current-room', [
722
+ {
723
+ name: 'market-analysis',
724
+ gt: DANGEROUS_CONTENT[0], // 'Lawrence said ... $5M ...'
725
+ problem_type: 'IDP',
726
+ dangerous_content: DANGEROUS_CONTENT[1], // 'meeting with email'
727
+ pattern_matches: '- ' + DANGEROUS_CONTENT[2], // SSN leak
728
+ framework_chain: '- ' + DANGEROUS_CONTENT[5], // 'Jonathan said ...'
729
+ problem_type_body: '- ' + DANGEROUS_CONTENT[3], // '$5M revenue'
730
+ },
731
+ ]);
732
+ buildRoom(root, 'peer-a', [
733
+ {
734
+ name: 'market-analysis',
735
+ gt: DANGEROUS_CONTENT[3], // '$5M revenue'
736
+ problem_type: 'WDP',
737
+ dangerous_content: DANGEROUS_CONTENT[4], // 'call Oren at +1...'
738
+ pattern_matches: '- ' + DANGEROUS_CONTENT[0],
739
+ },
740
+ ]);
741
+ buildRoom(root, 'peer-b', [
742
+ {
743
+ name: 'market-analysis',
744
+ gt: DANGEROUS_CONTENT[5], // 'Jonathan said ...'
745
+ problem_type: 'UDP',
746
+ dangerous_content: DANGEROUS_CONTENT[2],
747
+ pattern_matches: '- ' + DANGEROUS_CONTENT[4],
748
+ },
749
+ ]);
750
+ writeRegistry(root, [
751
+ { slug: 'current-room' },
752
+ { slug: 'peer-a' },
753
+ { slug: 'peer-b' },
754
+ ], 'current-room');
755
+
756
+ const agg = loadAggregator(root);
757
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
758
+ opt_in: true, max_rooms: 10,
759
+ });
760
+
761
+ const serialized = JSON.stringify(result);
762
+ // Must have scanned 2 peers; confirms we actually read the dangerous
763
+ // content and STILL produced clean output.
764
+ assert.equal(result.scanned_rooms, 2);
765
+ // Assert against the exact dangerous substrings
766
+ for (const bad of DANGEROUS_CONTENT) {
767
+ // Case-insensitive whole-string search
768
+ const found = serialized.toLowerCase().indexOf(bad.toLowerCase()) !== -1;
769
+ assert.equal(found, false,
770
+ 'Canon Part 8 breach: serialized output contains forbidden substring: ' + bad);
771
+ }
772
+ // Assert against the forbidden regex set
773
+ const regexHit = containsForbidden(serialized);
774
+ assert.equal(regexHit, null,
775
+ 'Canon Part 8 breach: serialized output matches forbidden regex: ' + regexHit);
776
+ // And produce at least one contradiction (confirms the scanner DID
777
+ // extract structural data even while rejecting user content)
778
+ assert.ok(result.contradictions.length >= 1,
779
+ 'scanner should still produce structural contradictions');
780
+ });
781
+
782
+ // ---------------- Test 17 (Canon Part 8 detail_scalar shape audit) ----------------
783
+ await run('17 CANON PART 8 -- detail_scalar shape is primitive-only', async function () {
784
+ const root = makeFixtureRoot('xroom-t17-');
785
+ buildRoom(root, 'current-room', [
786
+ {
787
+ name: 'market-analysis',
788
+ gt: DANGEROUS_CONTENT[0],
789
+ problem_type: 'IDP',
790
+ dangerous_content: DANGEROUS_CONTENT[1],
791
+ },
792
+ ]);
793
+ buildRoom(root, 'peer-a', [
794
+ {
795
+ name: 'market-analysis',
796
+ gt: DANGEROUS_CONTENT[5],
797
+ problem_type: 'WDP',
798
+ dangerous_content: DANGEROUS_CONTENT[3],
799
+ },
800
+ ]);
801
+ writeRegistry(root, [
802
+ { slug: 'current-room' },
803
+ { slug: 'peer-a' },
804
+ ], 'current-room');
805
+
806
+ const agg = loadAggregator(root);
807
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
808
+ opt_in: true,
809
+ });
810
+
811
+ assert.ok(result.contradictions.length >= 1);
812
+ for (const c of result.contradictions) {
813
+ assert.ok(c.detail_scalar && typeof c.detail_scalar === 'object',
814
+ 'detail_scalar is object');
815
+ for (const k of Object.keys(c.detail_scalar)) {
816
+ const v = c.detail_scalar[k];
817
+ const t = typeof v;
818
+ // Primitives only: string, number, boolean, null.
819
+ assert.ok(
820
+ t === 'string' || t === 'number' || t === 'boolean' || v === null,
821
+ 'detail_scalar.' + k + ' must be primitive; got ' + t
822
+ );
823
+ if (t === 'string') {
824
+ // Structural strings (enums, slugs) MUST be <= 40 chars
825
+ assert.ok(v.length <= 40,
826
+ 'detail_scalar.' + k + ' string must be <= 40 chars (was ' + v.length + '): ' + v);
827
+ // Must not match any forbidden regex
828
+ const hit = containsForbidden(v);
829
+ assert.equal(hit, null,
830
+ 'detail_scalar.' + k + ' matches forbidden regex: ' + hit);
831
+ }
832
+ }
833
+ // confidence must be [0,1]
834
+ if (typeof c.confidence === 'number') {
835
+ assert.ok(c.confidence >= 0 && c.confidence <= 1);
836
+ }
837
+ }
838
+ });
839
+
840
+ // ---------------- Test 18 (deriveSection integration) ----------------
841
+ await run('18 deriveSection integration w/ cross_room_scan:true', async function () {
842
+ // This test exercises the integration point: brain-derivation.cjs
843
+ // accepts options.cross_room_scan which, when true, calls the
844
+ // aggregator and populates the 'Flagged Contradictions (cross-room)'
845
+ // body with slug-based entries only.
846
+ const root = makeFixtureRoot('xroom-t18-');
847
+ buildRoom(root, 'current-room', [
848
+ {
849
+ name: 'market-analysis',
850
+ gt: DANGEROUS_CONTENT[0],
851
+ problem_type: 'IDP',
852
+ dangerous_content: DANGEROUS_CONTENT[1],
853
+ },
854
+ ]);
855
+ buildRoom(root, 'peer-a', [
856
+ {
857
+ name: 'market-analysis',
858
+ gt: DANGEROUS_CONTENT[3],
859
+ problem_type: 'WDP',
860
+ dangerous_content: DANGEROUS_CONTENT[5],
861
+ },
862
+ ]);
863
+ writeRegistry(root, [
864
+ { slug: 'current-room' },
865
+ { slug: 'peer-a' },
866
+ ], 'current-room');
867
+
868
+ // Load the aggregator and render a pseudo-body through its
869
+ // rendering helper (the actual deriveSection integration calls
870
+ // renderCrossRoomSection with the aggregator result).
871
+ const agg = loadAggregator(root);
872
+ const result = await agg.aggregateContradictions(path.join(root, 'current-room'), {
873
+ opt_in: true,
874
+ });
875
+ const body = agg.renderCrossRoomSection(result);
876
+
877
+ // Body must contain slug-based entries when contradictions exist
878
+ assert.ok(typeof body === 'string');
879
+ // Body must contain NO dangerous substrings
880
+ for (const bad of DANGEROUS_CONTENT) {
881
+ const found = body.toLowerCase().indexOf(bad.toLowerCase()) !== -1;
882
+ assert.equal(found, false,
883
+ 'rendered section body contains forbidden substring: ' + bad);
884
+ }
885
+ const hit = containsForbidden(body);
886
+ assert.equal(hit, null, 'rendered body matches forbidden regex: ' + hit);
887
+ // If contradictions exist, body references peer slug
888
+ if (result.contradictions.length > 0) {
889
+ assert.ok(/peer-a/.test(body) || /current-room/.test(body),
890
+ 'body cites peer slug structurally');
891
+ }
892
+
893
+ // Confirm brain-derivation.cjs exports the integration option
894
+ delete require.cache[require.resolve(path.join(REPO, 'lib/core/brain-derivation.cjs'))];
895
+ const deriv = require(path.join(REPO, 'lib/core/brain-derivation.cjs'));
896
+ const src = fs.readFileSync(path.join(REPO, 'lib/core/brain-derivation.cjs'), 'utf8');
897
+ assert.ok(/cross_room_scan/.test(src),
898
+ 'brain-derivation.cjs references cross_room_scan option');
899
+ assert.ok(/aggregateContradictions/.test(src),
900
+ 'brain-derivation.cjs wires aggregateContradictions');
901
+ });
902
+
903
+ // ---------- Summary ----------
904
+ process.stdout.write('\ncross-room-aggregator: ' + passed + '/' + total + ' passed\n');
905
+ if (passed < total) process.exit(1);
906
+ })().catch(function (err) {
907
+ process.stderr.write('TEST HARNESS ERROR: ' + (err && err.stack || err) + '\n');
908
+ process.exit(1);
909
+ });