@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,1057 @@
1
+ /**
2
+ * MindrianOS Plugin -- LazyGraph Operations
3
+ * SQLite-backed per-project queryable knowledge graph via node:sqlite DatabaseSync.
4
+ * Graph stored in room/.mindrian/room.db (WAL mode for concurrent reads).
5
+ *
6
+ * Exports: openGraph, closeGraph, initSchema, indexArtifact,
7
+ * rebuildGraph, queryGraph, graphStats, embedArtifact,
8
+ * + 12 edge-creator functions + EDGE_TYPES constant
9
+ *
10
+ * All functions remain async for backward compatibility with callers
11
+ * that use await. Awaiting a synchronous return resolves immediately.
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const crypto = require('crypto');
19
+ const { DatabaseSync } = require('node:sqlite');
20
+ const { discoverSections } = require('./section-registry.cjs');
21
+
22
+ // --- Schema ---
23
+
24
+ /** All relationship (edge) types in the LazyGraph */
25
+ const EDGE_TYPES = ['INFORMS', 'CONTRADICTS', 'CONVERGES', 'ENABLES', 'INVALIDATES', 'BELONGS_TO', 'REASONING_INFORMS', 'HSI_CONNECTION', 'REVERSE_SALIENT', 'ANALOGOUS_TO', 'STRUCTURALLY_ISOMORPHIC', 'RESOLVES_VIA', 'CAUSES', 'ROOT_CAUSE_OF', 'CASCADES_TO', 'EXTRACTED_FROM', 'WHITESPACE_DETECTED', 'WHITESPACE_NEAR', 'DISCOVERY_CYCLE_SOURCE', 'DISCOVERED', 'DERIVED_FROM', 'AUTHORED_BY', 'AFFILIATED_WITH'];
26
+
27
+ /**
28
+ * Create nodes and edges tables with indexes. Idempotent.
29
+ * @param {import('node:sqlite').DatabaseSync} db - node:sqlite DatabaseSync instance
30
+ */
31
+ function initSchema(db) {
32
+ db.exec(`
33
+ CREATE TABLE IF NOT EXISTS nodes (
34
+ id TEXT PRIMARY KEY,
35
+ type TEXT NOT NULL,
36
+ properties TEXT DEFAULT '{}'
37
+ );
38
+ CREATE TABLE IF NOT EXISTS edges (
39
+ source TEXT NOT NULL,
40
+ target TEXT NOT NULL,
41
+ type TEXT NOT NULL,
42
+ properties TEXT DEFAULT '{}',
43
+ PRIMARY KEY (source, target, type),
44
+ FOREIGN KEY (source) REFERENCES nodes(id),
45
+ FOREIGN KEY (target) REFERENCES nodes(id)
46
+ );
47
+ CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
48
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source);
49
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target);
50
+ CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
51
+ CREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(source, type);
52
+ CREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(target, type);
53
+ CREATE TABLE IF NOT EXISTS stakeholders (
54
+ id TEXT PRIMARY KEY,
55
+ type TEXT NOT NULL,
56
+ name TEXT NOT NULL,
57
+ canonical_ref TEXT,
58
+ notes TEXT,
59
+ metadata TEXT DEFAULT '{}',
60
+ created_at TEXT NOT NULL,
61
+ updated_at TEXT NOT NULL
62
+ );
63
+ CREATE INDEX IF NOT EXISTS idx_stakeholders_type ON stakeholders(type);
64
+ CREATE INDEX IF NOT EXISTS idx_stakeholders_canonical ON stakeholders(canonical_ref);
65
+ -- rs_discoveries: contract bridge between the RS SQLite mirror (writer) and
66
+ -- the RS NL/SQL readers. rs-sqlite-mirror.cjs writes RSDiscovery records
67
+ -- into the nodes table with the payload in a JSON properties bag
68
+ -- (keys: classification, breakthrough_score, dominant_dimension, thesis,
69
+ -- bridge_concept, diff, lsa, bert, ..., created_at, room_id, domain). The
70
+ -- readers (rs-nl-to-query.cjs SQL templates and scripts/rs-thesis-command.cjs)
71
+ -- query SELECT * FROM rs_discoveries WHERE room_slug = ? and
72
+ -- SELECT id, thesis, rs_type, breakthrough_score, room_slug, created_at
73
+ -- FROM rs_discoveries WHERE id = ? -- there was no such table, so queryGraph
74
+ -- swallowed the "no such table" error as []. This VIEW closes that gap: it
75
+ -- json_extract's the payload keys into the columns the queries expect and
76
+ -- renames the writer's room_id JSON key to the reader's room_slug column
77
+ -- and classification to rs_type. node:sqlite's bundled SQLite ships the
78
+ -- JSON1 extension so json_extract works. NOTE: room_id is mapped to
79
+ -- room_slug verbatim -- if ctx.room_id passed by the RS engine is ever
80
+ -- not a room slug, that is a separate data-lineage fix in
81
+ -- rs-discovery-engine.cjs / rs-sqlite-mirror.cjs; this view only aligns the
82
+ -- column NAMES the queries already use. CREATE VIEW IF NOT EXISTS makes it
83
+ -- idempotent -- existing room.db files pick it up on the next openGraph.
84
+ CREATE VIEW IF NOT EXISTS rs_discoveries AS
85
+ SELECT
86
+ id,
87
+ json_extract(properties, '$.thesis') AS thesis,
88
+ json_extract(properties, '$.classification') AS rs_type,
89
+ json_extract(properties, '$.breakthrough_score') AS breakthrough_score,
90
+ json_extract(properties, '$.room_id') AS room_slug,
91
+ json_extract(properties, '$.created_at') AS created_at,
92
+ json_extract(properties, '$.bridge_concept') AS bridge_concept,
93
+ json_extract(properties, '$.dominant_dimension') AS dominant_dimension,
94
+ json_extract(properties, '$.diff') AS diff,
95
+ json_extract(properties, '$.lsa') AS lsa,
96
+ json_extract(properties, '$.bert') AS bert,
97
+ json_extract(properties, '$.domain') AS domain,
98
+ properties AS properties_json
99
+ FROM nodes
100
+ WHERE type = 'RSDiscovery';
101
+ `);
102
+ }
103
+
104
+ // --- Stakeholder helpers (Phase 84-05, SCOPE-NB-03 / SCOPE-NB-04) ---
105
+
106
+ /** Allowed stakeholder.type values per Phase 84-05 spec. */
107
+ const STAKEHOLDER_TYPES = ['person', 'org', 'coalition', 'role'];
108
+
109
+ /**
110
+ * Validate a stakeholder type string. Throws TypeError on mismatch.
111
+ * @param {string} type
112
+ */
113
+ function validateStakeholderType(type) {
114
+ if (!STAKEHOLDER_TYPES.includes(type)) {
115
+ throw new TypeError(`Invalid stakeholder type: ${type}. Must be one of ${STAKEHOLDER_TYPES.join(', ')}`);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Create a new Stakeholder row.
121
+ * Generates a UUID, stamps created_at and updated_at, inserts the row.
122
+ * @param {import('node:sqlite').DatabaseSync} db
123
+ * @param {object} fields
124
+ * @param {string} fields.type - person | org | coalition | role
125
+ * @param {string} fields.name
126
+ * @param {string} [fields.canonical_ref]
127
+ * @param {string} [fields.notes]
128
+ * @param {object|string} [fields.metadata]
129
+ * @returns {Promise<object|null>} The new row or null on failure.
130
+ */
131
+ async function createStakeholder(db, fields) {
132
+ try {
133
+ const { type, name } = fields || {};
134
+ validateStakeholderType(type);
135
+ if (!name || typeof name !== 'string') {
136
+ throw new TypeError('Stakeholder name is required');
137
+ }
138
+ const id = crypto.randomUUID();
139
+ const now = new Date().toISOString();
140
+ const canonical_ref = fields.canonical_ref || null;
141
+ const notes = fields.notes || null;
142
+ const metadata = typeof fields.metadata === 'string'
143
+ ? fields.metadata
144
+ : JSON.stringify(fields.metadata || {});
145
+ db.prepare(
146
+ 'INSERT INTO stakeholders (id, type, name, canonical_ref, notes, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
147
+ ).run(id, type, name, canonical_ref, notes, metadata, now, now);
148
+ return Promise.resolve({ id, type, name, canonical_ref, notes, metadata, created_at: now, updated_at: now });
149
+ } catch (e) {
150
+ if (e instanceof TypeError) throw e;
151
+ return Promise.resolve(null);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Get a Stakeholder row by id.
157
+ * @param {import('node:sqlite').DatabaseSync} db
158
+ * @param {string} id
159
+ * @returns {Promise<object|null>}
160
+ */
161
+ async function getStakeholder(db, id) {
162
+ try {
163
+ const row = db.prepare('SELECT * FROM stakeholders WHERE id = ?').get(id);
164
+ return Promise.resolve(row || null);
165
+ } catch (e) {
166
+ return Promise.resolve(null);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Upsert a Stakeholder by canonical_ref. Updates in place if present, else inserts.
172
+ * Updates updated_at on every call.
173
+ * @param {import('node:sqlite').DatabaseSync} db
174
+ * @param {string} canonical_ref
175
+ * @param {object} fields
176
+ * @returns {Promise<object|null>}
177
+ */
178
+ async function upsertStakeholder(db, canonical_ref, fields) {
179
+ try {
180
+ if (!canonical_ref) {
181
+ throw new TypeError('canonical_ref is required for upsertStakeholder');
182
+ }
183
+ const existing = db.prepare('SELECT * FROM stakeholders WHERE canonical_ref = ?').get(canonical_ref);
184
+ const now = new Date().toISOString();
185
+ if (existing) {
186
+ const type = fields.type || existing.type;
187
+ validateStakeholderType(type);
188
+ const name = fields.name || existing.name;
189
+ const notes = fields.notes !== undefined ? fields.notes : existing.notes;
190
+ const metadata = fields.metadata !== undefined
191
+ ? (typeof fields.metadata === 'string' ? fields.metadata : JSON.stringify(fields.metadata))
192
+ : existing.metadata;
193
+ db.prepare(
194
+ 'UPDATE stakeholders SET type = ?, name = ?, notes = ?, metadata = ?, updated_at = ? WHERE id = ?'
195
+ ).run(type, name, notes, metadata, now, existing.id);
196
+ return Promise.resolve({ ...existing, type, name, notes, metadata, updated_at: now });
197
+ }
198
+ return createStakeholder(db, { ...fields, canonical_ref });
199
+ } catch (e) {
200
+ if (e instanceof TypeError) throw e;
201
+ return Promise.resolve(null);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Find stakeholders connected to a claim artifact via INFORMS edges.
207
+ * Walks both directions (stakeholder INFORMS claim, claim INFORMS stakeholder).
208
+ * @param {import('node:sqlite').DatabaseSync} db
209
+ * @param {string} claim_artifact_id
210
+ * @returns {Promise<Array<object>>}
211
+ */
212
+ async function findStakeholdersByClaim(db, claim_artifact_id) {
213
+ try {
214
+ const rows = db.prepare(
215
+ `SELECT s.* FROM stakeholders s
216
+ WHERE s.id IN (
217
+ SELECT source FROM edges WHERE target = ? AND type = 'INFORMS'
218
+ UNION
219
+ SELECT target FROM edges WHERE source = ? AND type = 'INFORMS'
220
+ )
221
+ LIMIT 20`
222
+ ).all(claim_artifact_id, claim_artifact_id);
223
+ return Promise.resolve(rows || []);
224
+ } catch (e) {
225
+ return Promise.resolve([]);
226
+ }
227
+ }
228
+
229
+ // --- Helpers ---
230
+
231
+ /**
232
+ * Get artifact ID from file path relative to room dir.
233
+ * @param {string} filePath - Absolute path to .md file
234
+ * @param {string} roomDir - Absolute path to room directory
235
+ * @returns {string} e.g. "problem-definition/market-trends"
236
+ */
237
+ function getArtifactId(filePath, roomDir) {
238
+ const rel = path.relative(path.resolve(roomDir), path.resolve(filePath));
239
+ return rel.replace(/\.md$/, '').replace(/\\/g, '/');
240
+ }
241
+
242
+ /**
243
+ * Extract title from first # heading in file content.
244
+ * @param {string} content - File content
245
+ * @param {string} filePath - Fallback basename
246
+ * @returns {string}
247
+ */
248
+ function extractTitle(content, filePath) {
249
+ const match = content.match(/^# (.+)$/m);
250
+ return match ? match[1].trim() : path.basename(filePath, '.md');
251
+ }
252
+
253
+ /**
254
+ * Extract a frontmatter field value.
255
+ * @param {string} content - File content
256
+ * @param {string} field - Field name
257
+ * @returns {string}
258
+ */
259
+ function extractFrontmatter(content, field) {
260
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
261
+ if (!fmMatch) return '';
262
+ const line = fmMatch[1].split('\n').find(l => l.startsWith(field + ':'));
263
+ if (!line) return '';
264
+ return line.slice(field.length + 1).trim().replace(/^["']|["']$/g, '');
265
+ }
266
+
267
+ /**
268
+ * Compute MD5 content hash (first 8 hex chars).
269
+ * @param {string} content
270
+ * @returns {string}
271
+ */
272
+ function computeHash(content) {
273
+ return crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
274
+ }
275
+
276
+ // --- Contradiction detection terms ---
277
+ const CONTRADICT_TERMS = ['however', 'contradicts', 'unlike', 'disagrees', 'conflicts', 'contrary', 'opposes'];
278
+
279
+ // --- Core Functions ---
280
+
281
+ /**
282
+ * Open (or create) a SQLite database at {roomDir}/.mindrian/room.db.
283
+ * Enables WAL mode and foreign keys. Initializes schema.
284
+ * @param {string} roomDir - Path to room directory
285
+ * @returns {Promise<{db: import('node:sqlite').DatabaseSync, conn: import('node:sqlite').DatabaseSync}>}
286
+ */
287
+ async function openGraph(roomDir) {
288
+ const resolved = path.resolve(roomDir);
289
+ const dbDir = path.join(resolved, '.mindrian');
290
+ const dbPath = path.join(dbDir, 'room.db');
291
+
292
+ // Ensure directories exist
293
+ fs.mkdirSync(dbDir, { recursive: true });
294
+
295
+ const db = new DatabaseSync(dbPath);
296
+ db.exec('PRAGMA journal_mode = WAL');
297
+ db.exec('PRAGMA foreign_keys = ON');
298
+
299
+ initSchema(db);
300
+
301
+ // conn === db in SQLite (single object handles both)
302
+ return Promise.resolve({ db, conn: db });
303
+ }
304
+
305
+ /**
306
+ * Close SQLite database.
307
+ * @param {import('node:sqlite').DatabaseSync} db - node:sqlite DatabaseSync instance
308
+ */
309
+ async function closeGraph(db) {
310
+ try {
311
+ db.close();
312
+ } catch (e) {
313
+ // Handle already-closed or invalid db gracefully
314
+ }
315
+ return Promise.resolve();
316
+ }
317
+
318
+ /**
319
+ * Index a single .md artifact into the graph.
320
+ * Creates Artifact node, Section node, BELONGS_TO edge.
321
+ * Scans for [[wikilinks]] to create INFORMS edges.
322
+ * Scans for contradiction terms near wikilinks to create CONTRADICTS edges.
323
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
324
+ * @param {string} roomDir - Absolute path to room directory
325
+ * @param {string} filePath - Absolute path to .md file
326
+ * @returns {Promise<{id: string, section: string, title: string, contentHash: string}>}
327
+ */
328
+ /**
329
+ * Internal: run the INSERT body of indexArtifact WITHOUT opening a transaction.
330
+ * The caller is responsible for transaction semantics. Used by:
331
+ * - indexArtifact() -- wraps this in BEGIN/COMMIT/ROLLBACK
332
+ * - rebuildGraph() -- calls this inside its own outer BEGIN/COMMIT so the
333
+ * whole rebuild is atomic (no nested transactions, which SQLite forbids
334
+ * without SAVEPOINT).
335
+ * @param {import('node:sqlite').DatabaseSync} conn
336
+ * @param {string} roomDir
337
+ * @param {string} filePath
338
+ * @returns {{id: string, section: string, title: string, contentHash: string}}
339
+ */
340
+ function _indexArtifactBody(conn, roomDir, filePath) {
341
+ const content = fs.readFileSync(filePath, 'utf-8');
342
+ const id = getArtifactId(filePath, roomDir);
343
+ const section = id.split('/')[0];
344
+ const title = extractTitle(content, filePath);
345
+ const methodology = extractFrontmatter(content, 'methodology');
346
+ const created = extractFrontmatter(content, 'date');
347
+ const contentHash = computeHash(content);
348
+ const enables = extractFrontmatter(content, 'enables');
349
+ const invalidates = extractFrontmatter(content, 'invalidates');
350
+ const wikilinks = content.match(/\[\[([^\]]+)\]\]/g) || [];
351
+ const artifactProps = JSON.stringify({ title, section, methodology, created, content_hash: contentHash });
352
+ const sectionLabel = section.replace(/-/g, ' ').toUpperCase();
353
+ const sectionProps = JSON.stringify({ name: section, label: sectionLabel });
354
+
355
+ // Upsert Artifact node
356
+ conn.prepare(
357
+ 'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
358
+ ).run(id, 'Artifact', artifactProps);
359
+
360
+ // Upsert Section node
361
+ conn.prepare(
362
+ 'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET properties = excluded.properties'
363
+ ).run(section, 'Section', sectionProps);
364
+
365
+ // Upsert BELONGS_TO edge
366
+ conn.prepare(
367
+ 'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
368
+ ).run(id, section, 'BELONGS_TO');
369
+
370
+ // Scan for [[wikilinks]] to create INFORMS edges
371
+ for (const link of wikilinks) {
372
+ const target = link.replace(/\[\[|\]\]/g, '').toLowerCase().replace(/\s/g, '-');
373
+
374
+ const linkIdx = content.indexOf(link);
375
+ const contextStart = Math.max(0, linkIdx - 200);
376
+ const contextEnd = Math.min(content.length, linkIdx + 200);
377
+ const nearbyText = content.slice(contextStart, contextEnd).toLowerCase();
378
+
379
+ const isContradiction = CONTRADICT_TERMS.some(term => nearbyText.includes(term));
380
+
381
+ const targetArtifacts = conn.prepare(
382
+ "SELECT id FROM nodes WHERE type = 'Artifact' AND json_extract(properties, '$.section') = ?"
383
+ ).all(target);
384
+
385
+ for (const row of targetArtifacts) {
386
+ const targetId = row.id;
387
+ if (targetId === id) continue;
388
+
389
+ if (isContradiction) {
390
+ const contradictProps = JSON.stringify({ confidence: 'medium' });
391
+ conn.prepare(
392
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
393
+ ).run(id, targetId, 'CONTRADICTS', contradictProps);
394
+ }
395
+
396
+ conn.prepare(
397
+ 'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
398
+ ).run(id, targetId, 'INFORMS');
399
+ }
400
+ }
401
+
402
+ if (enables) {
403
+ const targetRows = conn.prepare("SELECT id FROM nodes WHERE id = ? AND type = 'Artifact'").all(enables);
404
+ for (const row of targetRows) {
405
+ conn.prepare(
406
+ 'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
407
+ ).run(id, row.id, 'ENABLES');
408
+ }
409
+ }
410
+
411
+ if (invalidates) {
412
+ const targetRows = conn.prepare("SELECT id FROM nodes WHERE id = ? AND type = 'Artifact'").all(invalidates);
413
+ for (const row of targetRows) {
414
+ conn.prepare(
415
+ 'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
416
+ ).run(id, row.id, 'INVALIDATES');
417
+ }
418
+ }
419
+
420
+ return { id, section, title, contentHash };
421
+ }
422
+
423
+ async function indexArtifact(conn, roomDir, filePath) {
424
+ // --- Transaction: all INSERTs or none (Plan 87-06 / CASCADE-04) ---
425
+ // node:sqlite DatabaseSync does NOT expose a transaction(fn) higher-order
426
+ // helper (that is a better-sqlite3 API). We use explicit BEGIN/COMMIT with
427
+ // ROLLBACK on any throw. On successful completion, SQLite commits the
428
+ // block; on any error, we roll back the partial writes and re-throw so
429
+ // the caller sees the original error.
430
+ //
431
+ // Why this matters (CASCADE-04): a pre-patch throw between two INSERTs
432
+ // could leave dangling nodes without their edges, or vice versa. The
433
+ // BEGIN/COMMIT wrapper converts the whole indexArtifact body into a
434
+ // single atomic unit. The post-condition is either "all writes survive"
435
+ // or "no writes survive" -- there is no middle state.
436
+ //
437
+ // Pure computation (file read, hash, wikilink scan) runs inside the txn
438
+ // but is idempotent and side-effect free. Moving it outside would require
439
+ // duplicating it in _indexArtifactBody and the rebuildGraph call site;
440
+ // keeping it inside keeps indexArtifact a thin BEGIN/COMMIT shell.
441
+ conn.prepare('BEGIN').run();
442
+ let result;
443
+ try {
444
+ result = _indexArtifactBody(conn, roomDir, filePath);
445
+ conn.prepare('COMMIT').run();
446
+ } catch (err) {
447
+ // ROLLBACK undoes every INSERT issued since BEGIN. We swallow any error
448
+ // from ROLLBACK itself (rare: can happen if the connection is already
449
+ // aborted) and always re-throw the ORIGINAL error so the caller sees
450
+ // the failure reason, not a ROLLBACK noise error.
451
+ try { conn.prepare('ROLLBACK').run(); } catch (_rbErr) { /* ignore */ }
452
+ throw err;
453
+ }
454
+
455
+ return Promise.resolve(result);
456
+ }
457
+
458
+ /**
459
+ * Rebuild the entire graph from all room artifacts.
460
+ * Clears existing data, walks all sections, indexes every .md file.
461
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
462
+ * @param {string} roomDir - Absolute path to room directory
463
+ * @returns {Promise<{success: boolean, artifacts: number, sections: number}>}
464
+ */
465
+ async function rebuildGraph(conn, roomDir) {
466
+ const resolved = path.resolve(roomDir);
467
+
468
+ // Wrap entire rebuild in a transaction for atomicity.
469
+ // If anything throws mid-rebuild, the DB rolls back to pre-rebuild state.
470
+ //
471
+ // NOTE (Plan 87-06): use explicit BEGIN/COMMIT/ROLLBACK because
472
+ // node:sqlite DatabaseSync does NOT expose a transaction(fn) higher-order
473
+ // helper (that is a better-sqlite3 API). Calling the inner
474
+ // `_indexArtifactBody` (not `indexArtifact`) avoids a nested BEGIN that
475
+ // SQLite would reject ("cannot start a transaction within a transaction"
476
+ // without SAVEPOINT).
477
+ const discovery = discoverSections(resolved);
478
+ const sectionNames = discovery.all;
479
+ let artifactCount = 0;
480
+
481
+ conn.prepare('BEGIN').run();
482
+ try {
483
+ // Clear all existing data (edges first for FK compliance)
484
+ conn.exec('DELETE FROM edges; DELETE FROM nodes;');
485
+
486
+ for (const sectionName of sectionNames) {
487
+ const sectionDir = path.join(resolved, sectionName);
488
+ let files;
489
+ try {
490
+ files = fs.readdirSync(sectionDir).filter(f =>
491
+ f.endsWith('.md') && f !== 'STATE.md' && f !== 'ROOM.md'
492
+ );
493
+ } catch (e) {
494
+ continue; // skip this section
495
+ }
496
+
497
+ for (const file of files) {
498
+ const filePath = path.join(sectionDir, file);
499
+ _indexArtifactBody(conn, resolved, filePath); // inside outer BEGIN, no nested txn
500
+ artifactCount++;
501
+ }
502
+ }
503
+ conn.prepare('COMMIT').run();
504
+ } catch (err) {
505
+ try { conn.prepare('ROLLBACK').run(); } catch (_rbErr) { /* ignore */ }
506
+ throw err;
507
+ }
508
+
509
+ return Promise.resolve({ success: true, artifacts: artifactCount, sections: sectionNames.length });
510
+ }
511
+
512
+ /**
513
+ * Execute a SQL query and return all result rows.
514
+ * Gracefully returns empty array on error.
515
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
516
+ * @param {string} sql - SQL query string
517
+ * @param {Array<*>|Object} [params] - bound parameters; array for anonymous (?) placeholders, object for named ($x)
518
+ * @returns {Promise<Array<object>>}
519
+ */
520
+ async function queryGraph(conn, sql, params) {
521
+ try {
522
+ const stmt = conn.prepare(sql);
523
+ if (params == null) {
524
+ return Promise.resolve(stmt.all());
525
+ }
526
+ if (Array.isArray(params)) {
527
+ return Promise.resolve(params.length ? stmt.all(...params) : stmt.all());
528
+ }
529
+ return Promise.resolve(stmt.all(params));
530
+ } catch (e) {
531
+ return Promise.resolve([]);
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Get graph statistics: node counts, edge counts, totals.
537
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
538
+ * @returns {Promise<{nodes: object, edges: object, total: {nodes: number, edges: number}}>}
539
+ */
540
+ async function graphStats(conn) {
541
+ // Count nodes by type
542
+ // Count ALL node types dynamically (not hardcoded)
543
+ const nodeCounts = {};
544
+ const nodeRows = conn.prepare('SELECT type, COUNT(*) AS cnt FROM nodes GROUP BY type').all();
545
+ for (const row of nodeRows) {
546
+ nodeCounts[row.type] = row.cnt;
547
+ }
548
+
549
+ // Count edges by type
550
+ const edges = {};
551
+ for (const edgeType of EDGE_TYPES) { edges[edgeType] = 0; }
552
+ const edgeRows = conn.prepare('SELECT type, COUNT(*) AS cnt FROM edges GROUP BY type').all();
553
+ for (const row of edgeRows) {
554
+ if (row.type in edges) {
555
+ edges[row.type] = row.cnt;
556
+ }
557
+ }
558
+
559
+ const totalNodes = Object.values(nodeCounts).reduce((sum, n) => sum + n, 0);
560
+ const totalEdges = Object.values(edges).reduce((sum, n) => sum + n, 0);
561
+
562
+ return Promise.resolve({
563
+ nodes: nodeCounts,
564
+ edges,
565
+ total: { nodes: totalNodes, edges: totalEdges },
566
+ });
567
+ }
568
+
569
+ /**
570
+ * Tier 2 Pinecone semantic layer stub.
571
+ * Embeds an artifact for semantic search when Pinecone is configured.
572
+ * Gracefully degrades when Pinecone env vars are not set.
573
+ *
574
+ * Contract: embedArtifact(roomDir, filePath) -> { success: boolean, reason?: string, embeddingId?: string }
575
+ *
576
+ * @param {string} roomDir - Absolute path to room directory
577
+ * @param {string} filePath - Absolute path to .md artifact file
578
+ * @returns {Promise<{success: boolean, reason?: string, embeddingId?: string}>}
579
+ */
580
+ async function embedArtifact(roomDir, filePath) {
581
+ // Read the artifact content (validates the file exists)
582
+ const resolved = path.resolve(filePath);
583
+ if (!fs.existsSync(resolved)) {
584
+ return { success: false, reason: `Artifact not found: ${filePath}` };
585
+ }
586
+
587
+ const apiKey = process.env.PINECONE_API_KEY;
588
+ const index = process.env.PINECONE_INDEX;
589
+
590
+ if (!apiKey || !index) {
591
+ return {
592
+ success: false,
593
+ reason: 'Pinecone Tier 2 not configured - set PINECONE_API_KEY and PINECONE_INDEX to enable semantic search',
594
+ };
595
+ }
596
+
597
+ // Pinecone env vars are set but integration not yet wired
598
+ return {
599
+ success: false,
600
+ reason: 'Pinecone Tier 2 integration not yet implemented - stub ready for future wiring',
601
+ };
602
+ }
603
+
604
+ // --- Design-by-Analogy Edge Creation (DBA-08) ---
605
+
606
+ /**
607
+ * Create an ANALOGOUS_TO edge between two artifacts.
608
+ * Records functional analogy with distance, fitness, and transfer mapping.
609
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
610
+ * @param {string} sourceId - Source artifact ID
611
+ * @param {string} targetId - Target artifact ID
612
+ * @param {object} props - Edge properties
613
+ * @param {string} [props.analogy_distance='near'] - near|far|cross-domain
614
+ * @param {number} [props.structural_fitness=0.0] - 0-1 structural fitness score
615
+ * @param {string} [props.source_domain=''] - Domain of the source analogy
616
+ * @param {string} [props.transfer_map='{}'] - JSON string mapping source to target elements
617
+ * @param {string} [props.discovery_method='hsi'] - hsi|brain|llm|external|user
618
+ * @returns {Promise<boolean>}
619
+ */
620
+ async function createAnalogyEdge(conn, sourceId, targetId, props = {}) {
621
+ const edgeProps = JSON.stringify({
622
+ analogy_distance: props.analogy_distance || 'near',
623
+ structural_fitness: props.structural_fitness || 0.0,
624
+ source_domain: props.source_domain || '',
625
+ transfer_map: props.transfer_map || '{}',
626
+ discovery_method: props.discovery_method || 'hsi',
627
+ });
628
+
629
+ conn.prepare(
630
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
631
+ ).run(sourceId, targetId, 'ANALOGOUS_TO', edgeProps);
632
+ return Promise.resolve(true);
633
+ }
634
+
635
+ /**
636
+ * Create a STRUCTURALLY_ISOMORPHIC edge between two sections.
637
+ * Records identical relational topology between room sections.
638
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
639
+ * @param {string} sectionA - Source section name
640
+ * @param {string} sectionB - Target section name
641
+ * @param {object} props - Edge properties
642
+ * @param {number} [props.isomorphism_score=0.0] - 0-1 isomorphism score
643
+ * @param {string} [props.mapped_elements='{}'] - JSON string of element mappings
644
+ * @param {string} [props.source=''] - Source of the isomorphism detection
645
+ * @returns {Promise<boolean>}
646
+ */
647
+ async function createIsomorphismEdge(conn, sectionA, sectionB, props = {}) {
648
+ const edgeProps = JSON.stringify({
649
+ isomorphism_score: props.isomorphism_score || 0.0,
650
+ mapped_elements: props.mapped_elements || '{}',
651
+ source: props.source || '',
652
+ });
653
+
654
+ conn.prepare(
655
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
656
+ ).run(sectionA, sectionB, 'STRUCTURALLY_ISOMORPHIC', edgeProps);
657
+ return Promise.resolve(true);
658
+ }
659
+
660
+ /**
661
+ * Create a RESOLVES_VIA edge linking a contradiction to its resolution.
662
+ * Closes the loop: contradiction -> analogy/TRIZ -> resolution.
663
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
664
+ * @param {string} sourceId - Artifact ID (the contradicting artifact)
665
+ * @param {string} targetId - Artifact ID (the resolution artifact)
666
+ * @param {object} props - Edge properties
667
+ * @param {string} [props.resolution_type='direct'] - analogy|triz_principle|direct
668
+ * @param {string} [props.triz_principle=''] - TRIZ principle number/name if applicable
669
+ * @param {string} [props.analogy_source=''] - Source analogy reference if applicable
670
+ * @param {number} [props.confidence=0.0] - 0-1 confidence in resolution
671
+ * @returns {Promise<boolean>}
672
+ */
673
+ async function createResolutionEdge(conn, sourceId, targetId, props = {}) {
674
+ const edgeProps = JSON.stringify({
675
+ resolution_type: props.resolution_type || 'direct',
676
+ triz_principle: props.triz_principle || '',
677
+ analogy_source: props.analogy_source || '',
678
+ confidence: props.confidence || 0.0,
679
+ });
680
+
681
+ conn.prepare(
682
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
683
+ ).run(sourceId, targetId, 'RESOLVES_VIA', edgeProps);
684
+ return Promise.resolve(true);
685
+ }
686
+
687
+ // --- Causal Extraction CRUD (Phase 53) ---
688
+
689
+ /**
690
+ * Create or update a CausalClaim node in SQLite.
691
+ * Writes all 12 properties as JSON. Truncates cause/effect to 200 chars,
692
+ * mechanism to 300 chars to prevent oversized nodes.
693
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
694
+ * @param {object} claim - CausalClaim properties
695
+ * @param {string} claim.id - Unique claim ID
696
+ * @param {string} claim.cause - Cause statement (truncated to 200 chars)
697
+ * @param {string} claim.mechanism - Mechanism explanation (truncated to 300 chars)
698
+ * @param {string} claim.effect - Effect statement (truncated to 200 chars)
699
+ * @param {number} [claim.confidence=0.5] - Confidence score (0-1)
700
+ * @param {string} [claim.evidence='[]'] - JSON array of evidence references
701
+ * @param {string} [claim.source_artifact=''] - Source artifact ID
702
+ * @param {string} [claim.domain='general'] - Domain classification
703
+ * @param {string} [claim.falsifiable_prediction=''] - Testable prediction
704
+ * @param {number} [claim.novelty_score=0.0] - Novelty score (0-1)
705
+ * @param {string} [claim.extraction_method='inferred'] - observed|asserted|inferred
706
+ * @param {string} [claim.created=''] - ISO date string
707
+ * @returns {Promise<boolean>}
708
+ */
709
+ async function createCausalClaim(conn, claim) {
710
+ const cause = (claim.cause || '').slice(0, 200);
711
+ const mechanism = (claim.mechanism || '').slice(0, 300);
712
+ const effect = (claim.effect || '').slice(0, 200);
713
+ const confidence = typeof claim.confidence === 'number' ? claim.confidence : 0.5;
714
+ const evidence = Array.isArray(claim.evidence) ? JSON.stringify(claim.evidence) : (claim.evidence || '[]');
715
+ const sourceArtifact = claim.source_artifact || '';
716
+ const domain = claim.domain || 'general';
717
+ const prediction = claim.falsifiable_prediction || '';
718
+ const novelty = typeof claim.novelty_score === 'number' ? claim.novelty_score : 0.0;
719
+ const method = claim.extraction_method || 'inferred';
720
+ const created = claim.created || '';
721
+
722
+ const props = JSON.stringify({
723
+ cause, mechanism, effect, confidence, evidence, source_artifact: sourceArtifact,
724
+ domain, falsifiable_prediction: prediction, novelty_score: novelty,
725
+ extraction_method: method, created,
726
+ });
727
+
728
+ conn.prepare(
729
+ 'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
730
+ ).run(claim.id, 'CausalClaim', props);
731
+ return Promise.resolve(true);
732
+ }
733
+
734
+ /**
735
+ * Create an EXTRACTED_FROM edge linking a CausalClaim to its source Artifact.
736
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
737
+ * @param {string} claimId - CausalClaim node ID
738
+ * @param {string} artifactId - Artifact node ID
739
+ * @returns {Promise<boolean>}
740
+ */
741
+ async function createExtractedFromEdge(conn, claimId, artifactId) {
742
+ conn.prepare(
743
+ 'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
744
+ ).run(claimId, artifactId, 'EXTRACTED_FROM');
745
+ return Promise.resolve(true);
746
+ }
747
+
748
+ // --- Causal Graph Engine (Phase 54) ---
749
+
750
+ /**
751
+ * Create a CASCADES_TO edge between two CausalClaim nodes.
752
+ * Idempotent via ON CONFLICT. Records cascade type and severity.
753
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
754
+ * @param {string} fromClaimId - Source CausalClaim node ID
755
+ * @param {string} toClaimId - Target CausalClaim node ID
756
+ * @param {object} [opts] - Edge properties
757
+ * @param {string} [opts.cascade_type='invalidation'] - invalidation|weakening|dependency
758
+ * @param {string} [opts.severity='medium'] - high|medium|low
759
+ * @param {number} [opts.path_length=1] - Hop distance
760
+ * @returns {Promise<boolean>}
761
+ */
762
+ async function createCascadesToEdge(conn, fromClaimId, toClaimId, opts = {}) {
763
+ const edgeProps = JSON.stringify({
764
+ cascade_type: opts.cascade_type || 'invalidation',
765
+ severity: opts.severity || 'medium',
766
+ path_length: typeof opts.path_length === 'number' ? opts.path_length : 1,
767
+ });
768
+
769
+ conn.prepare(
770
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
771
+ ).run(fromClaimId, toClaimId, 'CASCADES_TO', edgeProps);
772
+ return Promise.resolve(true);
773
+ }
774
+
775
+ /**
776
+ * Export all CausalClaim nodes and CASCADES_TO edges as JSON.
777
+ * Writes .lazygraph-causal-export.json to roomDir for Python engine consumption.
778
+ * Handles empty graphs gracefully (writes JSON with empty arrays).
779
+ * @param {string} roomDir - Path to room directory
780
+ * @returns {Promise<{metadata: object, nodes: Array, edges: Array}>}
781
+ */
782
+ async function exportCausalGraph(roomDir) {
783
+ const resolved = path.resolve(roomDir);
784
+ const { db, conn } = await openGraph(resolved);
785
+ try {
786
+ // Query all CausalClaim nodes
787
+ let nodes = [];
788
+ try {
789
+ nodes = conn.prepare(
790
+ "SELECT id, json_extract(properties, '$.cause') AS cause, json_extract(properties, '$.mechanism') AS mechanism, json_extract(properties, '$.effect') AS effect, json_extract(properties, '$.confidence') AS confidence, json_extract(properties, '$.domain') AS domain, json_extract(properties, '$.source_artifact') AS source_artifact FROM nodes WHERE type = 'CausalClaim'"
791
+ ).all();
792
+ } catch (e) {
793
+ // table may be empty or schema different
794
+ }
795
+
796
+ // Query all CASCADES_TO edges
797
+ let edges = [];
798
+ try {
799
+ edges = conn.prepare(
800
+ "SELECT source, target, json_extract(properties, '$.cascade_type') AS cascade_type, json_extract(properties, '$.severity') AS severity FROM edges WHERE type = 'CASCADES_TO'"
801
+ ).all();
802
+ } catch (e) {
803
+ // no CASCADES_TO edges
804
+ }
805
+
806
+ const exportData = {
807
+ metadata: {
808
+ exported_at: new Date().toISOString(),
809
+ node_count: nodes.length,
810
+ edge_count: edges.length,
811
+ },
812
+ nodes,
813
+ edges,
814
+ };
815
+
816
+ const exportPath = path.join(resolved, '.lazygraph-causal-export.json');
817
+ fs.writeFileSync(exportPath, JSON.stringify(exportData, null, 2), 'utf-8');
818
+ return exportData;
819
+ } finally {
820
+ await closeGraph(db);
821
+ }
822
+ }
823
+
824
+ // --- TRIZ Contradiction Enrichment (DBA-09) ---
825
+
826
+ /**
827
+ * Enrich an existing CONTRADICTS edge with TRIZ parameter classification.
828
+ * Looks up triz-matrix.json to suggest inventive principles for the contradiction.
829
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
830
+ * @param {string} artifactA - Source artifact ID
831
+ * @param {string} artifactB - Target artifact ID
832
+ * @param {string} improvingParam - TRIZ parameter being improved (one of 39)
833
+ * @param {string} worseningParam - TRIZ parameter being worsened (one of 39)
834
+ * @returns {Promise<{success: boolean, principles: number[], reason?: string}>}
835
+ */
836
+ async function enrichContradictionWithTRIZ(conn, artifactA, artifactB, improvingParam, worseningParam) {
837
+ // Load TRIZ matrix
838
+ const matrixPath = path.join(__dirname, '..', '..', 'references', 'methodology', 'triz-matrix.json');
839
+ if (!fs.existsSync(matrixPath)) {
840
+ return { success: false, principles: [], reason: 'triz-matrix.json not found' };
841
+ }
842
+
843
+ let matrix;
844
+ try {
845
+ matrix = JSON.parse(fs.readFileSync(matrixPath, 'utf-8'));
846
+ } catch (e) {
847
+ return { success: false, principles: [], reason: 'Failed to parse triz-matrix.json' };
848
+ }
849
+
850
+ // Look up principles
851
+ const improvingEntry = matrix[improvingParam];
852
+ if (!improvingEntry) {
853
+ return { success: false, principles: [], reason: `Unknown improving parameter: ${improvingParam}` };
854
+ }
855
+
856
+ const principles = improvingEntry[worseningParam];
857
+ if (!principles || !Array.isArray(principles) || principles.length === 0) {
858
+ return { success: false, principles: [], reason: `No principles found for ${improvingParam} vs ${worseningParam}` };
859
+ }
860
+
861
+ const principlesStr = principles.join(',');
862
+
863
+ // Create a RESOLVES_VIA edge capturing the TRIZ resolution direction
864
+ const edgeProps = JSON.stringify({
865
+ resolution_type: 'triz_principle',
866
+ triz_principle: principlesStr,
867
+ analogy_source: `${improvingParam} vs ${worseningParam}`,
868
+ confidence: 0.7,
869
+ });
870
+
871
+ conn.prepare(
872
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
873
+ ).run(artifactA, artifactB, 'RESOLVES_VIA', edgeProps);
874
+
875
+ return { success: true, principles };
876
+ }
877
+
878
+ // --- Whitespace Zone CRUD (Phase 61) ---
879
+
880
+ /**
881
+ * Create or update a WhitespaceZone node in SQLite.
882
+ * Writes all properties as JSON. Idempotent.
883
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
884
+ * @param {object} zone - WhitespaceZone properties
885
+ * @param {string} zone.id - Unique zone ID
886
+ * @param {string} zone.brain_framework - Brain framework name this gap relates to
887
+ * @param {number} [zone.density_score=0.0] - Embedding space density score
888
+ * @param {number} [zone.knn_density=0.0] - KNN density from SemNovel algorithm
889
+ * @param {string} [zone.nearest_frameworks='[]'] - JSON array of nearest framework names
890
+ * @param {string} [zone.hypothesis=''] - Generated hypothesis about what should fill this gap
891
+ * @param {number} [zone.strategic_rank=0.0] - Strategic importance ranking
892
+ * @param {string} [zone.problem_type=''] - Problem type classification
893
+ * @param {string} [zone.exploration_status='detected'] - detected|exploring|resolved|dismissed
894
+ * @param {string} [zone.created=''] - ISO date string
895
+ * @returns {Promise<boolean>}
896
+ */
897
+ async function addWhitespaceZone(conn, zone) {
898
+ const props = JSON.stringify({
899
+ brain_framework: zone.brain_framework || '',
900
+ density_score: typeof zone.density_score === 'number' ? zone.density_score : 0.0,
901
+ knn_density: typeof zone.knn_density === 'number' ? zone.knn_density : 0.0,
902
+ nearest_frameworks: Array.isArray(zone.nearest_frameworks) ? JSON.stringify(zone.nearest_frameworks) : (zone.nearest_frameworks || '[]'),
903
+ hypothesis: (zone.hypothesis || '').slice(0, 500),
904
+ strategic_rank: typeof zone.strategic_rank === 'number' ? zone.strategic_rank : 0.0,
905
+ problem_type: zone.problem_type || '',
906
+ exploration_status: zone.exploration_status || 'detected',
907
+ created: zone.created || '',
908
+ });
909
+
910
+ conn.prepare(
911
+ 'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
912
+ ).run(zone.id, 'WhitespaceZone', props);
913
+ return Promise.resolve(true);
914
+ }
915
+
916
+ /**
917
+ * Create a WHITESPACE_DETECTED edge from a WhitespaceZone to a nearby Artifact.
918
+ * Records embedding distance. Idempotent.
919
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
920
+ * @param {string} zoneId - WhitespaceZone node ID
921
+ * @param {string} artifactId - Artifact node ID
922
+ * @param {number} [distance=0.0] - Embedding distance between zone centroid and artifact
923
+ * @returns {Promise<boolean>}
924
+ */
925
+ async function linkWhitespaceToArtifact(conn, zoneId, artifactId, distance = 0.0) {
926
+ const edgeProps = JSON.stringify({ distance });
927
+ conn.prepare(
928
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
929
+ ).run(zoneId, artifactId, 'WHITESPACE_DETECTED', edgeProps);
930
+ return Promise.resolve(true);
931
+ }
932
+
933
+ /**
934
+ * Create a WHITESPACE_NEAR edge from a WhitespaceZone to a Section.
935
+ * Records relevance score. Idempotent.
936
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
937
+ * @param {string} zoneId - WhitespaceZone node ID
938
+ * @param {string} sectionName - Section node name
939
+ * @param {number} [relevance=0.0] - Relevance score (0-1)
940
+ * @returns {Promise<boolean>}
941
+ */
942
+ async function linkWhitespaceToSection(conn, zoneId, sectionName, relevance = 0.0) {
943
+ const edgeProps = JSON.stringify({ relevance });
944
+ conn.prepare(
945
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
946
+ ).run(zoneId, sectionName, 'WHITESPACE_NEAR', edgeProps);
947
+ return Promise.resolve(true);
948
+ }
949
+
950
+ /**
951
+ * Link a WhitespaceZone to an Artifact via DISCOVERY_CYCLE_SOURCE edge.
952
+ * Records which discovery method (hsi, rs, analogy) found this zone.
953
+ * @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
954
+ * @param {string} zoneId - WhitespaceZone ID
955
+ * @param {string} artifactId - Source Artifact ID
956
+ * @param {string} discoveryMethod - 'hsi' | 'rs' | 'analogy'
957
+ * @param {string} [cycleTimestamp] - ISO timestamp of discovery cycle run
958
+ * @returns {Promise<boolean>}
959
+ */
960
+ async function linkDiscoveryCycleSource(conn, zoneId, artifactId, discoveryMethod, cycleTimestamp = '') {
961
+ const ts = cycleTimestamp || new Date().toISOString();
962
+ const edgeProps = JSON.stringify({ discovery_method: discoveryMethod, cycle_timestamp: ts });
963
+ conn.prepare(
964
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
965
+ ).run(zoneId, artifactId, 'DISCOVERY_CYCLE_SOURCE', edgeProps);
966
+ return Promise.resolve(true);
967
+ }
968
+
969
+ // --- Phase 89-07 Wave 1: generic typed-edge upsert primitive ---
970
+ //
971
+ // upsertEdge is the single chokepoint by which agentic surfaces (Phase 89-07
972
+ // ReverseSalientAgent, Phase 116 tension hook, Phase 117 auto-explore, Phase
973
+ // 118 MVA, Phase 120 breakthrough scan) emit typed cascade edges. It validates
974
+ // the edge type against EDGE_TYPES and performs the same UPSERT pattern used
975
+ // by every other edge writer in this module.
976
+ //
977
+ // Shape: upsertEdge(conn, { type, source, target, properties }) -> { ok, reason? }
978
+ // - conn: node:sqlite DatabaseSync instance (or any object with .prepare).
979
+ // - type: one of EDGE_TYPES (string).
980
+ // - source: source node id (string).
981
+ // - target: target node id (string).
982
+ // - properties: optional object; serialized to JSON.
983
+ //
984
+ // Synchronous (no Promise wrapping); the underlying prepare/run is sync per
985
+ // node:sqlite contract. Per-call shape mirrors the rest of this module.
986
+ //
987
+ // Canon Part 4: every choice is graph data; this primitive lets every agent
988
+ // emit typed edges without bypassing EDGE_TYPES validation. Canon Part 7:
989
+ // reuse-before-build; sibling agents reuse this instead of inlining SQL.
990
+ function upsertEdge(conn, edge) {
991
+ if (!edge || typeof edge !== 'object') {
992
+ return { ok: false, reason: 'invalid_edge_object' };
993
+ }
994
+ const { type, source, target } = edge;
995
+ if (typeof type !== 'string' || !EDGE_TYPES.includes(type)) {
996
+ return { ok: false, reason: 'invalid_edge_type', detail: String(type).slice(0, 40) };
997
+ }
998
+ if (typeof source !== 'string' || source.length === 0) {
999
+ return { ok: false, reason: 'invalid_source_id' };
1000
+ }
1001
+ if (typeof target !== 'string' || target.length === 0) {
1002
+ return { ok: false, reason: 'invalid_target_id' };
1003
+ }
1004
+ const props = edge.properties && typeof edge.properties === 'object' ? edge.properties : {};
1005
+ let propsJson;
1006
+ try {
1007
+ propsJson = JSON.stringify(props);
1008
+ } catch (_e) {
1009
+ return { ok: false, reason: 'properties_serialize_failed' };
1010
+ }
1011
+ try {
1012
+ conn.prepare(
1013
+ 'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
1014
+ ).run(source, target, type, propsJson);
1015
+ } catch (e) {
1016
+ return { ok: false, reason: 'edge_write_failed', detail: String(e.message || '').slice(0, 80) };
1017
+ }
1018
+ return { ok: true, type, source, target };
1019
+ }
1020
+
1021
+ // --- Exports ---
1022
+
1023
+ module.exports = {
1024
+ EDGE_TYPES,
1025
+ upsertEdge,
1026
+ openGraph,
1027
+ closeGraph,
1028
+ initSchema,
1029
+ indexArtifact,
1030
+ rebuildGraph,
1031
+ queryGraph,
1032
+ graphStats,
1033
+ embedArtifact,
1034
+ // Design-by-Analogy (DBA-08, DBA-09)
1035
+ createAnalogyEdge,
1036
+ createIsomorphismEdge,
1037
+ createResolutionEdge,
1038
+ enrichContradictionWithTRIZ,
1039
+ // Causal Extraction (Phase 53)
1040
+ createCausalClaim,
1041
+ createExtractedFromEdge,
1042
+ // Causal Graph Engine (Phase 54)
1043
+ createCascadesToEdge,
1044
+ exportCausalGraph,
1045
+ // Whitespace Zone Layer (Phase 61)
1046
+ addWhitespaceZone,
1047
+ linkWhitespaceToArtifact,
1048
+ linkWhitespaceToSection,
1049
+ // Discovery Cycle (Phase 64)
1050
+ linkDiscoveryCycleSource,
1051
+ // Stakeholder node type (Phase 84-05, SCOPE-NB-03 / SCOPE-NB-04)
1052
+ STAKEHOLDER_TYPES,
1053
+ createStakeholder,
1054
+ getStakeholder,
1055
+ upsertStakeholder,
1056
+ findStakeholdersByClaim,
1057
+ };