@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,1043 @@
1
+ /*
2
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
3
+ *
4
+ * Phase 117-01 Wave 1 -- AutoExploreAgent skeleton.
5
+ * Phase 117-02 Wave 1 -- composeAutoExploreFinding + Brain Substrate constants.
6
+ * Phase 117-03 Wave 2 -- F.1 surface: surfaceFinding + handleUserResponse +
7
+ * populateHSIAnalysis + composeBQAnchoredLarryVoice + buildExploreApprovedEdge
8
+ * + BQ_TEMPLATE_REGISTRY constant.
9
+ *
10
+ * Mirrors lib/agents/tension-hook-agent.cjs structure verbatim per RESEARCH
11
+ * Section 3 (sibling code-clone). Wave 1 ships:
12
+ * - detectFirstMaterial (117-01)
13
+ * - composeAutoExploreFinding (117-02)
14
+ * - CANONICAL_CHAIN_ORDER + CROSS_DOMAIN_THRESHOLD (117-02)
15
+ * - crossDomainSurprise + crossDomainGate helpers (117-02)
16
+ *
17
+ * Wave 2 (117-03 -- this plan) ships:
18
+ * - populateHSIAnalysis (Brain Section 8.4 HSIAnalysis schema population)
19
+ * - composeBQAnchoredLarryVoice (Brain Section 8.5 BQ-anchored render; not raw)
20
+ * - surfaceFinding (F.1 Decision Gate dispatch via lib/hmi/selector-dispatcher.cjs)
21
+ * - handleUserResponse (post-F.1 routing; INFORMS edge on EXPLORE)
22
+ * - buildExploreApprovedEdge (cascade edge spec for lazygraph-ops.upsertEdge)
23
+ * - BQ_TEMPLATE_REGISTRY (4-key registry; Brain Section 8.5 BQ patterns)
24
+ *
25
+ * Later waves ship:
26
+ * - 5 emit helpers (lands 117-05)
27
+ *
28
+ * Per Brain Section 8.7: detection routing is LOCAL-only. NO [Brain-only Cypher edge type, name elided to keep grep regression at zero]
29
+ * Brain calls. The detection rules below run entirely on file extension +
30
+ * path heuristics + room.db artifact count. AUTOEXPLORE-117-17 is enforced via
31
+ * the 117-04 grep regression that scans this file for [Brain-only Cypher edge type, name elided to keep grep regression at zero].
32
+ *
33
+ * Graph-native HARD RULES (memory feedback_reverse_salient_agent_graph_native.md):
34
+ * 1. NEVER require room-db.cjs directly (Phase 109 D-06 chokepoint).
35
+ * 2. NEVER require any Brain-MCP client module (Canon Part 8 boundary;
36
+ * the literal token name is elided to keep the bare-substring grep
37
+ * regression at zero per the 117-02 plan acceptance criteria).
38
+ * 3. NEVER write to stdout / stderr (telemetry side-channel rule).
39
+ *
40
+ * Pure CJS, node built-ins only, zero new runtime dependencies.
41
+ */
42
+ 'use strict';
43
+
44
+ const fs = require('node:fs');
45
+ const path = require('node:path');
46
+ const crypto = require('node:crypto');
47
+ const store = require('../memory/explored-materials-store.cjs');
48
+
49
+ // ---------- Constants ----------
50
+
51
+ const MATERIAL_ID_LEN = 32;
52
+
53
+ /**
54
+ * CANONICAL_CHAIN_ORDER -- Brain Section 8.1 canonical sequence from
55
+ * Stage 'Opportunity Discovery' HAS_STEP -> ProcessStep chain:
56
+ * 1. "Define Domain and Sub-Domain" -> tag: 'domain'
57
+ * 2. "Identify Trends to Exploit" -> tag: 'trends'
58
+ * 3. "Identify Reverse Salients" -> tag: 'reverse-salients'
59
+ * 4. (value-add layer, not a Brain ProcessStep) -> tag: 'cross-domain'
60
+ *
61
+ * Composition output preserves this order as the PRIMARY ranking axis;
62
+ * HSI score is the SECONDARY axis. AUTOEXPLORE-117-13 invariant.
63
+ * Reference: Brain Cypher Q2 (RESEARCH Section 8.9) -- verified 2026-05-06.
64
+ *
65
+ * The literal label "domain -> trends -> reverse-salients -> cross-domain"
66
+ * is preserved here verbatim so the AUTOEXPLORE-117-13 source-citation grep
67
+ * regression returns >= 1 hit on this file.
68
+ */
69
+ const CANONICAL_CHAIN_ORDER = Object.freeze(['domain', 'trends', 'reverse-salients', 'cross-domain']);
70
+
71
+ /**
72
+ * CROSS_DOMAIN_THRESHOLD -- Brain Section 8.3 default from
73
+ * node 'cynefin-cross-domain-detector' detection_method:
74
+ * "cosine_similarity > threshold AND different_domains"
75
+ * surprise_score formula:
76
+ * "similarity * domain_distance"
77
+ *
78
+ * Default 0.85 matches Phase 89-07 dedup gate per RESEARCH Section 8.3.
79
+ * AUTOEXPLORE-117-14 invariant.
80
+ */
81
+ const CROSS_DOMAIN_THRESHOLD = 0.85;
82
+
83
+ /**
84
+ * BQ_TEMPLATE_REGISTRY -- Brain Section 8.5 BQ-anchored Larry voice templates.
85
+ *
86
+ * Per Brain Cypher Q6 (BeautifulQuestion-[GUIDED_BY|GENERATES_MATRIX]-)
87
+ * domain analysis links to a portfolio of canonical questions. Phase 117
88
+ * v1 ships a LOCAL registry of 4 templates keyed by source_pipeline tag;
89
+ * future variant may replace via runtime Brain query (deferred to Phase 110
90
+ * Brain Context Packet Contract).
91
+ *
92
+ * Per Brain Section 8.5: F.1 line is BQ-anchored, NOT raw technical match.
93
+ * AUTOEXPLORE-117-16 invariant.
94
+ *
95
+ * W7 fix iteration 1: bq_id keys are LOCAL shorthand. brain_canonical_name field
96
+ * carries the verbatim Brain BQ name from RESEARCH Section 8.5 Cypher result;
97
+ * this is the moat-aligned key per docs/MOAT-MANDATE.md -- preserve verbatim
98
+ * across phases so cross-room comparability holds. 'bq_id' is the local registry
99
+ * lookup key; 'brain_canonical_name' is the Brain handle.
100
+ */
101
+ const BQ_TEMPLATE_REGISTRY = Object.freeze({
102
+ 'cross-domain': {
103
+ template: "What if the deepest pattern here isn't '{category_error}' but '{semantic_surprise}'?",
104
+ bq_id: 'bq-emerging-patterns',
105
+ bq_text: 'What new relationships or surprising patterns are emerging from our diverse evidence and where might they lead?',
106
+ brain_canonical_name: 'Question-Domain-Expert Breakthrough Map', // GENERATES_MATRIX BQ from Brain Cypher Section 8.5
107
+ },
108
+ 'reverse-salients': {
109
+ template: "What lagging element in '{source_section}' is holding back '{target_section}'?",
110
+ bq_id: 'bq-reverse-salient',
111
+ bq_text: 'Which lagging components in this system most constrain its forward motion?',
112
+ brain_canonical_name: 'Domain Reassessment & Transition', // GUIDED_BY BQ from Brain Cypher Section 8.5
113
+ },
114
+ 'domain': {
115
+ template: "What essential elements of '{source_section}' are we missing?",
116
+ bq_id: 'bq-domain-decomposition',
117
+ bq_text: 'What essential elements or patterns can we surface by breaking this context into its smallest actionable parts?',
118
+ brain_canonical_name: 'Domain Sensing & Placement', // GUIDED_BY BQ from Brain Cypher Section 8.5
119
+ },
120
+ 'trends': {
121
+ template: "What trend signals are reshaping '{source_section}' faster than we are adapting?",
122
+ bq_id: 'bq-domain-reassessment',
123
+ bq_text: 'How should we recalibrate our understanding now that new patterns or evidence have shifted domain boundaries?',
124
+ brain_canonical_name: 'Domain Reassessment & Transition', // GUIDED_BY BQ from Brain Cypher Section 8.5
125
+ },
126
+ });
127
+
128
+ // ---------- Cross-domain helpers (Brain Section 8.3) ----------
129
+
130
+ /**
131
+ * crossDomainSurprise -- Brain Section 8.3 canonical formula.
132
+ *
133
+ * surprise = similarity * domain_distance
134
+ *
135
+ * Commutative; deterministic; never throws. Non-finite inputs collapse to 0
136
+ * (the null path is "no candidate produced", not "zero-score").
137
+ *
138
+ * @param {number} similarity cosine similarity in [0,1]
139
+ * @param {number} domainDistance domain-distance scalar in [0,1]
140
+ * @returns {number}
141
+ */
142
+ function crossDomainSurprise(similarity, domainDistance) {
143
+ const s = Number(similarity);
144
+ const d = Number(domainDistance);
145
+ if (!Number.isFinite(s) || !Number.isFinite(d)) return 0;
146
+ return s * d;
147
+ }
148
+
149
+ /**
150
+ * crossDomainGate -- Brain Section 8.3 detection_method gate.
151
+ *
152
+ * cosine_similarity > threshold AND different_domains
153
+ *
154
+ * Defaults threshold to CROSS_DOMAIN_THRESHOLD (0.85) when caller does not
155
+ * pass an explicit threshold. Returns false on any non-finite cosine.
156
+ *
157
+ * @param {number} cosine cosine similarity in [0,1]
158
+ * @param {string} sourceDomain domain label of the source
159
+ * @param {string} targetDomain domain label of the target
160
+ * @param {number} [threshold] override default threshold (must be > 0)
161
+ * @returns {boolean}
162
+ */
163
+ function crossDomainGate(cosine, sourceDomain, targetDomain, threshold) {
164
+ const c = Number(cosine);
165
+ if (!Number.isFinite(c)) return false;
166
+ const t = (Number.isFinite(Number(threshold)) && Number(threshold) > 0)
167
+ ? Number(threshold)
168
+ : CROSS_DOMAIN_THRESHOLD;
169
+ return c > t && String(sourceDomain || '') !== String(targetDomain || '');
170
+ }
171
+
172
+ // ---------- detectFirstMaterial (LOCAL-only routing) ----------
173
+
174
+ /**
175
+ * Brain §8.7 INVARIANT (LOCAL-ONLY DETECTION ROUTING).
176
+ *
177
+ * Per Brain Cypher Q7 (RESEARCH Section 8.9) the canonical
178
+ * Domain & Trend Analysis Technique (id: 'tech-domain-analysis') is NOT
179
+ * bound to any ProblemType node in Brain via [Brain-only Cypher edge
180
+ * type, name elided to keep grep regression at zero], ADDRESSES, or
181
+ * FOR_PROBLEM relationships. The Cypher query returned EMPTY results
182
+ * when verified 2026-05-06.
183
+ *
184
+ * Implication: the auto-fire decision ("is this material domain-analyzable?")
185
+ * CANNOT be delegated to Brain. Detection rules MUST be self-contained in
186
+ * LOCAL code (file extension + path heuristics + room.db artifact count).
187
+ *
188
+ * Phase 117's first-material detection (117-01) does NOT block on Brain
189
+ * enrichment that doesn't exist; the auto-fire decision is LOCAL-only.
190
+ * Brain is consulted later (composeAutoExploreFinding via §8.4 HSIAnalysis
191
+ * schema reference and §8.5 BQ template registry) but NEVER for the
192
+ * detection-vs-skip routing decision.
193
+ *
194
+ * AUTOEXPLORE-117-17 enforced via:
195
+ * - This comment block (visible to maintainers)
196
+ * - Regression grep in tests/test-detection-routing-local-only.cjs
197
+ * (asserts grep on the Brain-only edge-type token returns 0 in agent.cjs)
198
+ *
199
+ * DO NOT add the Brain edge-type call to this module.
200
+ */
201
+
202
+ /**
203
+ * Decide whether the PostToolUse Write|Edit|MultiEdit event represents a
204
+ * "first material" worth exploring. Per Brain §8.7 the routing is
205
+ * entirely LOCAL: no Brain [Brain-only Cypher edge type, name elided to keep grep regression at zero] call, no remote query.
206
+ *
207
+ * Tier 0: artifactCount < 0 means caller could not determine (room.db missing
208
+ * or unreadable). Suppress with 'tier_0'.
209
+ * Tier 1: artifactCount in [0, 4]. First-material candidate; eligible to fire.
210
+ * Tier 2+: artifactCount >= 5. Auto-fire still eligible; daily-cap takes
211
+ * precedence and is enforced by the caller (the fingerprint hook).
212
+ *
213
+ * @param {object} args
214
+ * @param {string} args.roomDir absolute path to room (with .room-root)
215
+ * @param {string} args.relativeFilePath path relative to roomDir
216
+ * @param {number} args.mtimeMs file mtime in ms-epoch
217
+ * @param {number} args.artifactCount rows in nodes table for this room
218
+ * (use -1 to signal db missing -> Tier 0)
219
+ * @returns {{is_first_material: boolean, tier: number, material_id: string|null,
220
+ * suppress_reason: string|null}}
221
+ */
222
+ function detectFirstMaterial(args) {
223
+ const roomDir = (args && typeof args.roomDir === 'string') ? args.roomDir : '';
224
+ const relativeFilePath = (args && typeof args.relativeFilePath === 'string') ? args.relativeFilePath : '';
225
+ const mtimeMs = (args && Number.isFinite(args.mtimeMs)) ? args.mtimeMs : NaN;
226
+ const artifactCount = (args && Number.isFinite(args.artifactCount)) ? args.artifactCount : 0;
227
+
228
+ if (!roomDir || !relativeFilePath || !Number.isFinite(mtimeMs)) {
229
+ return {
230
+ is_first_material: false,
231
+ tier: 0,
232
+ material_id: null,
233
+ suppress_reason: 'invalid_args',
234
+ };
235
+ }
236
+
237
+ const material_id = store.computeMaterialId(roomDir, relativeFilePath, mtimeMs);
238
+
239
+ // Tier 0: artifactCount < 0 means caller could not read room.db.
240
+ if (artifactCount < 0) {
241
+ return {
242
+ is_first_material: false,
243
+ tier: 0,
244
+ material_id: material_id,
245
+ suppress_reason: 'tier_0',
246
+ };
247
+ }
248
+
249
+ // Tier 1 (0..4 artifacts) is the first-material moment per CONTEXT.md.
250
+ // Tier 2+ (5+ artifacts) is also auto-fire eligible -- the daily-cap enforces
251
+ // the per-room rate limit (CONTEXT.md AC4 + RESEARCH Section 4.5).
252
+ const tier = artifactCount < 5 ? 1 : 2;
253
+ return {
254
+ is_first_material: true,
255
+ tier: tier,
256
+ material_id: material_id,
257
+ suppress_reason: null,
258
+ };
259
+ }
260
+
261
+ // ---------- composeAutoExploreFinding (Brain Section 8.1 + 8.3 + 8.4) ----------
262
+
263
+ /**
264
+ * composeAutoExploreFinding({material_id, whitespace, rs, analogy})
265
+ *
266
+ * Per Brain Section 8.1 (Stage 'Opportunity Discovery' HAS_STEP sequence):
267
+ * emits candidates in CANONICAL_CHAIN_ORDER (domain -> trends ->
268
+ * reverse-salients -> cross-domain) as primary axis; HSI score as secondary
269
+ * axis.
270
+ *
271
+ * Per Brain Section 8.3 (Cross-Domain Surprise Detector formula): cross-domain
272
+ * tier applies surprise = similarity * domain_distance with gate cosine >
273
+ * threshold AND different_domains.
274
+ *
275
+ * Per Brain Section 8.4 (HSIAnalysis schema): finding object extends
276
+ * HSIAnalysis shape; population in 117-03 (populateHSIAnalysis fills the
277
+ * null/empty placeholders below).
278
+ *
279
+ * Returns the finding object or null when all pipelines yield zero candidates.
280
+ *
281
+ * Per Brain Section 8.7: this function performs ZERO Brain queries. Routing
282
+ * is LOCAL-only. The literal token [Brain-only Cypher edge type, name elided
283
+ * to keep grep regression at zero] never appears here.
284
+ *
285
+ * @param {object} args
286
+ * @param {string} args.material_id 32-char hex from store.computeMaterialId
287
+ * @param {object} [args.whitespace] { gaps: [...] } from compute-whitespace-gaps.py
288
+ * @param {object} [args.rs] { pairs: [...] } from rs-engine.py --mode hybrid
289
+ * @param {object} [args.analogy] { zones: [...] } from discovery-cycle.cjs analogy_whitespace
290
+ * @returns {object|null}
291
+ */
292
+ function composeAutoExploreFinding(args) {
293
+ const material_id = String((args && args.material_id) || '');
294
+ if (!material_id) return null;
295
+ const whitespace = (args && args.whitespace) || {};
296
+ const rs = (args && args.rs) || {};
297
+ const analogy = (args && args.analogy) || {};
298
+
299
+ // Per Brain Section 8.1: emit candidates tagged by canonical-chain pipeline,
300
+ // preserving the HAS_STEP sequence as primary axis.
301
+ const buckets = {
302
+ 'domain': [],
303
+ 'trends': [],
304
+ 'reverse-salients': [],
305
+ 'cross-domain': [],
306
+ };
307
+
308
+ // Whitespace gaps -> 'domain' bucket (the Define Domain ProcessStep).
309
+ const wsGaps = Array.isArray(whitespace.gaps) ? whitespace.gaps : [];
310
+ for (const gap of wsGaps) {
311
+ if (!gap || typeof gap !== 'object') continue;
312
+ const nearest = (Array.isArray(gap.nearest_room_artifacts) && gap.nearest_room_artifacts[0]) || null;
313
+ buckets['domain'].push({
314
+ source_pipeline: 'domain',
315
+ source_node_id: (nearest && nearest.id) || null,
316
+ target_node_id: null,
317
+ score: 1.0 - (Number(gap.density_score) || 0),
318
+ source_section: (nearest && nearest.section) || '',
319
+ target_section: '',
320
+ framework_chain: Array.isArray(gap.framework_chain) ? gap.framework_chain.slice() : [],
321
+ });
322
+ }
323
+
324
+ // Trends bucket: when whitespace.gaps are tagged with framework_chain
325
+ // including 'Trends' or external-paper signals -- for v1 we LEAVE this
326
+ // bucket empty (Phase 88.6 external-paper integration is the natural
327
+ // future feed); the bucket exists so canonical-order is structurally
328
+ // present (test 4: missing pipeline => empty array, not absent key).
329
+ // No-op block intentional.
330
+
331
+ // RS pairs -> 'reverse-salients' bucket (the Identify Reverse Salients ProcessStep).
332
+ const rsPairs = Array.isArray(rs.pairs) ? rs.pairs : [];
333
+ for (const pair of rsPairs) {
334
+ if (!pair || typeof pair !== 'object') continue;
335
+ buckets['reverse-salients'].push({
336
+ source_pipeline: 'reverse-salients',
337
+ source_node_id: pair.source_artifact_id,
338
+ target_node_id: pair.target_artifact_id,
339
+ score: Math.abs(Number(pair.signed_diff) || 0),
340
+ source_section: String(pair.source_section || ''),
341
+ target_section: String(pair.target_section || ''),
342
+ framework_chain: ['Reverse Salients'],
343
+ direction: pair.direction,
344
+ });
345
+ }
346
+
347
+ // Analogy zones -> 'cross-domain' bucket; apply Brain Section 8.3 gate + formula.
348
+ const analogyZones = Array.isArray(analogy.zones) ? analogy.zones : [];
349
+ for (const zone of analogyZones) {
350
+ if (!zone || typeof zone !== 'object') continue;
351
+ const cosine = Number(zone.relevance) || 0;
352
+ const sourceDomain = String(zone.source_domain || zone.source_section || '');
353
+ const targetDomain = String(zone.target_domain || zone.target_section || '');
354
+ if (!crossDomainGate(cosine, sourceDomain, targetDomain)) continue;
355
+ // Simple binary distance for v1: different_domains => 1.0; the gate already
356
+ // filtered out same-domain pairs. Future iterations may compute true distance
357
+ // from the embedding manifold but the formula stays similarity * domain_distance.
358
+ const domainDistance = (sourceDomain && targetDomain) ? 1.0 : 0;
359
+ const surprise = crossDomainSurprise(cosine, domainDistance);
360
+ buckets['cross-domain'].push({
361
+ source_pipeline: 'cross-domain',
362
+ source_node_id: zone.source_node_id,
363
+ target_node_id: zone.target_node_id,
364
+ score: surprise,
365
+ source_section: sourceDomain,
366
+ target_section: targetDomain,
367
+ framework_chain: ['Cross-Domain Analogy'],
368
+ });
369
+ }
370
+
371
+ // Dedup by (source_node_id, target_node_id) ACROSS BUCKETS -- pick max score.
372
+ // Preserves the canonical-order tag on whichever pipeline produced the higher
373
+ // score for that (src,tgt) tuple.
374
+ const dedup = new Map();
375
+ let totalCandidates = 0;
376
+ for (const tag of CANONICAL_CHAIN_ORDER) {
377
+ const arr = buckets[tag];
378
+ // Sort within bucket by score DESC (secondary axis).
379
+ arr.sort(function (a, b) { return Number(b.score) - Number(a.score); });
380
+ for (const c of arr) {
381
+ totalCandidates += 1;
382
+ const key = String(c.source_node_id) + '|' + String(c.target_node_id);
383
+ const existing = dedup.get(key);
384
+ if (!existing || Number(existing.score) < Number(c.score)) dedup.set(key, c);
385
+ }
386
+ }
387
+
388
+ const ranked = Array.from(dedup.values());
389
+ // Primary axis: pipeline tier (canonical order); secondary: score DESC.
390
+ ranked.sort(function (a, b) {
391
+ const ai = CANONICAL_CHAIN_ORDER.indexOf(a.source_pipeline);
392
+ const bi = CANONICAL_CHAIN_ORDER.indexOf(b.source_pipeline);
393
+ if (ai !== bi) return ai - bi;
394
+ return Number(b.score) - Number(a.score);
395
+ });
396
+
397
+ if (ranked.length === 0) return null;
398
+ const top = ranked[0];
399
+
400
+ // Deterministic finding id per Brain Section 8.4 finding-shape contract.
401
+ const idBasis = String(material_id) + '|' +
402
+ String(top.source_node_id) + '|' +
403
+ String(top.target_node_id) + '|' +
404
+ String(top.source_pipeline);
405
+ const id = crypto.createHash('sha256').update(idBasis).digest('hex').slice(0, 32);
406
+
407
+ // candidates_per_pipeline keys MUST exactly match CANONICAL_CHAIN_ORDER
408
+ // (test AUTOEXPLORE-117-13 #4: missing pipeline fills with empty array).
409
+ const candidates_per_pipeline = {};
410
+ for (const tag of CANONICAL_CHAIN_ORDER) candidates_per_pipeline[tag] = buckets[tag].length;
411
+
412
+ return {
413
+ id: id,
414
+ material_id: material_id,
415
+ source_pipeline: top.source_pipeline,
416
+ source_node_id: top.source_node_id,
417
+ target_node_id: (top.target_node_id === undefined) ? null : top.target_node_id,
418
+ score: top.score,
419
+ candidate_count: totalCandidates,
420
+ candidates_per_pipeline: candidates_per_pipeline,
421
+ // HSIAnalysis schema extension (Brain Section 8.4) -- populated in 117-03:
422
+ top_differential: null,
423
+ semantic_surprise: null,
424
+ category_errors_identified: [],
425
+ top_differential_score: null,
426
+ // Provenance (Canon Part 8: section names ONLY; NEVER user content fields):
427
+ source_section: top.source_section || '',
428
+ target_section: top.target_section || '',
429
+ framework_chain: Array.isArray(top.framework_chain) ? top.framework_chain.slice() : [],
430
+ generated_at: Date.now(),
431
+ };
432
+ }
433
+
434
+ // ---------- populateHSIAnalysis (Brain Section 8.4 schema population) ----------
435
+
436
+ /**
437
+ * populateHSIAnalysis(finding) -- populate Brain Section 8.4 HSIAnalysis schema
438
+ * fields from finding provenance. Wave 2 ships shape contract with nulls; this
439
+ * function fills them at render time so F.1 dispatch can apply the RECOMMENDED
440
+ * gate at top_differential_score >= 0.7 (Phase 88.2 invariant + AUTOEXPLORE-117-15).
441
+ *
442
+ * Per Brain Section 8.4 canonical HSIAnalysis property set:
443
+ * top_differential: string like "x * y: 0.985" (the top match)
444
+ * semantic_surprise: one-line reframe
445
+ * category_errors_identified: what the material was misframed as
446
+ * top_differential_score: 0-1 numeric; F.1 RECOMMENDED gate at >= 0.7
447
+ *
448
+ * Deterministic, idempotent (re-runs collapse to same shape). Never throws.
449
+ *
450
+ * @param {object} finding composeAutoExploreFinding output
451
+ * @returns {object} finding extended with 4 HSIAnalysis fields populated
452
+ */
453
+ function populateHSIAnalysis(finding) {
454
+ if (!finding || typeof finding !== 'object') return finding;
455
+ const score = Number(finding.score) || 0;
456
+ const sourceSec = String(finding.source_section || '');
457
+ const targetSec = String(finding.target_section || '');
458
+ // top_differential: render as "<source_section> * <target_section>: <score>"
459
+ // (no user-content fields per Canon Part 8).
460
+ const top_differential = (sourceSec && targetSec)
461
+ ? sourceSec + ' * ' + targetSec + ': ' + score.toFixed(3)
462
+ : (sourceSec ? sourceSec + ': ' + score.toFixed(3) : '<unspecified>: ' + score.toFixed(3));
463
+ // semantic_surprise: derived from source_pipeline tag (one-line reframe).
464
+ const reframeByPipeline = {
465
+ 'cross-domain': 'A non-obvious analogy across domains worth exploring',
466
+ 'reverse-salients': 'A lagging element constraining forward motion',
467
+ 'domain': 'A whitespace gap in the canonical decomposition',
468
+ 'trends': 'A trend signal reshaping the domain faster than adaptation',
469
+ };
470
+ const semantic_surprise = reframeByPipeline[finding.source_pipeline] || reframeByPipeline['domain'];
471
+ // category_errors_identified: framework_chain provenance (what we WERE framing it as).
472
+ const category_errors_identified = Array.isArray(finding.framework_chain)
473
+ ? finding.framework_chain.slice(0, 5)
474
+ : [];
475
+ // top_differential_score: normalized score [0,1]; clamp.
476
+ const top_differential_score = Math.max(0, Math.min(1, score));
477
+ return Object.assign({}, finding, {
478
+ top_differential: top_differential,
479
+ semantic_surprise: semantic_surprise,
480
+ category_errors_identified: category_errors_identified,
481
+ top_differential_score: top_differential_score,
482
+ });
483
+ }
484
+
485
+ // ---------- composeBQAnchoredLarryVoice (Brain Section 8.5 BQ render) ----------
486
+
487
+ /**
488
+ * composeBQAnchoredLarryVoice(finding, opts) -- Brain Section 8.5 BQ-anchored
489
+ * render. Uses BQ_TEMPLATE_REGISTRY keyed by source_pipeline tag.
490
+ *
491
+ * Per RESEARCH Section 8.5: F.1 line MUST be BQ-anchored, never raw technical
492
+ * match. AUTOEXPLORE-117-16 invariant.
493
+ * Generic (avoid): "Top match: weather_algorithm * synthetic_inertia (0.985)"
494
+ * BQ-anchored: "What if the deepest pattern here isn't 'wind power' but
495
+ * 'embodied algorithms providing computational stability'?"
496
+ *
497
+ * Future variant (deferred to Phase 110 Brain Context Packet Contract) may
498
+ * substitute a runtime Brain query for the local registry; the v1 ships the
499
+ * 4-key local constant.
500
+ *
501
+ * @param {object} finding populateHSIAnalysis output (or composeAutoExploreFinding output)
502
+ * @param {object} [opts] optional persona arg (v1 ignores per AUTOEXPLORE-117-16 test 7)
503
+ * @returns {string}
504
+ */
505
+ function composeBQAnchoredLarryVoice(finding, opts) {
506
+ if (!finding || typeof finding !== 'object') return '';
507
+ // opts reserved for future persona-blend variant (v1: ignored).
508
+ void opts;
509
+ const tag = String(finding.source_pipeline || 'domain');
510
+ const tmpl = BQ_TEMPLATE_REGISTRY[tag] || BQ_TEMPLATE_REGISTRY['domain'];
511
+ const categoryError = (Array.isArray(finding.category_errors_identified)
512
+ && finding.category_errors_identified.length > 0)
513
+ ? String(finding.category_errors_identified[0])
514
+ : 'the surface framing';
515
+ const semanticSurprise = String(finding.semantic_surprise || 'the deeper pattern');
516
+ const sourceSection = String(finding.source_section || 'this material');
517
+ const targetSection = String(finding.target_section || 'the rest of the room');
518
+ let line = tmpl.template;
519
+ line = line.split('{category_error}').join(categoryError);
520
+ line = line.split('{semantic_surprise}').join(semanticSurprise);
521
+ line = line.split('{source_section}').join(sourceSection);
522
+ line = line.split('{target_section}').join(targetSection);
523
+ return line;
524
+ }
525
+
526
+ // ---------- buildExploreApprovedEdge (cascade edge spec) ----------
527
+
528
+ /**
529
+ * buildExploreApprovedEdge -- INFORMS cascade edge spec on F.1 EXPLORE.
530
+ *
531
+ * Mirrors lib/agents/tension-hook-agent.cjs::buildResolvedViaEdge with type-swap
532
+ * RESOLVES_VIA -> INFORMS.
533
+ *
534
+ * properties.source = 'auto-explore' (distinct from rs-engine attribution).
535
+ *
536
+ * Returns null on missing source/target (defensive; caller passes through to
537
+ * lazygraph-ops.upsertEdge which has its own validation).
538
+ *
539
+ * @param {object} args
540
+ * @param {object} args.finding populateHSIAnalysis output
541
+ * @param {string} [args.materialNodeId] the uploaded artifact node id
542
+ * @param {string} [args.parent_decision_id] sha256-32 hex
543
+ * @returns {object|null}
544
+ */
545
+ function buildExploreApprovedEdge(args) {
546
+ const finding = (args && args.finding) || {};
547
+ const materialNodeId = String((args && args.materialNodeId) || '');
548
+ const target = String(finding.target_node_id || '');
549
+ const source = materialNodeId || String(finding.source_node_id || '');
550
+ if (!source || !target) return null;
551
+ return {
552
+ type: 'INFORMS',
553
+ source: source,
554
+ target: target,
555
+ properties: {
556
+ source: 'auto-explore',
557
+ material_id: String(finding.material_id || ''),
558
+ finding_id: String(finding.id || ''),
559
+ parent_decision_id: String((args && args.parent_decision_id) || finding.id || ''),
560
+ canonical_chain_pipeline: String(finding.source_pipeline || ''),
561
+ created_at: Date.now(),
562
+ },
563
+ };
564
+ }
565
+
566
+ // ---------- surfaceFinding (F.1 Decision Gate dispatch) ----------
567
+
568
+ /**
569
+ * surfaceFinding({finding, roomDir, operator, tier}) -- F.1 Decision Gate dispatch.
570
+ *
571
+ * Mirrors lib/agents/tension-hook-agent.cjs::surfaceFinding shape verbatim
572
+ * with verb-swap [Resolve/Later/Skip] -> [Explore/Skip/Later] and edge-type
573
+ * swap RESOLVES_VIA -> INFORMS.
574
+ *
575
+ * Suppression paths per Phase 88.2 + RESEARCH Section 5 scenarios 1, 9:
576
+ * tier===0 -> {surfaced:false, suppress_reason:'tier_0'}
577
+ * operator==='JUST_TALK' -> {surfaced:false, suppress_reason:'just_talk'}
578
+ * dispatcher load fail -> {surfaced:false, suppress_reason:'dispatcher_load_failed'}
579
+ * pickShape throws -> {surfaced:false, suppress_reason:'dispatch_threw:<err>'}
580
+ * pickShape unavailable -> {surfaced:false, suppress_reason:'pickShape_unavailable'}
581
+ *
582
+ * Side-effect: atomic re-persist enriched finding to room/.mindrian/auto-explore-<material_id>.json
583
+ * so post-hoc audit reading the on-disk JSON sees populated HSI fields, not nulls
584
+ * (W6 fix iteration 1).
585
+ *
586
+ * @param {object} args
587
+ * @param {object} args.finding composeAutoExploreFinding output
588
+ * @param {string} args.roomDir absolute path to room (with .room-root)
589
+ * @param {string} [args.operator] 'AUTONOMOUS' | 'JUST_TALK' | undefined
590
+ * @param {number} [args.tier] 0 | 1 | 2
591
+ * @returns {object} {surfaced: bool, contract?, suppress_reason?, finding?, bq_line?, parent_decision_id?}
592
+ */
593
+ function surfaceFinding(args) {
594
+ const finding = (args && args.finding) || null;
595
+ const roomDir = String((args && args.roomDir) || '');
596
+ const operator = String((args && args.operator) || 'AUTONOMOUS');
597
+ const tier = Number((args && args.tier) || 0);
598
+
599
+ if (!finding || !finding.id) {
600
+ return { surfaced: false, suppress_reason: 'invalid_finding' };
601
+ }
602
+ if (tier === 0) {
603
+ return { surfaced: false, suppress_reason: 'tier_0', finding: finding };
604
+ }
605
+ if (operator === 'JUST_TALK') {
606
+ return { surfaced: false, suppress_reason: 'just_talk', finding: finding };
607
+ }
608
+
609
+ // Populate HSIAnalysis schema fields (Brain Section 8.4) before dispatch.
610
+ const populated = populateHSIAnalysis(finding);
611
+
612
+ // W6 fix iteration 1: atomic re-persist enriched finding to
613
+ // room/.mindrian/auto-explore-<material_id>.json so post-hoc audit reading
614
+ // the on-disk JSON sees populated HSI fields, not nulls. populateHSIAnalysis
615
+ // is deterministic -- re-runs are idempotent -- but persisting closes the
616
+ // audit-trail gap. Use the same atomic-write pattern from
617
+ // explored-materials-store.cjs (write-temp + rename).
618
+ if (roomDir && populated.material_id) {
619
+ try {
620
+ const mindrianDir = path.join(roomDir, '.mindrian');
621
+ try { fs.mkdirSync(mindrianDir, { recursive: true }); } catch (_e) { /* graceful */ }
622
+ const findingPath = path.join(mindrianDir, 'auto-explore-' + populated.material_id + '.json');
623
+ const tmpPath = findingPath + '.tmp.' + process.pid;
624
+ fs.writeFileSync(tmpPath, JSON.stringify(populated, null, 2), 'utf8');
625
+ fs.renameSync(tmpPath, findingPath);
626
+ } catch (_e) { /* best-effort persistence; surface still proceeds */ }
627
+ }
628
+
629
+ // F.1 RECOMMENDED gate per Phase 88.2 invariant: only at >= 0.7.
630
+ const recommendedVerb = (Number(populated.top_differential_score) >= 0.7) ? 'Explore' : null;
631
+
632
+ // Compose BQ-anchored Larry-voice line (Brain Section 8.5).
633
+ const bqLine = composeBQAnchoredLarryVoice(populated);
634
+
635
+ // Compose parent_decision_id (deterministic over finding.id; Phase 88.2-05 pattern).
636
+ const parent_decision_id = String(finding.id);
637
+
638
+ let dispatcher;
639
+ try {
640
+ dispatcher = require('../hmi/selector-dispatcher.cjs');
641
+ } catch (_e) {
642
+ return { surfaced: false, suppress_reason: 'dispatcher_load_failed', finding: populated };
643
+ }
644
+ if (!dispatcher || typeof dispatcher.pickShape !== 'function') {
645
+ return { surfaced: false, suppress_reason: 'pickShape_unavailable', finding: populated };
646
+ }
647
+
648
+ let result;
649
+ try {
650
+ result = dispatcher.pickShape({
651
+ requestedShape: 'F.1',
652
+ roomDir: roomDir,
653
+ operator: operator,
654
+ tier: tier,
655
+ payload: {
656
+ verbs: ['Explore', 'Skip', 'Later'],
657
+ header: bqLine,
658
+ recommendedVerb: recommendedVerb,
659
+ emitTelemetry: true,
660
+ parent_decision_id: parent_decision_id,
661
+ },
662
+ });
663
+ } catch (err) {
664
+ const detail = String((err && err.message) || err).slice(0, 60);
665
+ return {
666
+ surfaced: false,
667
+ suppress_reason: 'dispatch_threw:' + detail,
668
+ finding: populated,
669
+ };
670
+ }
671
+
672
+ if (!result || result.shape === 'error') {
673
+ const errCode = (result && result.rendered && result.rendered.error)
674
+ ? String(result.rendered.error)
675
+ : 'pickShape_unavailable';
676
+ return {
677
+ surfaced: false,
678
+ suppress_reason: errCode,
679
+ finding: populated,
680
+ };
681
+ }
682
+
683
+ return {
684
+ surfaced: true,
685
+ contract: (result.rendered && result.rendered.contract) ? result.rendered.contract : (result.rendered || null),
686
+ rendered: result.rendered || null,
687
+ finding: populated,
688
+ bq_line: bqLine,
689
+ parent_decision_id: parent_decision_id,
690
+ };
691
+ }
692
+
693
+ // ---------- handleUserResponse (post-F.1 routing) ----------
694
+
695
+ /**
696
+ * handleUserResponse -- F.1 user-pick router.
697
+ *
698
+ * EXPLORE -> appendMaterial response='EXPLORE' + emit INFORMS cascade edge
699
+ * SKIP -> appendMaterial response='SKIP' (rejection captured per Canon Part 4 D-13)
700
+ * LATER -> appendMaterial response='LATER' (re-queue; surfacing_count NOT decremented)
701
+ * FREE_TEXT -> appendMaterial response='FREE_TEXT'; Larry interprets via Canon Part 3 Verb 10
702
+ *
703
+ * Wrapped in try/catch -- never throws. Returns scalar-only result envelope
704
+ * (no user-content fields) so the caller can mirror to telemetry safely.
705
+ *
706
+ * @param {object} args
707
+ * @param {object} args.finding populated finding object
708
+ * @param {string} args.userResponse 'EXPLORE'|'SKIP'|'LATER'|'FREE_TEXT' (case-insensitive)
709
+ * @param {string} args.roomDir absolute path to room
710
+ * @param {object} [args.db] node:sqlite DatabaseSync handle (for EXPLORE path)
711
+ * @param {string} [args.materialNodeId] explicit material node id (defaults to finding.source_node_id)
712
+ * @param {string} [args.parent_decision_id]
713
+ * @returns {object}
714
+ */
715
+ function handleUserResponse(args) {
716
+ try {
717
+ const finding = (args && args.finding) || {};
718
+ const userResponse = String((args && args.userResponse) || '').toUpperCase();
719
+ const roomDir = String((args && args.roomDir) || '');
720
+ const db = (args && args.db) || null;
721
+ const roomSlug = roomDir ? path.basename(roomDir) : 'default-room';
722
+
723
+ const valid = new Set(['EXPLORE', 'SKIP', 'LATER', 'FREE_TEXT']);
724
+ if (!valid.has(userResponse)) return { ok: false, reason: 'invalid_response' };
725
+
726
+ // Append responded entry to ledger (LWW).
727
+ const respondedEntry = {
728
+ material_id: String(finding.material_id || ''),
729
+ file_path_sha256: String(finding.file_path_sha256 || ''),
730
+ relative_file_path: '',
731
+ mtime_seconds: 0,
732
+ fired_at: Date.now(),
733
+ state: 'completed',
734
+ finding_count: 1,
735
+ surfaced: true,
736
+ user_response: userResponse,
737
+ responded_at: Date.now(),
738
+ suppress_reason: null,
739
+ in_flight_since: null,
740
+ };
741
+ let appendResult = null;
742
+ try {
743
+ appendResult = store.appendMaterial(roomSlug, respondedEntry);
744
+ } catch (_e) { /* graceful */ }
745
+
746
+ let edgeResult = null;
747
+ if (userResponse === 'EXPLORE' && db) {
748
+ // Emit INFORMS cascade edge per Canon Part 4.
749
+ const edgeSpec = buildExploreApprovedEdge({
750
+ finding: finding,
751
+ materialNodeId: (args && args.materialNodeId) || finding.source_node_id,
752
+ parent_decision_id: (args && args.parent_decision_id) || finding.id,
753
+ });
754
+ if (edgeSpec) {
755
+ try {
756
+ const lazygraph = require('../core/lazygraph-ops.cjs');
757
+ if (typeof lazygraph.upsertEdge === 'function') {
758
+ edgeResult = lazygraph.upsertEdge(db, edgeSpec);
759
+ }
760
+ } catch (_e) { /* graceful: edge emission best-effort */ }
761
+ }
762
+ }
763
+
764
+ // Phase 117-05 telemetry: emit auto_explore_user_response per F.1 verb.
765
+ // Single event with response field discriminator (DELTA vs Phase 116-04
766
+ // which used separate emitResolved/Skipped/Decayed events).
767
+ try {
768
+ emitUserResponse(roomDir, {
769
+ finding: finding,
770
+ response: userResponse,
771
+ latency_ms: 0,
772
+ reason_present: false,
773
+ });
774
+ } catch (_e) { /* never throw on telemetry */ }
775
+
776
+ return {
777
+ ok: true,
778
+ response: userResponse,
779
+ jsonl: appendResult,
780
+ edge: edgeResult,
781
+ };
782
+ } catch (e) {
783
+ return {
784
+ ok: false,
785
+ reason: 'handle_response_threw',
786
+ detail: String((e && e.message) || '').slice(0, 80),
787
+ };
788
+ }
789
+ }
790
+
791
+ // ---------- Telemetry emit helpers (Phase 117-05 Wave 3) ----------
792
+ //
793
+ // Per RESEARCH Section 11: scalar-only payloads. Per Canon Part 8: zero
794
+ // user-content strings ever leave LOCAL state in event payloads. Each helper
795
+ // delegates to lib/hmi/selector-telemetry.cjs::recordSelectorMirror which
796
+ // writes BOTH (a) a memory_event row in room.db (graph-side) AND (b) a JSONL
797
+ // line at ~/.mindrian/telemetry/selector.jsonl (Phase 121 corpus).
798
+ //
799
+ // Mirrors lib/agents/tension-hook-agent.cjs lines 416-528 (Phase 116-04
800
+ // pattern) verbatim with field substitutions per RESEARCH Section 3 sibling
801
+ // code-clone. DELTA vs Phase 116-04: 116 ships 5 helpers (Detected / Surfaced
802
+ // / Resolved / Decayed / Skipped); 117 ships 6 (Fired / FindingSurfaced /
803
+ // UserResponse / Skipped / SanitizerHit / BrainCanonDrift). The drift event
804
+ // is NEW per Brain Section 8.6 (FourLenses-vs-FiveLenses asymmetry); the
805
+ // sanitizer-hit event is NEW per 117-04 SEED-003 A3 sanitizer.
806
+
807
+ let _telemetryRef;
808
+ function _getTelemetry() {
809
+ if (_telemetryRef !== undefined) return _telemetryRef;
810
+ try { _telemetryRef = require('../hmi/selector-telemetry.cjs'); }
811
+ catch (_e) { _telemetryRef = null; }
812
+ return _telemetryRef;
813
+ }
814
+
815
+ function _hashShort(str, len) {
816
+ const n = Number.isInteger(len) && len > 0 ? len : 16;
817
+ return crypto.createHash('sha256').update(String(str || '')).digest('hex').slice(0, n);
818
+ }
819
+
820
+ /**
821
+ * emitFired -- auto_explore_fired memory_event.
822
+ *
823
+ * Fired by scripts/auto-explore-fingerprint.cjs immediately before spawning
824
+ * the detached fire child. Records material_id + file_path_sha256 (16-hex)
825
+ * + room_slug_sha256 (16-hex) + tier + brain_baseline_present + suppress_reason.
826
+ */
827
+ function emitFired(roomDir, args) {
828
+ try {
829
+ const telemetry = _getTelemetry();
830
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
831
+ return { ok: false, reason: 'telemetry_module_unavailable' };
832
+ }
833
+ const a = (args && typeof args === 'object') ? args : {};
834
+ const slugSource = a.room_slug || (roomDir ? path.basename(roomDir) : '');
835
+ const payload = {
836
+ material_id: String(a.material_id || ''),
837
+ file_path_sha256: _hashShort(a.relative_file_path || '', 16),
838
+ fired_at: Date.now(),
839
+ room_slug_sha256: _hashShort(slugSource, 16),
840
+ surfacing_count: Number(a.surfacing_count) || 0,
841
+ tier: Number(a.tier) || 0,
842
+ suppress_reason: (a.suppress_reason === null || a.suppress_reason === undefined)
843
+ ? null : String(a.suppress_reason),
844
+ brain_baseline_present: Boolean(a.brain_baseline_present),
845
+ created_at: Date.now(),
846
+ };
847
+ return telemetry.recordSelectorMirror(roomDir, 'auto_explore_fired', payload);
848
+ } catch (_e) {
849
+ return { ok: false, reason: 'fired_telemetry_threw' };
850
+ }
851
+ }
852
+
853
+ /**
854
+ * emitFindingSurfaced -- auto_explore_finding_surfaced memory_event.
855
+ *
856
+ * Fired by scripts/auto-explore-drain.cjs after surfaceFinding returns
857
+ * surfaced=true. Records finding_id + source_pipeline + candidate_count +
858
+ * score + top_differential_score scalar.
859
+ */
860
+ function emitFindingSurfaced(roomDir, args) {
861
+ try {
862
+ const telemetry = _getTelemetry();
863
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
864
+ return { ok: false, reason: 'telemetry_module_unavailable' };
865
+ }
866
+ const a = (args && typeof args === 'object') ? args : {};
867
+ const finding = (a.finding && typeof a.finding === 'object') ? a.finding : {};
868
+ const payload = {
869
+ material_id: String(finding.material_id || ''),
870
+ finding_id: String(finding.id || ''),
871
+ source_pipeline: String(finding.source_pipeline || ''),
872
+ candidate_count: Number(finding.candidate_count) || 0,
873
+ score: Number(finding.score) || 0,
874
+ surfacing_count: Number(a.surfacing_count) || 1,
875
+ tier: Number(a.tier) || 0,
876
+ top_differential_score: Number(finding.top_differential_score) || 0,
877
+ created_at: Date.now(),
878
+ };
879
+ return telemetry.recordSelectorMirror(roomDir, 'auto_explore_finding_surfaced', payload);
880
+ } catch (_e) {
881
+ return { ok: false, reason: 'finding_surfaced_telemetry_threw' };
882
+ }
883
+ }
884
+
885
+ /**
886
+ * emitUserResponse -- auto_explore_user_response memory_event.
887
+ *
888
+ * Fired by handleUserResponse for each F.1 verb pick (EXPLORE / SKIP / LATER
889
+ * / FREE_TEXT). Records response discriminator + latency + reason_present.
890
+ */
891
+ function emitUserResponse(roomDir, args) {
892
+ try {
893
+ const telemetry = _getTelemetry();
894
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
895
+ return { ok: false, reason: 'telemetry_module_unavailable' };
896
+ }
897
+ const a = (args && typeof args === 'object') ? args : {};
898
+ const finding = (a.finding && typeof a.finding === 'object') ? a.finding : {};
899
+ const valid = new Set(['EXPLORE', 'SKIP', 'LATER', 'FREE_TEXT']);
900
+ const respRaw = String(a.response || '').toUpperCase();
901
+ const payload = {
902
+ material_id: String(finding.material_id || ''),
903
+ finding_id: String(finding.id || ''),
904
+ response: valid.has(respRaw) ? respRaw : 'FREE_TEXT',
905
+ latency_ms: Number.isFinite(a.latency_ms) ? Math.max(0, Math.floor(a.latency_ms)) : 0,
906
+ reason_present: Boolean(a.reason_present),
907
+ created_at: Date.now(),
908
+ };
909
+ return telemetry.recordSelectorMirror(roomDir, 'auto_explore_user_response', payload);
910
+ } catch (_e) {
911
+ return { ok: false, reason: 'user_response_telemetry_threw' };
912
+ }
913
+ }
914
+
915
+ /**
916
+ * emitSkipped -- auto_explore_skipped memory_event.
917
+ *
918
+ * Fired by every suppress path in fingerprint + fire (Tier 0, just_talk,
919
+ * dispatcher_load_failed, all_pipelines_empty, brain_baseline_unavailable,
920
+ * rate_limited, daily_cap_exceeded, room_dir_not_writable, ledger_replay_failed,
921
+ * hybrid_mode_insufficient_corpus, sanitizer_blocked).
922
+ */
923
+ function emitSkipped(roomDir, args) {
924
+ try {
925
+ const telemetry = _getTelemetry();
926
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
927
+ return { ok: false, reason: 'telemetry_module_unavailable' };
928
+ }
929
+ const a = (args && typeof args === 'object') ? args : {};
930
+ const payload = {
931
+ material_id: String(a.material_id || ''),
932
+ suppress_reason: String(a.suppress_reason || 'unknown'),
933
+ tier: Number(a.tier) || 0,
934
+ surfacing_count: Number(a.surfacing_count) || 0,
935
+ created_at: Date.now(),
936
+ };
937
+ return telemetry.recordSelectorMirror(roomDir, 'auto_explore_skipped', payload);
938
+ } catch (_e) {
939
+ return { ok: false, reason: 'skipped_telemetry_threw' };
940
+ }
941
+ }
942
+
943
+ /**
944
+ * emitSanitizerHit -- auto_explore_sanitizer_hit memory_event.
945
+ *
946
+ * Fired by scripts/brain-response-sanitize-hook.cjs when sanitize() modifies
947
+ * the input (i.e. at least one PII pattern matched and was redacted). One
948
+ * emit per pattern that matched (multi-emit per response if multiple
949
+ * patterns). tool_name is hashed; pattern_name is enum scalar.
950
+ */
951
+ function emitSanitizerHit(roomDir, args) {
952
+ try {
953
+ const telemetry = _getTelemetry();
954
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
955
+ return { ok: false, reason: 'telemetry_module_unavailable' };
956
+ }
957
+ const a = (args && typeof args === 'object') ? args : {};
958
+ const validPatterns = new Set(['ssn', 'email', 'phone', 'money', 'iso_date', 'abs_path']);
959
+ const patternRaw = String(a.pattern_name || 'unknown');
960
+ const payload = {
961
+ tool_name_hash: _hashShort(a.tool_name || '', 16),
962
+ pattern_name: validPatterns.has(patternRaw) ? patternRaw : 'unknown',
963
+ redaction_count: Math.max(1, Number(a.redaction_count) || 1),
964
+ created_at: Date.now(),
965
+ };
966
+ return telemetry.recordSelectorMirror(roomDir, 'auto_explore_sanitizer_hit', payload);
967
+ } catch (_e) {
968
+ return { ok: false, reason: 'sanitizer_hit_telemetry_threw' };
969
+ }
970
+ }
971
+
972
+ /**
973
+ * emitBrainCanonDrift -- brain_canon_drift_observed memory_event (Brain Section 8.6).
974
+ *
975
+ * Per AUTOEXPLORE-117-18: Brain stores FourLenses (Gibson Innovation,
976
+ * framework: 'Torqox_Innovation_Lenses'); Canon Part 2 Engine 1 v1.3
977
+ * canonizes FiveLenses (Disciplinary, Stakeholder, System, Temporal, Scale).
978
+ * The drift is detectable signal -- recording it locally surfaces it to
979
+ * Phase 121 audit corpus. NO Brain write-back attempted (Canon Part 8
980
+ * LOCAL -> BRAIN forbidden). Idempotent within session via in-memory cache.
981
+ */
982
+ let _driftEmittedThisSession = false;
983
+
984
+ function emitBrainCanonDrift(roomDir) {
985
+ try {
986
+ if (_driftEmittedThisSession) {
987
+ return { ok: true, skipped: 'already_emitted_this_session' };
988
+ }
989
+ _driftEmittedThisSession = true;
990
+ const telemetry = _getTelemetry();
991
+ if (!telemetry || typeof telemetry.recordSelectorMirror !== 'function') {
992
+ return { ok: false, reason: 'telemetry_module_unavailable' };
993
+ }
994
+ const payload = {
995
+ axis: 'lens_count',
996
+ brain_count: 4,
997
+ canon_count: 5,
998
+ phase: '117',
999
+ detected_at: Date.now(),
1000
+ created_at: Date.now(),
1001
+ };
1002
+ return telemetry.recordSelectorMirror(roomDir, 'brain_canon_drift_observed', payload);
1003
+ } catch (_e) {
1004
+ return { ok: false, reason: 'drift_telemetry_threw' };
1005
+ }
1006
+ }
1007
+
1008
+ /**
1009
+ * _resetDriftCacheForTests -- test-only helper to clear the in-session
1010
+ * idempotency cache. NOT exported in production module surface; consumed
1011
+ * via require-cache reload pattern in test files.
1012
+ */
1013
+ function _resetDriftCacheForTests() {
1014
+ _driftEmittedThisSession = false;
1015
+ }
1016
+
1017
+ // ---------- Module exports ----------
1018
+
1019
+ module.exports = {
1020
+ detectFirstMaterial,
1021
+ composeAutoExploreFinding,
1022
+ surfaceFinding,
1023
+ handleUserResponse,
1024
+ populateHSIAnalysis,
1025
+ composeBQAnchoredLarryVoice,
1026
+ buildExploreApprovedEdge,
1027
+ crossDomainSurprise,
1028
+ crossDomainGate,
1029
+ // Phase 117-05 Wave 3 telemetry emit helpers (6 total):
1030
+ emitFired,
1031
+ emitFindingSurfaced,
1032
+ emitUserResponse,
1033
+ emitSkipped,
1034
+ emitSanitizerHit,
1035
+ emitBrainCanonDrift,
1036
+ // Constants:
1037
+ BQ_TEMPLATE_REGISTRY,
1038
+ CANONICAL_CHAIN_ORDER,
1039
+ CROSS_DOMAIN_THRESHOLD,
1040
+ MATERIAL_ID_LEN,
1041
+ // Test-only helpers (underscore prefix; not part of public contract):
1042
+ _resetDriftCacheForTests,
1043
+ };