@mindrian_os/cli 2.0.0-beta.3 → 2.0.0-beta.33

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 (1829) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +14 -0
  3. package/CHANGELOG.md +497 -0
  4. package/agents/analogy-query-fetcher.md +119 -0
  5. package/agents/brain-query.md +91 -0
  6. package/agents/competitor-watch-fetcher.md +94 -0
  7. package/agents/framework-runner.md +258 -0
  8. package/agents/grading.md +216 -0
  9. package/agents/grant-reviewer.md +191 -0
  10. package/agents/investor.md +141 -0
  11. package/agents/larry-extended.md +215 -0
  12. package/agents/meeting-perspective-extractor.md +94 -0
  13. package/agents/opportunity-scanner.md +105 -0
  14. package/agents/persona-analyst.md +201 -0
  15. package/agents/research.md +103 -0
  16. package/agents/reverse-salient-agent.md +42 -0
  17. package/agents/vault-section-reviewer.md +107 -0
  18. package/assets/banner-showcase.html +537 -0
  19. package/assets/fonts/BebasNeue-Regular.ttf +0 -0
  20. package/assets/fonts/Inter-Bold.ttf +0 -0
  21. package/assets/fonts/Inter-Regular.ttf +0 -0
  22. package/assets/logo.svg +10 -0
  23. package/bin/local-chain-recommender.cjs +256 -0
  24. package/bin/mindrian-brain-mcp-client.cjs +319 -0
  25. package/bin/mindrian-mcp-server.cjs +445 -0
  26. package/bin/mindrian-mcp-shim.cjs +100 -0
  27. package/bin/mindrian-tools.cjs +784 -0
  28. package/commands/act.md +529 -0
  29. package/commands/admin.md +439 -0
  30. package/commands/agentshield.md +49 -0
  31. package/commands/analyze-needs.md +78 -0
  32. package/commands/analyze-systems.md +73 -0
  33. package/commands/analyze-timing.md +98 -0
  34. package/commands/auto-explore.md +85 -0
  35. package/commands/beautiful-question.md +76 -0
  36. package/commands/bono.md +152 -0
  37. package/commands/brain-derive.md +137 -0
  38. package/commands/build-knowledge.md +121 -0
  39. package/commands/build-thesis.md +94 -0
  40. package/commands/causal.md +269 -0
  41. package/commands/challenge-assumptions.md +78 -0
  42. package/commands/compare-ventures.md +117 -0
  43. package/commands/correct-reference-now.md +88 -0
  44. package/commands/dashboard.md +144 -0
  45. package/commands/deck.md +117 -0
  46. package/commands/deep-grade.md +214 -0
  47. package/commands/diagnose.md +131 -0
  48. package/commands/diagnostics.md +195 -0
  49. package/commands/dial-memory-refresh.md +113 -0
  50. package/commands/diffusion.md +129 -0
  51. package/commands/discover.md +221 -0
  52. package/commands/doctor.md +271 -0
  53. package/commands/dogfood-flush.md +130 -0
  54. package/commands/dominant-designs.md +76 -0
  55. package/commands/eureka.md +315 -0
  56. package/commands/explain-decision.md +135 -0
  57. package/commands/explore-domains.md +78 -0
  58. package/commands/explore-futures.md +67 -0
  59. package/commands/explore-opportunity.md +172 -0
  60. package/commands/explore-trends.md +78 -0
  61. package/commands/export.md +128 -0
  62. package/commands/feynman-timeline-refresh.md +102 -0
  63. package/commands/file-meeting.md +1195 -0
  64. package/commands/find-analogies.md +369 -0
  65. package/commands/find-bottlenecks.md +108 -0
  66. package/commands/find-connections.md +111 -0
  67. package/commands/funding.md +114 -0
  68. package/commands/futures.md +128 -0
  69. package/commands/grade-grant.md +297 -0
  70. package/commands/grade.md +257 -0
  71. package/commands/graph.md +218 -0
  72. package/commands/hat-briefing.md +163 -0
  73. package/commands/heal.md +72 -0
  74. package/commands/help.md +176 -0
  75. package/commands/hmi-status.md +75 -0
  76. package/commands/ignite.md +229 -0
  77. package/commands/ingest-methodology.md +128 -0
  78. package/commands/intel-pipeline.md +133 -0
  79. package/commands/jtbd.md +262 -0
  80. package/commands/leadership.md +113 -0
  81. package/commands/lean-canvas.md +74 -0
  82. package/commands/macro-trends.md +88 -0
  83. package/commands/map-unknowns.md +76 -0
  84. package/commands/memory-cortex-reach.md +79 -0
  85. package/commands/memory.md +213 -0
  86. package/commands/models.md +200 -0
  87. package/commands/mos-reason.md +489 -0
  88. package/commands/mos.md +162 -0
  89. package/commands/mullins.md +155 -0
  90. package/commands/mva-brief.md +86 -0
  91. package/commands/mva-option.md +119 -0
  92. package/commands/mva-report.md +76 -0
  93. package/commands/new-project.md +633 -0
  94. package/commands/new-surface.md +116 -0
  95. package/commands/onboard.md +489 -0
  96. package/commands/operator.md +168 -0
  97. package/commands/opportunities.md +177 -0
  98. package/commands/organize.md +74 -0
  99. package/commands/persona.md +276 -0
  100. package/commands/pipeline.md +167 -0
  101. package/commands/present.md +125 -0
  102. package/commands/publish.md +227 -0
  103. package/commands/pws-brain.md +154 -0
  104. package/commands/qualify-opportunity.md +113 -0
  105. package/commands/query.md +70 -0
  106. package/commands/radar.md +110 -0
  107. package/commands/reanalyze.md +124 -0
  108. package/commands/research.md +467 -0
  109. package/commands/room.md +385 -0
  110. package/commands/rooms.md +682 -0
  111. package/commands/root-cause.md +77 -0
  112. package/commands/rs-experts.md +153 -0
  113. package/commands/rs-explain.md +134 -0
  114. package/commands/rs-fetch.md +119 -0
  115. package/commands/rs-thesis.md +131 -0
  116. package/commands/scenario-plan.md +80 -0
  117. package/commands/scheduled-tasks.md +308 -0
  118. package/commands/score-innovation.md +78 -0
  119. package/commands/scout.md +379 -0
  120. package/commands/setup.md +658 -0
  121. package/commands/show.md +71 -0
  122. package/commands/skill.md +108 -0
  123. package/commands/snapshot.md +179 -0
  124. package/commands/speakers.md +118 -0
  125. package/commands/splash.md +55 -0
  126. package/commands/stance.md +91 -0
  127. package/commands/status.md +109 -0
  128. package/commands/structure-argument.md +82 -0
  129. package/commands/suggest-next.md +141 -0
  130. package/commands/systems-thinking.md +224 -0
  131. package/commands/think-hats.md +86 -0
  132. package/commands/trending-to-absurd.md +151 -0
  133. package/commands/update.md +256 -0
  134. package/commands/user-needs.md +77 -0
  135. package/commands/validate.md +77 -0
  136. package/commands/value-proposition.md +116 -0
  137. package/commands/vault.md +330 -0
  138. package/commands/visualize.md +71 -0
  139. package/commands/whitespace.md +541 -0
  140. package/commands/wiki.md +96 -0
  141. package/data/ROOM.md +47 -0
  142. package/data/brain-census.generated.json +2617 -0
  143. package/data/brain-orchestration-projection.json +4973 -0
  144. package/data/brain-packet-schema.json +545 -0
  145. package/data/brain-surface-contract.json +32 -0
  146. package/data/capability-ledger.json +391 -0
  147. package/data/capability-map-registry.json +23 -0
  148. package/data/command-registry.json +2433 -0
  149. package/data/connector-coverage-ledger.json +1618 -0
  150. package/data/connector-out-of-spine-allowlist.json +71 -0
  151. package/data/connector-registry.json +3637 -0
  152. package/data/corpus-stats-source.json +11 -0
  153. package/data/corpus-stats.generated.json +10 -0
  154. package/data/cross-domain-analogues.json +15 -0
  155. package/data/deck-aliases.json +6 -0
  156. package/data/deck-styles.json +85 -0
  157. package/data/deployment-surfaces.json +69 -0
  158. package/data/dispatch-framework-map.json +18 -0
  159. package/data/doctor-modules.json +206 -0
  160. package/data/dual-graph-health-baseline.json +12 -0
  161. package/data/eureka-critic-tags.json +32 -0
  162. package/data/first-reward-surfaces.json +66 -0
  163. package/data/first-touch-surfaces.json +60 -0
  164. package/data/flagship-floor-set.json +37 -0
  165. package/data/framework-names.json +121 -0
  166. package/data/grant-rubric-fixtures/early-stage-pre-seed.json +18 -0
  167. package/data/grant-rubric-fixtures/early-stage-round-a.json +18 -0
  168. package/data/grant-rubric-fixtures/early-stage-seed.json +18 -0
  169. package/data/grant-rubric-fixtures/hybrid-seed-incentive.json +18 -0
  170. package/data/grant-rubric-fixtures/rd-fund.json +18 -0
  171. package/data/grant-rubric-fixtures/regional-innovation-incubators.json +18 -0
  172. package/data/grant-rubric-fixtures/technology-incubators.json +18 -0
  173. package/data/grant-rubric-fixtures/tnufa.json +167 -0
  174. package/data/grant-rubric-schema.json +33 -0
  175. package/data/harness-fixtures/converged-room/.context/ROOM.md +13 -0
  176. package/data/harness-fixtures/converged-room/.intelligence/ROOM.md +13 -0
  177. package/data/harness-fixtures/converged-room/.snapshots/ROOM.md +13 -0
  178. package/data/harness-fixtures/converged-room/MINTO.md +19 -0
  179. package/data/harness-fixtures/converged-room/STATE.md +73 -0
  180. package/data/harness-fixtures/converged-room/USER.md +23 -0
  181. package/data/harness-fixtures/converged-room/assets/ROOM.md +13 -0
  182. package/data/harness-fixtures/converged-room/business-model/CONTEXT.md +32 -0
  183. package/data/harness-fixtures/converged-room/business-model/ROOM.md +36 -0
  184. package/data/harness-fixtures/converged-room/competitive-analysis/CONTEXT.md +33 -0
  185. package/data/harness-fixtures/converged-room/competitive-analysis/ROOM.md +35 -0
  186. package/data/harness-fixtures/converged-room/financial-model/CONTEXT.md +30 -0
  187. package/data/harness-fixtures/converged-room/financial-model/ROOM.md +35 -0
  188. package/data/harness-fixtures/converged-room/funding/CONTEXT.md +41 -0
  189. package/data/harness-fixtures/converged-room/funding/ROOM.md +35 -0
  190. package/data/harness-fixtures/converged-room/legal-ip/CONTEXT.md +30 -0
  191. package/data/harness-fixtures/converged-room/legal-ip/ROOM.md +35 -0
  192. package/data/harness-fixtures/converged-room/market-analysis/CONTEXT.md +32 -0
  193. package/data/harness-fixtures/converged-room/market-analysis/ROOM.md +35 -0
  194. package/data/harness-fixtures/converged-room/opportunity-bank/CONTEXT.md +39 -0
  195. package/data/harness-fixtures/converged-room/opportunity-bank/ROOM.md +36 -0
  196. package/data/harness-fixtures/converged-room/problem-definition/CONTEXT.md +32 -0
  197. package/data/harness-fixtures/converged-room/problem-definition/ROOM.md +36 -0
  198. package/data/harness-fixtures/converged-room/references/ROOM.md +13 -0
  199. package/data/harness-fixtures/converged-room/references/SECTION-SCHEMA.md +213 -0
  200. package/data/harness-fixtures/converged-room/references/SUB-SCHEMAS.md +91 -0
  201. package/data/harness-fixtures/converged-room/solution-design/CONTEXT.md +36 -0
  202. package/data/harness-fixtures/converged-room/solution-design/ROOM.md +36 -0
  203. package/data/harness-fixtures/converged-room/strategy/CONTEXT.md +35 -0
  204. package/data/harness-fixtures/converged-room/strategy/ROOM.md +35 -0
  205. package/data/harness-fixtures/converged-room/team/ROOM.md +13 -0
  206. package/data/harness-fixtures/converged-room/team-execution/CONTEXT.md +36 -0
  207. package/data/harness-fixtures/converged-room/team-execution/ROOM.md +35 -0
  208. package/data/harness-manifest.json +74 -0
  209. package/data/harness-policies/CONTEXT.md +55 -0
  210. package/data/harness-policies/_schema.json +42 -0
  211. package/data/harness-policies/contract-parity-larry.json +63 -0
  212. package/data/harness-policies/gate-card-fire.json +13 -0
  213. package/data/harness-policies/gate-graph-derive-health.json +13 -0
  214. package/data/harness-policies/gate-shape-declaration.json +13 -0
  215. package/data/harness-policies/gate-tool-honesty.json +13 -0
  216. package/data/harness-policies/gate-worktree-hygiene.json +13 -0
  217. package/data/harness-policies/memory-write-policy.json +28 -0
  218. package/data/harness-policies/registry-drift.json +13 -0
  219. package/data/harness-policies/release-payload-ceiling.json +13 -0
  220. package/data/harness-policies/voice-backend-noun-free.json +13 -0
  221. package/data/harness-policies/voice-fork-as-card.json +13 -0
  222. package/data/harness-policies/voice-glyph-present.json +13 -0
  223. package/data/harness-policies/voice-hyphens-only.json +13 -0
  224. package/data/help-groups.json +236 -0
  225. package/data/hitl-shape-backfill.json +777 -0
  226. package/data/hitl-shape-declaration-fixtures/invalid-bad-shape.json +5 -0
  227. package/data/hitl-shape-declaration-fixtures/invalid-missing-why.json +4 -0
  228. package/data/hitl-shape-declaration-fixtures/invalid-ranker-contradiction.json +6 -0
  229. package/data/hitl-shape-declaration-fixtures/invalid-skill-gap.json +3 -0
  230. package/data/hitl-shape-declaration-fixtures/valid-multi-stage.json +7 -0
  231. package/data/hitl-shape-declaration-fixtures/valid-single-shape.json +5 -0
  232. package/data/hitl-shape-declaration-fixtures/valid-skill-exempt.json +5 -0
  233. package/data/hitl-shape-declaration-schema.json +34 -0
  234. package/data/hitl-stages-fixtures/bono.json +10 -0
  235. package/data/hitl-stages-fixtures/pipeline-1.json +9 -0
  236. package/data/hitl-stages-fixtures/pipeline-2.json +9 -0
  237. package/data/hitl-stages-fixtures/pipeline-3.json +10 -0
  238. package/data/hitl-stages-fixtures/research.json +10 -0
  239. package/data/hitl-stages-fixtures/rs-convergence.json +10 -0
  240. package/data/hitl-stages-fixtures/rs-family.json +11 -0
  241. package/data/hitl-stages-fixtures/rs-reverse-salient.json +10 -0
  242. package/data/hitl-stages-fixtures/trending-to-absurd.json +11 -0
  243. package/data/hitl-stages-schema.json +26 -0
  244. package/data/mcp-tool-connectors.json +464 -0
  245. package/data/mva-agent-prompts.json +31 -0
  246. package/data/mva-deck-template.html +15 -0
  247. package/data/mva-heuristic-keywords.json +35 -0
  248. package/data/orchestration-command-ledger.json +576 -0
  249. package/data/orchestration-unwired-allowlist.json +6 -0
  250. package/data/portfolio-ahp-matrix.json +16 -0
  251. package/data/publish-needs.json +76 -0
  252. package/data/render-coverage-registry.json +1152 -0
  253. package/data/research-sources.json +58 -0
  254. package/data/roadmap-type-chains.json +28 -0
  255. package/data/room-blueprints.json +156 -0
  256. package/data/subagent-dispatch-grants.json +150 -0
  257. package/hooks/hooks.json +493 -0
  258. package/hooks/run-hook.cmd +64 -0
  259. package/lib/__init__.py +0 -0
  260. package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  261. package/lib/agents/auto-explore-agent.cjs +1163 -0
  262. package/lib/agents/mva/brain-classic-traps.cjs +55 -0
  263. package/lib/agents/mva/brain-cross-domain.cjs +79 -0
  264. package/lib/agents/mva/brain-similar-ventures.cjs +93 -0
  265. package/lib/agents/mva/dashboard-graph-neighborhood.cjs +72 -0
  266. package/lib/agents/mva/index.cjs +42 -0
  267. package/lib/agents/mva/six-hats-red-black.cjs +137 -0
  268. package/lib/agents/mva/tavily-funding-scan.cjs +147 -0
  269. package/lib/agents/mva/test-all-six-agents.cjs +467 -0
  270. package/lib/agents/reverse-salient-agent.cjs +757 -0
  271. package/lib/agents/tension-hook-agent.cjs +586 -0
  272. package/lib/brain/ROOM.md +44 -0
  273. package/lib/brain/chain-recommender.cjs +784 -0
  274. package/lib/brain/curation-batch.cjs +519 -0
  275. package/lib/brain/framework-chain-slice.cjs +252 -0
  276. package/lib/brain/hypergraph-event-schema.cjs +184 -0
  277. package/lib/chat/chat-context.js +185 -0
  278. package/lib/chat/chat-panel.js +721 -0
  279. package/lib/chat/fabric-chat.cjs +288 -0
  280. package/lib/chat/generative-tools.js +219 -0
  281. package/lib/conversation/ROOM.md +39 -0
  282. package/lib/conversation/classifier-rules.json +38 -0
  283. package/lib/conversation/classifier.cjs +264 -0
  284. package/lib/conversation/operator-epistemic-cap.test.cjs +156 -0
  285. package/lib/conversation/operator.cjs +492 -0
  286. package/lib/conversation/operator.test.cjs +160 -0
  287. package/lib/copy/115-spec-strings.cjs +55 -0
  288. package/lib/core/__init__.py +0 -0
  289. package/lib/core/__nav-stub.cjs +14 -0
  290. package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
  291. package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
  292. package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
  293. package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
  294. package/lib/core/__pycache__/rs_corpus_exclude.cpython-312.pyc +0 -0
  295. package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
  296. package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
  297. package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
  298. package/lib/core/abstraction-gate.cjs +133 -0
  299. package/lib/core/act-jtbd-blurb.cjs +231 -0
  300. package/lib/core/artifact-id.cjs +148 -0
  301. package/lib/core/asset-ops.cjs +151 -0
  302. package/lib/core/auto-commit-throttle.cjs +129 -0
  303. package/lib/core/bearer-token.cjs +199 -0
  304. package/lib/core/behavioral/observation-schema.cjs +86 -0
  305. package/lib/core/bono/cell-fanout.cjs +305 -0
  306. package/lib/core/bono/debate-composition.cjs +495 -0
  307. package/lib/core/bono/hat-governance.cjs +367 -0
  308. package/lib/core/bono/persona-research.cjs +288 -0
  309. package/lib/core/bono/reviewer-governance.cjs +331 -0
  310. package/lib/core/brain-client.cjs +2621 -0
  311. package/lib/core/brain-derivation-prompts.cjs +331 -0
  312. package/lib/core/brain-derivation-queue.cjs +535 -0
  313. package/lib/core/brain-derivation.cjs +740 -0
  314. package/lib/core/brain-md-schema.cjs +528 -0
  315. package/lib/core/brain-md-staleness.cjs +357 -0
  316. package/lib/core/brain-response-sanitize.cjs +396 -0
  317. package/lib/core/breakthrough/canary.cjs +134 -0
  318. package/lib/core/breakthrough/canary.test.cjs +136 -0
  319. package/lib/core/breakthrough/detectors.cjs +359 -0
  320. package/lib/core/breakthrough/detectors.test.cjs +333 -0
  321. package/lib/core/breakthrough/ethics-fence.cjs +129 -0
  322. package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
  323. package/lib/core/breakthrough/resurfacing.cjs +150 -0
  324. package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
  325. package/lib/core/breakthrough/review-queue.cjs +168 -0
  326. package/lib/core/breakthrough/review-queue.test.cjs +160 -0
  327. package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
  328. package/lib/core/breakthrough/scanner.cjs +435 -0
  329. package/lib/core/breakthrough/scanner.test.cjs +267 -0
  330. package/lib/core/breakthrough/schema.cjs +180 -0
  331. package/lib/core/breakthrough/schema.test.cjs +256 -0
  332. package/lib/core/breakthrough/scoring.cjs +293 -0
  333. package/lib/core/breakthrough/scoring.test.cjs +423 -0
  334. package/lib/core/breakthrough/verb-dispatch.cjs +230 -0
  335. package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
  336. package/lib/core/breakthrough/voice-scaffold.cjs +249 -0
  337. package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
  338. package/lib/core/bridge-writer.cjs +477 -0
  339. package/lib/core/cache-prune.cjs +314 -0
  340. package/lib/core/card-fire-gate.cjs +107 -0
  341. package/lib/core/card-fire-sidechannel.cjs +518 -0
  342. package/lib/core/chain-executor.cjs +939 -0
  343. package/lib/core/chain-retry.cjs +214 -0
  344. package/lib/core/chain-step-dispatcher.cjs +397 -0
  345. package/lib/core/chat-context-builder.cjs +253 -0
  346. package/lib/core/check-plugin-enabled.cjs +145 -0
  347. package/lib/core/close-loop-writer.cjs +492 -0
  348. package/lib/core/command-registration-check.cjs +246 -0
  349. package/lib/core/correlation-label-index.cjs +230 -0
  350. package/lib/core/correlation.cjs +112 -0
  351. package/lib/core/coverage-monitor.cjs +183 -0
  352. package/lib/core/coverage-rollup.cjs +232 -0
  353. package/lib/core/cross-room-aggregator.cjs +1127 -0
  354. package/lib/core/cross-room-store.cjs +292 -0
  355. package/lib/core/cross-room-triggers.cjs +222 -0
  356. package/lib/core/daily-briefing.cjs +438 -0
  357. package/lib/core/decision-axes.cjs +178 -0
  358. package/lib/core/decision-capture.cjs +618 -0
  359. package/lib/core/deck-design-rules.cjs +211 -0
  360. package/lib/core/deep-links.cjs +82 -0
  361. package/lib/core/directive-envelope.cjs +230 -0
  362. package/lib/core/directive-envelope.test.cjs +225 -0
  363. package/lib/core/dispatch-optimizer.cjs +354 -0
  364. package/lib/core/doc-text-extractor.cjs +133 -0
  365. package/lib/core/doctor/capability-ledger-module.cjs +170 -0
  366. package/lib/core/doctor/card-fire-health-module.cjs +159 -0
  367. package/lib/core/doctor/cascade-rooms-active-module.cjs +127 -0
  368. package/lib/core/doctor/cascade-rooms-module.cjs +184 -0
  369. package/lib/core/doctor/class-m-brain-smoke.cjs +479 -0
  370. package/lib/core/doctor/class-m-brain-smoke.test.cjs +448 -0
  371. package/lib/core/doctor/class-s-eureka-smoke.cjs +380 -0
  372. package/lib/core/doctor/deployment-surfaces-module.cjs +408 -0
  373. package/lib/core/doctor/deprecated-usage-module.cjs +155 -0
  374. package/lib/core/doctor/eureka-fts-health-module.cjs +236 -0
  375. package/lib/core/doctor/graph-derive-heal-retrofit-module.cjs +148 -0
  376. package/lib/core/doctor/graph-derive-health-module.cjs +486 -0
  377. package/lib/core/doctor/install-incomplete-module.cjs +320 -0
  378. package/lib/core/doctor/install-state-module.cjs +503 -0
  379. package/lib/core/doctor/mcp-surface-module.cjs +369 -0
  380. package/lib/core/doctor/mode-select-checkpoint-module.cjs +113 -0
  381. package/lib/core/doctor/plugin-enabled-state-module.cjs +86 -0
  382. package/lib/core/doctor/room-graph-density-module.cjs +181 -0
  383. package/lib/core/doctor/room-md-module.cjs +198 -0
  384. package/lib/core/doctor/shared.cjs +555 -0
  385. package/lib/core/doctor/stale-first-touch-copy-module.cjs +83 -0
  386. package/lib/core/doctor/statusline-visibility-module.cjs +295 -0
  387. package/lib/core/doctor/ui-compliance-module.cjs +223 -0
  388. package/lib/core/doctor/umbilical-module.cjs +486 -0
  389. package/lib/core/doctor/verify-surface-module.cjs +82 -0
  390. package/lib/core/doctor/voice-style-log-module.cjs +146 -0
  391. package/lib/core/domain-insight-sweep.cjs +224 -0
  392. package/lib/core/drift-baseline.cjs +362 -0
  393. package/lib/core/drift-runtime-reachability.cjs +283 -0
  394. package/lib/core/dual-path-detector.cjs +84 -0
  395. package/lib/core/dual-path-detector.test.cjs +334 -0
  396. package/lib/core/enrichment-queue.cjs +636 -0
  397. package/lib/core/eureka/ahp-weights.cjs +232 -0
  398. package/lib/core/eureka/analogy-fitness.cjs +326 -0
  399. package/lib/core/eureka/compression-meter.cjs +165 -0
  400. package/lib/core/eureka/embedding-classifier.cjs +226 -0
  401. package/lib/core/eureka/embedding-spine.cjs +563 -0
  402. package/lib/core/eureka/entity-classifier.cjs +232 -0
  403. package/lib/core/eureka/entity-extractor.cjs +356 -0
  404. package/lib/core/eureka/eureka-enable.cjs +228 -0
  405. package/lib/core/eureka/eureka-offer.cjs +225 -0
  406. package/lib/core/eureka/eureka-reach-runner.cjs +430 -0
  407. package/lib/core/eureka/explore-chain.cjs +834 -0
  408. package/lib/core/eureka/explored-artifact.cjs +456 -0
  409. package/lib/core/eureka/fts-index-lifecycle.cjs +265 -0
  410. package/lib/core/eureka/grade-grant-examine.cjs +544 -0
  411. package/lib/core/eureka/grade-grant.cjs +596 -0
  412. package/lib/core/eureka/hybrid-retrieve.cjs +256 -0
  413. package/lib/core/eureka/lateral-engine-adapter.cjs +118 -0
  414. package/lib/core/eureka/lexical-overlap.cjs +98 -0
  415. package/lib/core/eureka/online-pattern-query.cjs +142 -0
  416. package/lib/core/eureka/opportunity-harvest.cjs +752 -0
  417. package/lib/core/eureka/opportunity-statement.cjs +519 -0
  418. package/lib/core/eureka/portfolio-dimensions.cjs +255 -0
  419. package/lib/core/eureka/qualify-opportunity.cjs +607 -0
  420. package/lib/core/eureka/reasoning-mode.cjs +481 -0
  421. package/lib/core/eureka/report-html.cjs +368 -0
  422. package/lib/core/eureka/research-filing.cjs +547 -0
  423. package/lib/core/eureka/room-native-substrate.cjs +343 -0
  424. package/lib/core/eureka/tail-quadrant.cjs +177 -0
  425. package/lib/core/eureka/tri-modal-index.cjs +564 -0
  426. package/lib/core/eureka/vector-store.cjs +414 -0
  427. package/lib/core/eureka-critic.cjs +733 -0
  428. package/lib/core/eureka-deps-resolver.cjs +179 -0
  429. package/lib/core/expert-genesis.cjs +437 -0
  430. package/lib/core/expert-library.cjs +303 -0
  431. package/lib/core/exports-log.cjs +79 -0
  432. package/lib/core/feynman/ROOM.md +25 -0
  433. package/lib/core/feynman/dial-memory-renderer.cjs +279 -0
  434. package/lib/core/feynman/dial-memory-runner.cjs +173 -0
  435. package/lib/core/feynman/feynman-seed-writer.cjs +187 -0
  436. package/lib/core/feynman/timeline-renderer.cjs +197 -0
  437. package/lib/core/feynman/timeline-runner.cjs +281 -0
  438. package/lib/core/feynman-minto-invariants.cjs +609 -0
  439. package/lib/core/findings-wirer.cjs +360 -0
  440. package/lib/core/first-touch-version-stamper.cjs +113 -0
  441. package/lib/core/folder-memory-async.cjs +424 -0
  442. package/lib/core/folder-memory-shared.cjs +890 -0
  443. package/lib/core/folder-memory.cjs +546 -0
  444. package/lib/core/framework-chain-composer.cjs +583 -0
  445. package/lib/core/frontmatter-schemas.cjs +468 -0
  446. package/lib/core/fusion-router.cjs +540 -0
  447. package/lib/core/futures/causal-cue.cjs +91 -0
  448. package/lib/core/futures/orchestrator.cjs +1391 -0
  449. package/lib/core/futures/subsystem-render.cjs +164 -0
  450. package/lib/core/gate-relevance.cjs +278 -0
  451. package/lib/core/git-ops.cjs +141 -0
  452. package/lib/core/graph-backfill.cjs +562 -0
  453. package/lib/core/graph-candidate-producer.cjs +235 -0
  454. package/lib/core/graph-derivation.cjs +546 -0
  455. package/lib/core/graph-derive-classifier.cjs +352 -0
  456. package/lib/core/graph-ops.cjs +274 -0
  457. package/lib/core/graph-refine-loop.cjs +194 -0
  458. package/lib/core/graph-self-heal.cjs +289 -0
  459. package/lib/core/greeting-intent-detector.cjs +201 -0
  460. package/lib/core/grill-engine.cjs +459 -0
  461. package/lib/core/hat-persistence.cjs +274 -0
  462. package/lib/core/hsi-engine.cjs +439 -0
  463. package/lib/core/hsi-lsa.cjs +246 -0
  464. package/lib/core/hsi-spectral.cjs +554 -0
  465. package/lib/core/hsi-to-graph.test.cjs +168 -0
  466. package/lib/core/icm-forest.cjs +703 -0
  467. package/lib/core/ignite-branch-gate.cjs +66 -0
  468. package/lib/core/index.cjs +60 -0
  469. package/lib/core/insight-sensors.cjs +1000 -0
  470. package/lib/core/install-state.cjs +242 -0
  471. package/lib/core/integration-registry.cjs +232 -0
  472. package/lib/core/intel-pipeline.cjs +566 -0
  473. package/lib/core/intelligence-cascade.cjs +852 -0
  474. package/lib/core/issue-tree.cjs +269 -0
  475. package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
  476. package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
  477. package/lib/core/lazygraph-ops.cjs +1453 -0
  478. package/lib/core/lens-engine.cjs +434 -0
  479. package/lib/core/leverage-scan.cjs +218 -0
  480. package/lib/core/llm-name-suggester.cjs +194 -0
  481. package/lib/core/llm-name-suggester.test.cjs +132 -0
  482. package/lib/core/lru-cache.cjs +139 -0
  483. package/lib/core/mcp-dep-heal.cjs +475 -0
  484. package/lib/core/mcp-dep-heal.test.cjs +253 -0
  485. package/lib/core/mcp-profiles.cjs +185 -0
  486. package/lib/core/meeting-ops.cjs +54 -0
  487. package/lib/core/memory/governance-candidate-raiser.cjs +205 -0
  488. package/lib/core/memory/governance-candidate.cjs +95 -0
  489. package/lib/core/memory/reconcile-memory-runner.cjs +653 -0
  490. package/lib/core/memory-ops.cjs +641 -0
  491. package/lib/core/meter/gate-density-reader.cjs +112 -0
  492. package/lib/core/meter/transfer-reader.cjs +276 -0
  493. package/lib/core/meter/two-gauge.cjs +245 -0
  494. package/lib/core/methodology-ingest.cjs +346 -0
  495. package/lib/core/metrics/larryreach-loop-health.cjs +142 -0
  496. package/lib/core/migration-snapshot.cjs +287 -0
  497. package/lib/core/migration-snapshot.test.cjs +174 -0
  498. package/lib/core/migrations/ROOM.md +33 -0
  499. package/lib/core/migrations/phase-109-nodes-provenance.cjs +400 -0
  500. package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
  501. package/lib/core/migrations/phase-160-nodes-bitemporal.cjs +168 -0
  502. package/lib/core/migrations/phase-160-nodes-bitemporal.test.cjs +140 -0
  503. package/lib/core/migrations/phase-162-section-nodes.cjs +156 -0
  504. package/lib/core/migrations/phase-222-ranker-weights.cjs +62 -0
  505. package/lib/core/migrations/phase-224-edge-review-status.cjs +87 -0
  506. package/lib/core/mindrian-brain-shim.test.cjs +221 -0
  507. package/lib/core/mode-select-sidechannel.cjs +259 -0
  508. package/lib/core/model-profiles.cjs +246 -0
  509. package/lib/core/mullins-scaffold.cjs +258 -0
  510. package/lib/core/mullins-scaffold.test.cjs +128 -0
  511. package/lib/core/mva-agent-contract.cjs +170 -0
  512. package/lib/core/mva-agent-contract.test.cjs +169 -0
  513. package/lib/core/mva-budget.cjs +75 -0
  514. package/lib/core/mva-budget.test.cjs +68 -0
  515. package/lib/core/mva-classifier.cjs +392 -0
  516. package/lib/core/mva-classifier.test.cjs +248 -0
  517. package/lib/core/mva-deck-builder.cjs +452 -0
  518. package/lib/core/mva-deck-builder.test.cjs +287 -0
  519. package/lib/core/mva-detect.smoke.test.cjs +209 -0
  520. package/lib/core/mva-dispatcher.cjs +110 -0
  521. package/lib/core/mva-dispatcher.test.cjs +216 -0
  522. package/lib/core/mva-option-router.cjs +414 -0
  523. package/lib/core/mva-option-router.test.cjs +483 -0
  524. package/lib/core/mva-orchestrator.cjs +365 -0
  525. package/lib/core/mva-orchestrator.test.cjs +916 -0
  526. package/lib/core/mva-progressive-renderer.cjs +194 -0
  527. package/lib/core/mva-progressive-renderer.test.cjs +157 -0
  528. package/lib/core/mva-rule-linter.cjs +458 -0
  529. package/lib/core/mva-rule-linter.test.cjs +484 -0
  530. package/lib/core/mva-state.cjs +159 -0
  531. package/lib/core/mva-telemetry.cjs +58 -0
  532. package/lib/core/mva-telemetry.test.cjs +196 -0
  533. package/lib/core/mva-vercel-deploy.cjs +168 -0
  534. package/lib/core/mva-vercel-deploy.test.cjs +239 -0
  535. package/lib/core/nav-dial.cjs +440 -0
  536. package/lib/core/navigation/ROOM.md +15 -0
  537. package/lib/core/navigation/abstraction-claim.cjs +185 -0
  538. package/lib/core/navigation/calibration-gate.cjs +284 -0
  539. package/lib/core/navigation/calibration-log.cjs +141 -0
  540. package/lib/core/navigation/confirm-node.cjs +73 -0
  541. package/lib/core/navigation/dashboard-helpers.cjs +145 -0
  542. package/lib/core/navigation/edges.cjs +1150 -0
  543. package/lib/core/navigation/evidence-claim.cjs +186 -0
  544. package/lib/core/navigation/explanation.cjs +48 -0
  545. package/lib/core/navigation/file-evidence-readback.cjs +242 -0
  546. package/lib/core/navigation/focus.cjs +155 -0
  547. package/lib/core/navigation/get-domains-for-trends.cjs +277 -0
  548. package/lib/core/navigation/governance.cjs +101 -0
  549. package/lib/core/navigation/grant-rubric.cjs +218 -0
  550. package/lib/core/navigation/graph-export.cjs +325 -0
  551. package/lib/core/navigation/ingestion.cjs +109 -0
  552. package/lib/core/navigation/insights-stale.test.cjs +127 -0
  553. package/lib/core/navigation/insights.cjs +574 -0
  554. package/lib/core/navigation/lens-nodes.cjs +315 -0
  555. package/lib/core/navigation/memory-artifacts.cjs +437 -0
  556. package/lib/core/navigation/memory-cortex-packet.cjs +274 -0
  557. package/lib/core/navigation/memory-events.cjs +811 -0
  558. package/lib/core/navigation/neighborhood.cjs +79 -0
  559. package/lib/core/navigation/packet.cjs +420 -0
  560. package/lib/core/navigation/planning-artifacts.cjs +219 -0
  561. package/lib/core/navigation/projections.cjs +295 -0
  562. package/lib/core/navigation/ranker-weights.cjs +133 -0
  563. package/lib/core/navigation/reasoning-write.cjs +211 -0
  564. package/lib/core/navigation/reconcile-guard.cjs +98 -0
  565. package/lib/core/navigation/reified-claim.cjs +287 -0
  566. package/lib/core/navigation/research-preflight.cjs +156 -0
  567. package/lib/core/navigation/room-birth.cjs +1213 -0
  568. package/lib/core/navigation/room-context.cjs +380 -0
  569. package/lib/core/navigation/room-home.cjs +142 -0
  570. package/lib/core/navigation/spine-events.cjs +548 -0
  571. package/lib/core/navigation/synthetic-expert.cjs +177 -0
  572. package/lib/core/navigation/transitions.cjs +253 -0
  573. package/lib/core/navigation/typed-claim.cjs +208 -0
  574. package/lib/core/navigation/typed-domain.cjs +249 -0
  575. package/lib/core/navigation/typed-entity.cjs +236 -0
  576. package/lib/core/navigation/typed-frame.cjs +257 -0
  577. package/lib/core/navigation/typed-open-question.cjs +130 -0
  578. package/lib/core/navigation/typed-opportunity.cjs +418 -0
  579. package/lib/core/navigation-engine-offer.cjs +776 -0
  580. package/lib/core/navigation-engine-shared.cjs +256 -0
  581. package/lib/core/navigation-engine.cjs +1696 -0
  582. package/lib/core/navigation.cjs +686 -0
  583. package/lib/core/nl-graph-queries.cjs +164 -0
  584. package/lib/core/node-insert-epistemic.test.cjs +212 -0
  585. package/lib/core/node-insert-overrides.test.cjs +273 -0
  586. package/lib/core/node-insert.cjs +274 -0
  587. package/lib/core/npm-cli-resolve.cjs +151 -0
  588. package/lib/core/npm-cli-resolve.test.cjs +153 -0
  589. package/lib/core/npm-install-lock.cjs +326 -0
  590. package/lib/core/npm-install-lock.test.cjs +379 -0
  591. package/lib/core/numeric/sklearn-stopwords-v1.json +325 -0
  592. package/lib/core/numeric/svd.cjs +377 -0
  593. package/lib/core/numeric/tfidf.cjs +241 -0
  594. package/lib/core/offer-presenter.cjs +406 -0
  595. package/lib/core/opportunity-extractor.cjs +183 -0
  596. package/lib/core/opportunity-ops.cjs +1423 -0
  597. package/lib/core/orchestration-candidate-lift.cjs +284 -0
  598. package/lib/core/part8-egress-guard.cjs +563 -0
  599. package/lib/core/part8-egress-guard.test.cjs +194 -0
  600. package/lib/core/part8-egress-ontology.cjs +174 -0
  601. package/lib/core/part8-egress-ontology.test.cjs +133 -0
  602. package/lib/core/persona-ops.cjs +692 -0
  603. package/lib/core/persona-override.cjs +327 -0
  604. package/lib/core/persona-taxonomy.cjs +328 -0
  605. package/lib/core/pinecone-inference.cjs +176 -0
  606. package/lib/core/pitch-feedback-schemas.cjs +303 -0
  607. package/lib/core/planning/artifact-brain-packet.cjs +212 -0
  608. package/lib/core/planning/reconcile-runner.cjs +347 -0
  609. package/lib/core/platform-gates.cjs +120 -0
  610. package/lib/core/platform.cjs +257 -0
  611. package/lib/core/proactive-intelligence.cjs +528 -0
  612. package/lib/core/problem-type-router.cjs +315 -0
  613. package/lib/core/publish-needs-default-lane.cjs +110 -0
  614. package/lib/core/ralph-loop-gate.cjs +48 -0
  615. package/lib/core/reader/ab-harness.cjs +177 -0
  616. package/lib/core/reader/decide-projection-reader.cjs +358 -0
  617. package/lib/core/reasoning-ops.cjs +639 -0
  618. package/lib/core/recipe-maps.cjs +442 -0
  619. package/lib/core/recovery/case-file.cjs +358 -0
  620. package/lib/core/recovery/controller.cjs +1008 -0
  621. package/lib/core/recovery/dispatcher.cjs +533 -0
  622. package/lib/core/recovery/result-semantics.cjs +488 -0
  623. package/lib/core/recovery/stage-envelope.cjs +338 -0
  624. package/lib/core/refusal-messaging.cjs +569 -0
  625. package/lib/core/refusal-messaging.test.cjs +220 -0
  626. package/lib/core/repo-version.cjs +134 -0
  627. package/lib/core/research-cache.cjs +165 -0
  628. package/lib/core/research-cache.test.cjs +137 -0
  629. package/lib/core/research-context-extractor.cjs +283 -0
  630. package/lib/core/research-corpus.cjs +814 -0
  631. package/lib/core/research-corpus.test.cjs +280 -0
  632. package/lib/core/research-filing-selector.cjs +189 -0
  633. package/lib/core/resolve-active-room.cjs +602 -0
  634. package/lib/core/resolve-brain-key.cjs +273 -0
  635. package/lib/core/resolve-umbilical-target.cjs +288 -0
  636. package/lib/core/resolve-vercel-key.cjs +107 -0
  637. package/lib/core/resolve-vercel-key.test.cjs +137 -0
  638. package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
  639. package/lib/core/room-auto-create.cjs +318 -0
  640. package/lib/core/room-auto-create.test.cjs +198 -0
  641. package/lib/core/room-chooser.cjs +244 -0
  642. package/lib/core/room-classifier-strict-mode.cjs +235 -0
  643. package/lib/core/room-db.cjs +348 -0
  644. package/lib/core/room-discard-cascade.cjs +238 -0
  645. package/lib/core/room-discard-cascade.test.cjs +135 -0
  646. package/lib/core/room-name-validator.cjs +132 -0
  647. package/lib/core/room-name-validator.test.cjs +156 -0
  648. package/lib/core/room-naming-selector.cjs +357 -0
  649. package/lib/core/room-naming-selector.test.cjs +277 -0
  650. package/lib/core/room-open.cjs +259 -0
  651. package/lib/core/room-ops-async.cjs +92 -0
  652. package/lib/core/room-ops-shared.cjs +64 -0
  653. package/lib/core/room-ops-sync.cjs +70 -0
  654. package/lib/core/room-ops.cjs +32 -0
  655. package/lib/core/room-receipt-emit.cjs +63 -0
  656. package/lib/core/room-root.cjs +93 -0
  657. package/lib/core/room-skeleton-scaffold.cjs +669 -0
  658. package/lib/core/room-skeleton-scaffold.test.cjs +296 -0
  659. package/lib/core/room-type-detector.cjs +386 -0
  660. package/lib/core/rs-backend-dispatch.cjs +64 -0
  661. package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
  662. package/lib/core/rs-brain-substrate.cjs +570 -0
  663. package/lib/core/rs-breakthrough-scorer.cjs +255 -0
  664. package/lib/core/rs-canon-violations.cjs +82 -0
  665. package/lib/core/rs-chain-feeder.cjs +381 -0
  666. package/lib/core/rs-commercial-assessor.cjs +280 -0
  667. package/lib/core/rs-corpus-quality-gate.cjs +62 -0
  668. package/lib/core/rs-differential-scorer.cjs +629 -0
  669. package/lib/core/rs-domain-analyzer.cjs +385 -0
  670. package/lib/core/rs-egress-prompts.cjs +113 -0
  671. package/lib/core/rs-egress-telemetry.cjs +225 -0
  672. package/lib/core/rs-egress-violations.cjs +53 -0
  673. package/lib/core/rs-engine.cjs +597 -0
  674. package/lib/core/rs-expert-brain-projection.cjs +280 -0
  675. package/lib/core/rs-expert-mapper.cjs +467 -0
  676. package/lib/core/rs-fetcher-academic.cjs +697 -0
  677. package/lib/core/rs-fetcher-experts.cjs +314 -0
  678. package/lib/core/rs-fetcher-industry.cjs +731 -0
  679. package/lib/core/rs-fetcher-patents.cjs +564 -0
  680. package/lib/core/rs-innovation-classifier.cjs +194 -0
  681. package/lib/core/rs-math.cjs +458 -0
  682. package/lib/core/rs-mind-map.cjs +656 -0
  683. package/lib/core/rs-neo4j-writer.cjs +388 -0
  684. package/lib/core/rs-nl-to-query.cjs +435 -0
  685. package/lib/core/rs-pinecone-bridge.cjs +344 -0
  686. package/lib/core/rs-preprocessor.cjs +350 -0
  687. package/lib/core/rs-query-matrix.cjs +316 -0
  688. package/lib/core/rs-query-to-text.cjs +438 -0
  689. package/lib/core/rs-sqlite-mirror.cjs +495 -0
  690. package/lib/core/rs-thesis-generator.cjs +188 -0
  691. package/lib/core/rs_cache.py +635 -0
  692. package/lib/core/rs_corpus.py +562 -0
  693. package/lib/core/rs_corpus_exclude.py +62 -0
  694. package/lib/core/rs_hybrid.py +589 -0
  695. package/lib/core/rs_math.py +287 -0
  696. package/lib/core/rs_rooms.py +196 -0
  697. package/lib/core/salient-governance.cjs +315 -0
  698. package/lib/core/scheduled-scanner.cjs +658 -0
  699. package/lib/core/scratchpad-ops.cjs +316 -0
  700. package/lib/core/seam-liveness.cjs +191 -0
  701. package/lib/core/seam-liveness.test.cjs +177 -0
  702. package/lib/core/section-8-trace-schema.cjs +138 -0
  703. package/lib/core/section-registry.cjs +167 -0
  704. package/lib/core/security/agentshield-adapters.test.cjs +104 -0
  705. package/lib/core/security/agentshield-run.cjs +343 -0
  706. package/lib/core/security/agentshield-scanner.cjs +314 -0
  707. package/lib/core/security/agentshield-scanner.test.cjs +259 -0
  708. package/lib/core/sensors/hat-scoping-table.cjs +103 -0
  709. package/lib/core/sensors/sensor-circularity.cjs +278 -0
  710. package/lib/core/sensors/sensor-content-relevance.cjs +407 -0
  711. package/lib/core/sensors/sensor-diffusion-adoption.cjs +230 -0
  712. package/lib/core/sensors/sensor-eureka.cjs +208 -0
  713. package/lib/core/sensors/sensor-expert-skill.cjs +293 -0
  714. package/lib/core/sensors/sensor-external-fact.cjs +184 -0
  715. package/lib/core/sensors/sensor-gate-approach.cjs +104 -0
  716. package/lib/core/sensors/sensor-jtbd-reweight.cjs +141 -0
  717. package/lib/core/sensors/sensor-lagging-component.cjs +115 -0
  718. package/lib/core/sensors/sensor-memory-cortex.cjs +111 -0
  719. package/lib/core/sensors/sensor-methodology-decision.cjs +131 -0
  720. package/lib/core/sensors/sensor-opportunity-harvest.cjs +195 -0
  721. package/lib/core/sensors/sensor-perspective-lock.cjs +174 -0
  722. package/lib/core/sensors/sensor-priority.cjs +183 -0
  723. package/lib/core/sensors/sensor-recency.cjs +151 -0
  724. package/lib/core/sensors/sensor-roadmap-type.cjs +421 -0
  725. package/lib/core/sensors/sensor-room-pick.cjs +238 -0
  726. package/lib/core/sensors/sensor-show-share.cjs +215 -0
  727. package/lib/core/sensors/sensor-temporal-blindness.cjs +123 -0
  728. package/lib/core/sensors/sensor-types.cjs +294 -0
  729. package/lib/core/sensors/sensor-url-ingest.cjs +234 -0
  730. package/lib/core/session-binding.cjs +208 -0
  731. package/lib/core/session-presence.cjs +404 -0
  732. package/lib/core/session-register.cjs +138 -0
  733. package/lib/core/session-state.cjs +144 -0
  734. package/lib/core/shallow-doc-parser.cjs +353 -0
  735. package/lib/core/shallow-doc-parser.test.cjs +226 -0
  736. package/lib/core/skill-activation-router.cjs +284 -0
  737. package/lib/core/skillopt-schemas.cjs +509 -0
  738. package/lib/core/stale-copy-scanner.cjs +190 -0
  739. package/lib/core/stance-state.cjs +153 -0
  740. package/lib/core/state-aware-router.cjs +78 -0
  741. package/lib/core/state-ops.cjs +80 -0
  742. package/lib/core/state-version.cjs +350 -0
  743. package/lib/core/statusline-cache.cjs +266 -0
  744. package/lib/core/statusline-liveness-gate.cjs +105 -0
  745. package/lib/core/surface-fence.cjs +127 -0
  746. package/lib/core/synthesizers/comparison-matrix.cjs +82 -0
  747. package/lib/core/synthesizers/convergence-map.cjs +107 -0
  748. package/lib/core/synthesizers/domain-hierarchy.cjs +138 -0
  749. package/lib/core/synthesizers/source-comparison.cjs +56 -0
  750. package/lib/core/synthesizers/tension-map.cjs +119 -0
  751. package/lib/core/synthetic-expert-behavior-gate.cjs +99 -0
  752. package/lib/core/synthetic-expert-construction-gate.cjs +170 -0
  753. package/lib/core/telemetry/schema.cjs +168 -0
  754. package/lib/core/telemetry/schema.test.cjs +124 -0
  755. package/lib/core/telemetry/validator.cjs +200 -0
  756. package/lib/core/telemetry/validator.test.cjs +188 -0
  757. package/lib/core/telemetry/writer.cjs +141 -0
  758. package/lib/core/telemetry/writer.test.cjs +331 -0
  759. package/lib/core/temporal/date-sync-gate.cjs +256 -0
  760. package/lib/core/temporal/date-sync-gate.test.cjs +191 -0
  761. package/lib/core/temporal/dual-stamp.cjs +172 -0
  762. package/lib/core/temporal/dual-stamp.test.cjs +172 -0
  763. package/lib/core/temporal/point-in-time.cjs +76 -0
  764. package/lib/core/temporal/point-in-time.test.cjs +169 -0
  765. package/lib/core/temporal/recency-decay.cjs +119 -0
  766. package/lib/core/temporal/recency-decay.test.cjs +140 -0
  767. package/lib/core/temporal/reference-now.cjs +281 -0
  768. package/lib/core/temporal/reference-now.test.cjs +225 -0
  769. package/lib/core/temporal/resolve-relative-time.cjs +116 -0
  770. package/lib/core/temporal/resolve-relative-time.test.cjs +138 -0
  771. package/lib/core/temporal/supersession.cjs +250 -0
  772. package/lib/core/temporal/supersession.test.cjs +103 -0
  773. package/lib/core/terminal-capability.cjs +88 -0
  774. package/lib/core/token-estimator.cjs +348 -0
  775. package/lib/core/tone-filter.cjs +147 -0
  776. package/lib/core/trending-to-absurd/orchestrator.cjs +341 -0
  777. package/lib/core/trending-to-absurd/stage7-roadmap.cjs +317 -0
  778. package/lib/core/trending-to-absurd/variance.cjs +169 -0
  779. package/lib/core/unknowns/bandit.cjs +332 -0
  780. package/lib/core/unknowns/corpus-adapter.cjs +327 -0
  781. package/lib/core/unknowns/dsp.cjs +356 -0
  782. package/lib/core/unknowns/edge-writer.cjs +211 -0
  783. package/lib/core/unknowns/iface.cjs +212 -0
  784. package/lib/core/unknowns/orchestrator.cjs +375 -0
  785. package/lib/core/unknowns/pattern-miner.cjs +214 -0
  786. package/lib/core/unknowns/rumsfeld-matrix.cjs +155 -0
  787. package/lib/core/unknowns/verdict.cjs +254 -0
  788. package/lib/core/update-path.cjs +57 -0
  789. package/lib/core/url-ingest.cjs +568 -0
  790. package/lib/core/user-archetype.cjs +284 -0
  791. package/lib/core/user-md-ops.cjs +578 -0
  792. package/lib/core/venture-shape-nudge.cjs +178 -0
  793. package/lib/core/venture-shape-nudge.test.cjs +266 -0
  794. package/lib/core/verb-reach-affinity.cjs +299 -0
  795. package/lib/core/visual-ops.cjs +692 -0
  796. package/lib/core/voice-transition-detector.cjs +125 -0
  797. package/lib/core/watched-sources.cjs +237 -0
  798. package/lib/core/write-lock.cjs +149 -0
  799. package/lib/graph/canvas-graph.js +467 -0
  800. package/lib/graph/constellation-config.cjs +299 -0
  801. package/lib/graph/graph-detail-panel.js +165 -0
  802. package/lib/hmi/ROOM.md +47 -0
  803. package/lib/hmi/across-session-memory.cjs +669 -0
  804. package/lib/hmi/agentshield-drift-gate.cjs +142 -0
  805. package/lib/hmi/brain-review-packet.cjs +221 -0
  806. package/lib/hmi/cortex-reach-adapter.cjs +271 -0
  807. package/lib/hmi/cross-room-memory.cjs +618 -0
  808. package/lib/hmi/decoy-tier.cjs +395 -0
  809. package/lib/hmi/depth-state.cjs +164 -0
  810. package/lib/hmi/dial-label-composer.cjs +410 -0
  811. package/lib/hmi/dial-presenter.cjs +508 -0
  812. package/lib/hmi/dial-reach-orchestrator.cjs +377 -0
  813. package/lib/hmi/dial-selector.cjs +239 -0
  814. package/lib/hmi/f1-pick-capture-cli.cjs +131 -0
  815. package/lib/hmi/f3-depth-capture-cli.cjs +77 -0
  816. package/lib/hmi/f4-scope-capture-cli.cjs +86 -0
  817. package/lib/hmi/f8-action-capture-cli.cjs +145 -0
  818. package/lib/hmi/f8-capture.test.cjs +77 -0
  819. package/lib/hmi/f9-ordered-capture-cli.cjs +138 -0
  820. package/lib/hmi/harvest-scope-state.cjs +202 -0
  821. package/lib/hmi/hats-persona-cache.cjs +142 -0
  822. package/lib/hmi/jtbd-classifier.cjs +219 -0
  823. package/lib/hmi/jtbd-state.cjs +263 -0
  824. package/lib/hmi/jtbd-taxonomy.json +402 -0
  825. package/lib/hmi/mindrian-ascii-logo.cjs +96 -0
  826. package/lib/hmi/part8-egress-gate.cjs +149 -0
  827. package/lib/hmi/reach-component-map.json +47 -0
  828. package/lib/hmi/reach-relevance-gate.cjs +147 -0
  829. package/lib/hmi/selector-dispatcher.cjs +1201 -0
  830. package/lib/hmi/selector-dispatcher.test.cjs +141 -0
  831. package/lib/hmi/selector-telemetry.cjs +269 -0
  832. package/lib/hmi/shape-f0-renderer.cjs +145 -0
  833. package/lib/hmi/shape-f1-fallback.cjs +80 -0
  834. package/lib/hmi/shape-f1-renderer.cjs +219 -0
  835. package/lib/hmi/shape-f2-renderer.cjs +132 -0
  836. package/lib/hmi/shape-f3-parity.test.cjs +64 -0
  837. package/lib/hmi/shape-f3-renderer.cjs +66 -0
  838. package/lib/hmi/shape-f4-parity.test.cjs +72 -0
  839. package/lib/hmi/shape-f4-renderer.cjs +72 -0
  840. package/lib/hmi/shape-f5-renderer.cjs +155 -0
  841. package/lib/hmi/shape-f6-plan-review-renderer.cjs +324 -0
  842. package/lib/hmi/shape-f6-renderer.cjs +144 -0
  843. package/lib/hmi/shape-f7-breakthrough-renderer.cjs +234 -0
  844. package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
  845. package/lib/hmi/shape-f8-renderer.cjs +150 -0
  846. package/lib/hmi/shape-f8-renderer.test.cjs +70 -0
  847. package/lib/hmi/shape-f9-renderer.cjs +187 -0
  848. package/lib/hmi/shape-f9-renderer.test.cjs +80 -0
  849. package/lib/hmi/shape-g-renderer.cjs +219 -0
  850. package/lib/hmi/shape-h-renderer.cjs +222 -0
  851. package/lib/hmi/tier-check.cjs +63 -0
  852. package/lib/hmi/turn-text.cjs +268 -0
  853. package/lib/hmi/voice-color-mark.cjs +318 -0
  854. package/lib/hmi/voice-style-log.cjs +276 -0
  855. package/lib/import/PRECONDITIONS.md +41 -0
  856. package/lib/import/branding.cjs +210 -0
  857. package/lib/import/branding.test.cjs +235 -0
  858. package/lib/import/classifications-sync.cjs +104 -0
  859. package/lib/import/classifications-sync.test.cjs +129 -0
  860. package/lib/import/enricher.cjs +296 -0
  861. package/lib/import/enricher.test.cjs +273 -0
  862. package/lib/import/integration.test.cjs +376 -0
  863. package/lib/import/manifest.cjs +129 -0
  864. package/lib/import/manifest.schema.json +185 -0
  865. package/lib/import/manifest.test.cjs +123 -0
  866. package/lib/import/meeting-detector.cjs +92 -0
  867. package/lib/import/meeting-detector.test.cjs +100 -0
  868. package/lib/import/person-detector.cjs +229 -0
  869. package/lib/import/person-detector.test.cjs +149 -0
  870. package/lib/import/report.cjs +186 -0
  871. package/lib/import/report.test.cjs +186 -0
  872. package/lib/import/room-md-scaffolder.cjs +49 -0
  873. package/lib/import/router.cjs +224 -0
  874. package/lib/import/router.test.cjs +356 -0
  875. package/lib/import/run-all-tests.cjs +36 -0
  876. package/lib/import/smoke-test.cjs +213 -0
  877. package/lib/import/smoke-test.test.cjs +148 -0
  878. package/lib/import/undo.test.cjs +199 -0
  879. package/lib/import/vault-scanner.cjs +105 -0
  880. package/lib/import/vault-scanner.test.cjs +67 -0
  881. package/lib/lens-engine/correlation-resolver.cjs +174 -0
  882. package/lib/lens-engine/source-lens-driver.cjs +736 -0
  883. package/lib/mcp/adapter-client.cjs +103 -0
  884. package/lib/mcp/app-html/dashboard.html +316 -0
  885. package/lib/mcp/app-html/graph.html +428 -0
  886. package/lib/mcp/app-html/mindrian-platform.html +1841 -0
  887. package/lib/mcp/app-html/wiki.html +383 -0
  888. package/lib/mcp/app-views.cjs +322 -0
  889. package/lib/mcp/brain-composition-census.cjs +184 -0
  890. package/lib/mcp/brain-router.cjs +551 -0
  891. package/lib/mcp/capability-registry.cjs +62 -0
  892. package/lib/mcp/contract-version.cjs +59 -0
  893. package/lib/mcp/daemon-lifecycle.cjs +324 -0
  894. package/lib/mcp/gate-dedup.cjs +120 -0
  895. package/lib/mcp/gate-ledger.cjs +145 -0
  896. package/lib/mcp/gate-render.cjs +483 -0
  897. package/lib/mcp/hook-adapter-audit.cjs +199 -0
  898. package/lib/mcp/larry-context.cjs +42 -0
  899. package/lib/mcp/mcp-first-flag.cjs +147 -0
  900. package/lib/mcp/no-instructions.test.cjs +270 -0
  901. package/lib/mcp/pipeline-state.cjs +424 -0
  902. package/lib/mcp/prompts.cjs +421 -0
  903. package/lib/mcp/register-core-tools.cjs +78 -0
  904. package/lib/mcp/resources.cjs +359 -0
  905. package/lib/mcp/runtime-instructions.cjs +38 -0
  906. package/lib/mcp/session-catchup.cjs +349 -0
  907. package/lib/mcp/session-registry.cjs +113 -0
  908. package/lib/mcp/session-room.cjs +124 -0
  909. package/lib/mcp/sse-event-bus.cjs +98 -0
  910. package/lib/mcp/stop-gate-handler.cjs +589 -0
  911. package/lib/mcp/surface-detect.cjs +186 -0
  912. package/lib/mcp/tool-router.cjs +2115 -0
  913. package/lib/mcp/tools/chain.cjs +674 -0
  914. package/lib/mcp/tools/claim.cjs +180 -0
  915. package/lib/mcp/tools/context.cjs +92 -0
  916. package/lib/mcp/tools/dual-path.cjs +79 -0
  917. package/lib/mcp/tools/gate.cjs +392 -0
  918. package/lib/mcp/tools/graph-reason.cjs +130 -0
  919. package/lib/mcp/tools/graph.cjs +321 -0
  920. package/lib/mcp/tools/identity.cjs +174 -0
  921. package/lib/mcp/tools/room.cjs +449 -0
  922. package/lib/mcp/tools/sensors.cjs +450 -0
  923. package/lib/mcp/tools/status.cjs +183 -0
  924. package/lib/mcp/tools/stop-gate.cjs +103 -0
  925. package/lib/mcp/tools/views.cjs +382 -0
  926. package/lib/mcp/tree-watcher.cjs +199 -0
  927. package/lib/memory/.mindrian/auto-explore-mat-test-001.json +11 -0
  928. package/lib/memory/aaak-compress.cjs +403 -0
  929. package/lib/memory/aaak-compress.test.cjs +288 -0
  930. package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
  931. package/lib/memory/bearer-token.test.cjs +315 -0
  932. package/lib/memory/body-shape-coverage.test.cjs +268 -0
  933. package/lib/memory/brain-cache-lru.test.cjs +259 -0
  934. package/lib/memory/brain-client-query-shape.test.cjs +160 -0
  935. package/lib/memory/brain-cypher-chain-slice.test.cjs +368 -0
  936. package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
  937. package/lib/memory/brain-derivation-queue.test.cjs +553 -0
  938. package/lib/memory/brain-derivation.test.cjs +639 -0
  939. package/lib/memory/brain-derive-command.test.cjs +534 -0
  940. package/lib/memory/brain-derive-curation.test.cjs +243 -0
  941. package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
  942. package/lib/memory/brain-md-schema.test.cjs +467 -0
  943. package/lib/memory/brain-md-staleness.test.cjs +525 -0
  944. package/lib/memory/brain-server-resolution.test.cjs +314 -0
  945. package/lib/memory/brain-suggestion-template.test.cjs +200 -0
  946. package/lib/memory/chain-recommender-canonical.test.cjs +208 -0
  947. package/lib/memory/chain-recommender.test.cjs +233 -0
  948. package/lib/memory/chat-context.test.cjs +128 -0
  949. package/lib/memory/command-registry.test.cjs +220 -0
  950. package/lib/memory/connector-registry.test.cjs +94 -0
  951. package/lib/memory/correlation-label-index.test.cjs +119 -0
  952. package/lib/memory/correlation.test.cjs +253 -0
  953. package/lib/memory/cross-room-aggregator.test.cjs +909 -0
  954. package/lib/memory/curation-132-03-dedup-held-rename.test.cjs +251 -0
  955. package/lib/memory/curation-132-05-pseudonymize.test.cjs +205 -0
  956. package/lib/memory/curation-batch.test.cjs +213 -0
  957. package/lib/memory/dashboard-server.test.cjs +256 -0
  958. package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
  959. package/lib/memory/decision-capture.test.cjs +632 -0
  960. package/lib/memory/decision-capture.worker.cjs +70 -0
  961. package/lib/memory/doctor-deprecation-surface.test.cjs +188 -0
  962. package/lib/memory/dual-graph-health.test.cjs +485 -0
  963. package/lib/memory/explain-decision-command.test.cjs +521 -0
  964. package/lib/memory/explain-decision-footer.test.cjs +316 -0
  965. package/lib/memory/explored-materials-store.cjs +392 -0
  966. package/lib/memory/f-selector-ranker.test.cjs +593 -0
  967. package/lib/memory/feynman-minto-guardian.test.cjs +863 -0
  968. package/lib/memory/feynman-minto-invariants.test.cjs +554 -0
  969. package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
  970. package/lib/memory/feynman-prompts.cjs +151 -0
  971. package/lib/memory/feynman-prompts.test.cjs +96 -0
  972. package/lib/memory/first-touch-version.test.cjs +198 -0
  973. package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
  974. package/lib/memory/folder-memory.test.cjs +503 -0
  975. package/lib/memory/framework-chain-composer.test.cjs +549 -0
  976. package/lib/memory/frontmatter-schema-validator.test.cjs +344 -0
  977. package/lib/memory/guardian-onstop-reaches-user.test.cjs +396 -0
  978. package/lib/memory/heal-command.test.cjs +604 -0
  979. package/lib/memory/help-coverage.test.cjs +108 -0
  980. package/lib/memory/help-renderer.test.cjs +145 -0
  981. package/lib/memory/huji-run-one-async-parity.test.cjs +242 -0
  982. package/lib/memory/hypergraph-event-schema.test.cjs +116 -0
  983. package/lib/memory/index-artifact-transaction.test.cjs +333 -0
  984. package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
  985. package/lib/memory/local-chain-recommender.test.cjs +172 -0
  986. package/lib/memory/mcp-input-validation.test.cjs +240 -0
  987. package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
  988. package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
  989. package/lib/memory/minto-debounce-consumer-census.test.cjs +567 -0
  990. package/lib/memory/minto-debouncer.test.cjs +407 -0
  991. package/lib/memory/minto-debouncer.worker.cjs +46 -0
  992. package/lib/memory/minto-migration-v88.test.cjs +265 -0
  993. package/lib/memory/minto-schema-v88.test.cjs +390 -0
  994. package/lib/memory/mos-status-renderer.test.cjs +631 -0
  995. package/lib/memory/narrative-schema.cjs +376 -0
  996. package/lib/memory/narrative-schema.test.cjs +209 -0
  997. package/lib/memory/nav-dial.test.cjs +414 -0
  998. package/lib/memory/navigation-engine-core.test.cjs +712 -0
  999. package/lib/memory/navigation-engine-offer.test.cjs +404 -0
  1000. package/lib/memory/navigation-hook-resolver.test.cjs +177 -0
  1001. package/lib/memory/navigation-invariants.test.cjs +483 -0
  1002. package/lib/memory/navigation-projections.test.cjs +241 -0
  1003. package/lib/memory/navigation-write-edge.test.cjs +206 -0
  1004. package/lib/memory/offer-closer.test.cjs +258 -0
  1005. package/lib/memory/offer-presenter.test.cjs +554 -0
  1006. package/lib/memory/on-stop-snapshot.test.cjs +404 -0
  1007. package/lib/memory/orchestration-projection.test.cjs +706 -0
  1008. package/lib/memory/packet-chain-hint.test.cjs +407 -0
  1009. package/lib/memory/packet-schema-validation.test.cjs +317 -0
  1010. package/lib/memory/palette-consistency.test.cjs +127 -0
  1011. package/lib/memory/pending-tension-store.cjs +453 -0
  1012. package/lib/memory/per-command-jtbd-derivation.test.cjs +130 -0
  1013. package/lib/memory/per-command-teaching.test.cjs +110 -0
  1014. package/lib/memory/post-compact-reinjection.test.cjs +854 -0
  1015. package/lib/memory/post-write-triple.test.cjs +317 -0
  1016. package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
  1017. package/lib/memory/precommit-real-commit.test.cjs +301 -0
  1018. package/lib/memory/problem-type-router.test.cjs +656 -0
  1019. package/lib/memory/query-efficiency-telemetry.test.cjs +612 -0
  1020. package/lib/memory/recompile-room-references.test.cjs +392 -0
  1021. package/lib/memory/recompile-room-references.worker.cjs +42 -0
  1022. package/lib/memory/record-decision-dual-write.test.cjs +454 -0
  1023. package/lib/memory/render-v2-disposition.test.cjs +199 -0
  1024. package/lib/memory/room-classifier-strict-mode.test.cjs +430 -0
  1025. package/lib/memory/room-minto-hook.test.cjs +398 -0
  1026. package/lib/memory/rs-discovery-engine.test.cjs +323 -0
  1027. package/lib/memory/run-feynman-tests.cjs +2101 -0
  1028. package/lib/memory/security-trifecta.test.cjs +329 -0
  1029. package/lib/memory/selector-alias-map.test.cjs +172 -0
  1030. package/lib/memory/selector-decisions.test.cjs +417 -0
  1031. package/lib/memory/selector-miss.test.cjs +290 -0
  1032. package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
  1033. package/lib/memory/session-start-triple-injection.test.cjs +514 -0
  1034. package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
  1035. package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
  1036. package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
  1037. package/lib/memory/skill-activation-router.test.cjs +465 -0
  1038. package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
  1039. package/lib/memory/soft-alias.test.cjs +144 -0
  1040. package/lib/memory/spine-events-correlation.test.cjs +198 -0
  1041. package/lib/memory/stale-copy-scanner.test.cjs +294 -0
  1042. package/lib/memory/stamp-artifact-write.test.cjs +304 -0
  1043. package/lib/memory/state-aware-router.test.cjs +90 -0
  1044. package/lib/memory/statusline-active-room-write.test.cjs +245 -0
  1045. package/lib/memory/statusline-active-room.test.cjs +315 -0
  1046. package/lib/memory/statusline-minto-segment.test.cjs +292 -0
  1047. package/lib/memory/statusline-two-row.test.cjs +338 -0
  1048. package/lib/memory/suggest-next-workflow.test.cjs +176 -0
  1049. package/lib/memory/sync-async-entry-points.test.cjs +204 -0
  1050. package/lib/memory/terminal-capability.test.cjs +155 -0
  1051. package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
  1052. package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
  1053. package/lib/memory/test-rs-brain-substrate.cjs +636 -0
  1054. package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
  1055. package/lib/memory/test-rs-canon-violations.cjs +218 -0
  1056. package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
  1057. package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
  1058. package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
  1059. package/lib/memory/test-rs-differential-scorer.cjs +480 -0
  1060. package/lib/memory/test-rs-discovery-engine.cjs +648 -0
  1061. package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
  1062. package/lib/memory/test-rs-egress-primitives.cjs +420 -0
  1063. package/lib/memory/test-rs-expert-mapper.cjs +547 -0
  1064. package/lib/memory/test-rs-explain-command.cjs +443 -0
  1065. package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
  1066. package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
  1067. package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
  1068. package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
  1069. package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
  1070. package/lib/memory/test-rs-mind-map.cjs +646 -0
  1071. package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
  1072. package/lib/memory/test-rs-nl-to-query.cjs +449 -0
  1073. package/lib/memory/test-rs-pinecone-bridge.cjs +282 -0
  1074. package/lib/memory/test-rs-preprocessor.cjs +433 -0
  1075. package/lib/memory/test-rs-query-matrix.cjs +391 -0
  1076. package/lib/memory/test-rs-query-to-text.cjs +551 -0
  1077. package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
  1078. package/lib/memory/test-rs-thesis-generator.cjs +360 -0
  1079. package/lib/memory/triple-context-formatter.cjs +473 -0
  1080. package/lib/memory/triple-context-formatter.test.cjs +442 -0
  1081. package/lib/memory/user-md-persona.test.cjs +565 -0
  1082. package/lib/memory/userpromptsubmit-integration.test.cjs +787 -0
  1083. package/lib/memory/validators/README.md +157 -0
  1084. package/lib/memory/validators/brain-md-invariants.cjs +475 -0
  1085. package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
  1086. package/lib/memory/validators/external-academic-invariants.cjs +249 -0
  1087. package/lib/memory/validators/external-industry-invariants.cjs +271 -0
  1088. package/lib/memory/validators/external-patents-invariants.cjs +266 -0
  1089. package/lib/memory/validators/minto-invariants.cjs +62 -0
  1090. package/lib/memory/validators/navigation-invariants.cjs +340 -0
  1091. package/lib/memory/validators/queue-health.cjs +95 -0
  1092. package/lib/memory/validators/snapshot-integrity.cjs +129 -0
  1093. package/lib/memory/validators/stale-lifecycle.cjs +116 -0
  1094. package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
  1095. package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
  1096. package/lib/memory/workflow-layer-e2e.test.cjs +262 -0
  1097. package/lib/memory/write-lock-atomic.test.cjs +137 -0
  1098. package/lib/memory/write-lock-atomic.worker.cjs +55 -0
  1099. package/lib/parity/check-parity.cjs +83 -0
  1100. package/lib/presentation/presentation-server.cjs +101 -0
  1101. package/lib/presentation/presentation-watcher.cjs +123 -0
  1102. package/lib/quickview/hub-server.cjs +719 -0
  1103. package/lib/quickview/server.cjs +533 -0
  1104. package/lib/render/JTBD-PALETTES.md +145 -0
  1105. package/lib/render/ROOM.md +111 -0
  1106. package/lib/render/render-v2.cjs +532 -0
  1107. package/lib/render/render-v2.test.cjs +267 -0
  1108. package/lib/render/render.cjs +65 -0
  1109. package/lib/sessionstart/budget-compressor.cjs +130 -0
  1110. package/lib/sessionstart/contributor-interface.cjs +134 -0
  1111. package/lib/sessionstart/contributor-isolator.cjs +128 -0
  1112. package/lib/sessionstart/precedence-ladder.cjs +47 -0
  1113. package/lib/state/ROOM.md +46 -0
  1114. package/lib/state/state-md-parser.cjs +215 -0
  1115. package/lib/statusline/ROOM.md +38 -0
  1116. package/lib/statusline/banner-suppression.cjs +50 -0
  1117. package/lib/statusline/cockpit-renderer.cjs +443 -0
  1118. package/lib/statusline/cockpit-signals.cjs +312 -0
  1119. package/lib/statusline/cockpit-telemetry.cjs +152 -0
  1120. package/lib/statusline/ctx-window.cjs +135 -0
  1121. package/lib/statusline/governing-thought-truncator.cjs +45 -0
  1122. package/lib/statusline/next-move-cache.cjs +235 -0
  1123. package/lib/statusline/ratification-next.cjs +164 -0
  1124. package/lib/statusline/room-health-cache.cjs +200 -0
  1125. package/lib/statusline/surface-detect.cjs +85 -0
  1126. package/lib/statusline/two-row-renderer.cjs +207 -0
  1127. package/lib/statusline/version-resolver.cjs +81 -0
  1128. package/lib/ui/design-system.cjs +19 -0
  1129. package/lib/update-bootstrap.sh.template +145 -0
  1130. package/lib/vault/frontmatter-schema.cjs +297 -0
  1131. package/lib/vault/room-scanner.cjs +365 -0
  1132. package/lib/vault/wikilink-builder.cjs +231 -0
  1133. package/lib/vault/wikilink-builder.test.cjs +182 -0
  1134. package/lib/wiki/briefing.cjs +193 -0
  1135. package/lib/wiki/editor-dist/wiki-editor.css +1 -0
  1136. package/lib/wiki/editor-dist/wiki-editor.js +1327 -0
  1137. package/lib/wiki/graph-links.cjs +281 -0
  1138. package/lib/wiki/page-renderer.cjs +229 -0
  1139. package/lib/wiki/room-home.cjs +353 -0
  1140. package/lib/wiki/wiki-chat.cjs +81 -0
  1141. package/lib/wiki/wiki-export.cjs +364 -0
  1142. package/lib/wiki/wiki-layout.cjs +1143 -0
  1143. package/lib/wiki/wiki-search.cjs +142 -0
  1144. package/lib/wiki/wiki-server.cjs +748 -0
  1145. package/lib/wiki/wiki-watcher.cjs +105 -0
  1146. package/lib/workflow/ROOM.md +47 -0
  1147. package/lib/workflow/chain-source.cjs +179 -0
  1148. package/lib/workflow/command-resolver.cjs +155 -0
  1149. package/lib/workflow/command-resolver.test.cjs +235 -0
  1150. package/lib/workflow/cross-room-umbilical-closer.cjs +364 -0
  1151. package/lib/workflow/dial-close-reach.cjs +405 -0
  1152. package/lib/workflow/f-selector-ranker.cjs +1148 -0
  1153. package/lib/workflow/f1-pick-consumer.cjs +232 -0
  1154. package/lib/workflow/f3-depth-consumer.cjs +98 -0
  1155. package/lib/workflow/f4-scope-consumer.cjs +112 -0
  1156. package/lib/workflow/f8-consumer.test.cjs +82 -0
  1157. package/lib/workflow/f8-fanout-consumer.cjs +208 -0
  1158. package/lib/workflow/f9-consumer.test.cjs +71 -0
  1159. package/lib/workflow/f9-ordered-consumer.cjs +199 -0
  1160. package/lib/workflow/local-chain-recommender.cjs +330 -0
  1161. package/lib/workflow/memory-cascade-f9-adapter.cjs +273 -0
  1162. package/lib/workflow/memory-governance-closer.cjs +384 -0
  1163. package/lib/workflow/offer-closer.cjs +407 -0
  1164. package/lib/workflow/reach-hedge-ranker.cjs +809 -0
  1165. package/lib/workflow/reach-reject-reader.cjs +372 -0
  1166. package/lib/workflow/reconcile-f9-adapter.cjs +299 -0
  1167. package/lib/workflow/selector-decisions.cjs +478 -0
  1168. package/lib/workflow/session-binding-consumer.cjs +249 -0
  1169. package/npm-shrinkwrap.json +1544 -0
  1170. package/output-styles/destijl.md +67 -0
  1171. package/package.json +22 -4
  1172. package/pipelines/PWS_grading/01-deep-grade.md +48 -0
  1173. package/pipelines/PWS_grading/02-mullins.md +50 -0
  1174. package/pipelines/PWS_grading/03-build-thesis.md +60 -0
  1175. package/pipelines/PWS_grading/04-structure-argument.md +77 -0
  1176. package/pipelines/PWS_grading/CHAIN.md +79 -0
  1177. package/pipelines/analogy/01-decompose.md +80 -0
  1178. package/pipelines/analogy/02-abstract.md +87 -0
  1179. package/pipelines/analogy/03-search.md +135 -0
  1180. package/pipelines/analogy/04-transfer.md +101 -0
  1181. package/pipelines/analogy/05-validate.md +106 -0
  1182. package/pipelines/analogy/CHAIN.md +73 -0
  1183. package/pipelines/discovery/01-explore-domains.md +44 -0
  1184. package/pipelines/discovery/02-think-hats.md +50 -0
  1185. package/pipelines/discovery/03-analyze-needs.md +54 -0
  1186. package/pipelines/discovery/CHAIN.md +48 -0
  1187. package/pipelines/thesis/01-structure-argument.md +45 -0
  1188. package/pipelines/thesis/02-challenge-assumptions.md +48 -0
  1189. package/pipelines/thesis/03-build-thesis.md +54 -0
  1190. package/pipelines/thesis/CHAIN.md +48 -0
  1191. package/references/brain/causal-directives.md +91 -0
  1192. package/references/brain/causal-enrichment.cypher +165 -0
  1193. package/references/brain/command-triggers-schema.md +15 -0
  1194. package/references/brain/graph-architecture.md +317 -0
  1195. package/references/brain/query-patterns.md +504 -0
  1196. package/references/brain/room-hierarchy-schema.md +224 -0
  1197. package/references/brain/schema.md +86 -0
  1198. package/references/capability-radar/capabilities-index.md +269 -0
  1199. package/references/capability-radar/changelog-cache.md +95 -0
  1200. package/references/causal/causal-schema.md +103 -0
  1201. package/references/design/email-template-standard.md +169 -0
  1202. package/references/design/graph-visualization-standard.md +178 -0
  1203. package/references/design/newsletter-email-template.html +307 -0
  1204. package/references/document-generation.md +179 -0
  1205. package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
  1206. package/references/import-config.md +141 -0
  1207. package/references/integrations/detection-patterns.md +101 -0
  1208. package/references/meeting/artifact-template.md +377 -0
  1209. package/references/meeting/cross-meeting-intelligence.md +216 -0
  1210. package/references/meeting/cross-relationship-patterns.md +202 -0
  1211. package/references/meeting/extraction-perspectives.md +225 -0
  1212. package/references/meeting/filing-protocol.md +222 -0
  1213. package/references/meeting/knowledge-typing.md +242 -0
  1214. package/references/meeting/live-join-interface.md +244 -0
  1215. package/references/meeting/section-mapping.md +192 -0
  1216. package/references/meeting/segment-classification.md +258 -0
  1217. package/references/meeting/speaker-profile-template.md +219 -0
  1218. package/references/meeting/summary-template.md +348 -0
  1219. package/references/meeting/transcript-patterns.md +226 -0
  1220. package/references/methodology/analyze-needs.md +135 -0
  1221. package/references/methodology/analyze-systems.md +121 -0
  1222. package/references/methodology/analyze-timing.md +149 -0
  1223. package/references/methodology/beautiful-question.md +109 -0
  1224. package/references/methodology/build-knowledge.md +161 -0
  1225. package/references/methodology/build-thesis-scored.md +60 -0
  1226. package/references/methodology/build-thesis.md +237 -0
  1227. package/references/methodology/causal-loop-diagrams.md +65 -0
  1228. package/references/methodology/challenge-assumptions.md +127 -0
  1229. package/references/methodology/diagnose.md +169 -0
  1230. package/references/methodology/dominant-designs.md +212 -0
  1231. package/references/methodology/explore-domains.md +147 -0
  1232. package/references/methodology/explore-futures.md +163 -0
  1233. package/references/methodology/explore-trends.md +129 -0
  1234. package/references/methodology/find-bottlenecks.md +131 -0
  1235. package/references/methodology/grade.md +211 -0
  1236. package/references/methodology/huji-stage-a-intake.md +246 -0
  1237. package/references/methodology/index.md +34 -0
  1238. package/references/methodology/leadership.md +200 -0
  1239. package/references/methodology/lean-canvas.md +116 -0
  1240. package/references/methodology/leverage-scan-signatures.md +83 -0
  1241. package/references/methodology/macro-trends.md +192 -0
  1242. package/references/methodology/map-unknowns.md +137 -0
  1243. package/references/methodology/mullins-7-domains.md +104 -0
  1244. package/references/methodology/problem-types.md +65 -0
  1245. package/references/methodology/root-cause.md +178 -0
  1246. package/references/methodology/rubric-huji.md +160 -0
  1247. package/references/methodology/sapphire-encoding.md +355 -0
  1248. package/references/methodology/scenario-plan.md +190 -0
  1249. package/references/methodology/score-innovation.md +154 -0
  1250. package/references/methodology/skillopt-judge-rubric.md +48 -0
  1251. package/references/methodology/skillopt-queries-rubric.md +60 -0
  1252. package/references/methodology/skillopt-refute-rubric.md +59 -0
  1253. package/references/methodology/skillopt-review-rubric.md +72 -0
  1254. package/references/methodology/skillopt-revise-rubric.md +48 -0
  1255. package/references/methodology/structure-argument.md +158 -0
  1256. package/references/methodology/systems-thinking.md +231 -0
  1257. package/references/methodology/think-hats.md +147 -0
  1258. package/references/methodology/triz-matrix.json +751 -0
  1259. package/references/methodology/triz-principles.md +501 -0
  1260. package/references/methodology/user-needs.md +199 -0
  1261. package/references/methodology/validate.md +163 -0
  1262. package/references/methodology/value-proposition.md +244 -0
  1263. package/references/opportunities/funding-lifecycle.md +103 -0
  1264. package/references/opportunities/grant-api-patterns.md +99 -0
  1265. package/references/opportunities/opportunity-template.md +84 -0
  1266. package/references/opportunities/tnufa-rubric.md +81 -0
  1267. package/references/personality/assessment-philosophy.md +72 -0
  1268. package/references/personality/lexicon.md +100 -0
  1269. package/references/personality/persona-chains.md +56 -0
  1270. package/references/personality/pws-lexicon-full.md +499 -0
  1271. package/references/personality/voice-dna.md +156 -0
  1272. package/references/personas/hat-perspectives.md +76 -0
  1273. package/references/personas/persona-template.md +63 -0
  1274. package/references/pipeline/act-output-contract.md +88 -0
  1275. package/references/pipeline/chains-index.md +39 -0
  1276. package/references/pws-profile-generation.md +79 -0
  1277. package/references/reasoning/reasoning-schema.md +143 -0
  1278. package/references/reasoning/reasoning-template.md +68 -0
  1279. package/references/reasoning/run-template.md +38 -0
  1280. package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
  1281. package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
  1282. package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
  1283. package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
  1284. package/references/security/agentshield-baseline.json +8 -0
  1285. package/references/security/cve-db.json +220 -0
  1286. package/references/taxonomy/TAXONOMY.md +192 -0
  1287. package/references/templates/MINTO.md +36 -0
  1288. package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
  1289. package/references/vault-kit/README.md +35 -0
  1290. package/references/vault-kit/app.json +12 -0
  1291. package/references/vault-kit/appearance.json +12 -0
  1292. package/references/vault-kit/graph.json +35 -0
  1293. package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
  1294. package/references/vault-kit/templates/new-artifact.md +37 -0
  1295. package/references/vault-kit/templates/new-meeting-note.md +35 -0
  1296. package/references/vault-kit/templates/new-team-profile.md +29 -0
  1297. package/references/vault-kit/templates/new-xref.md +35 -0
  1298. package/references/visual/ROOM.md +55 -0
  1299. package/references/visual/palette.json +54 -0
  1300. package/references/visual/symbol-system.md +151 -0
  1301. package/scripts/189-plurai-gate-check.cjs +88 -0
  1302. package/scripts/198-plurai-gate-check.cjs +118 -0
  1303. package/scripts/198-rollback-rehearsal.cjs +233 -0
  1304. package/scripts/83-scope-injection.test.cjs +648 -0
  1305. package/scripts/PYTHON_GATES.md +78 -0
  1306. package/scripts/__pycache__/compute-hsi.cpython-312.pyc +0 -0
  1307. package/scripts/__pycache__/detect-reverse-salients.cpython-312.pyc +0 -0
  1308. package/scripts/__pycache__/rs-engine.cpython-312.pyc +0 -0
  1309. package/scripts/act-command.cjs +518 -0
  1310. package/scripts/admin-brain-write.cjs +442 -0
  1311. package/scripts/admin-command-gate.cjs +178 -0
  1312. package/scripts/agentshield-scan-cli.cjs +226 -0
  1313. package/scripts/agentshield-sessionstart-scan.cjs +282 -0
  1314. package/scripts/analogy-fitness-report.cjs +266 -0
  1315. package/scripts/analyze-room +1079 -0
  1316. package/scripts/apply-teaching-content.cjs +298 -0
  1317. package/scripts/async-artifact-auto-commit.cjs +485 -0
  1318. package/scripts/audit-active-phase-resolver.cjs +160 -0
  1319. package/scripts/audit-body-shape-coverage.cjs +213 -0
  1320. package/scripts/auto-explore-drain.cjs +274 -0
  1321. package/scripts/auto-explore-fingerprint.cjs +354 -0
  1322. package/scripts/auto-explore-fire.cjs +359 -0
  1323. package/scripts/backfill-correlation-id.cjs +425 -0
  1324. package/scripts/backfill-hitl-shape.cjs +276 -0
  1325. package/scripts/backfill-hitl-shape.test.cjs +307 -0
  1326. package/scripts/backup-modifications +68 -0
  1327. package/scripts/banner +182 -0
  1328. package/scripts/brain-derivation-drain.cjs +291 -0
  1329. package/scripts/brain-derive-command.cjs +846 -0
  1330. package/scripts/brain-normalize-books.cypher +505 -0
  1331. package/scripts/brain-normalize-final.cypher +1355 -0
  1332. package/scripts/brain-normalize-problemtype.cypher +475 -0
  1333. package/scripts/brain-normalize-supplement.cypher +449 -0
  1334. package/scripts/brain-normalize-targeted.cypher +344 -0
  1335. package/scripts/brain-normalize-v194.cypher +61 -0
  1336. package/scripts/brain-normalize.cypher +530 -0
  1337. package/scripts/brain-response-sanitize-hook.cjs +165 -0
  1338. package/scripts/build-brain-census.cjs +957 -0
  1339. package/scripts/build-brain-packet-schema.cjs +225 -0
  1340. package/scripts/build-command-registry.cjs +587 -0
  1341. package/scripts/build-connector-registry.cjs +1331 -0
  1342. package/scripts/build-corpus-stats.cjs +408 -0
  1343. package/scripts/build-dist-bundles.cjs +495 -0
  1344. package/scripts/build-ecosystem-graph.cjs +490 -0
  1345. package/scripts/build-graph +1462 -0
  1346. package/scripts/build-graph-from-sqlite.cjs +97 -0
  1347. package/scripts/build-harness-manifest.cjs +1090 -0
  1348. package/scripts/build-jtbd-nudges +205 -0
  1349. package/scripts/build-new-surface.cjs +805 -0
  1350. package/scripts/build-orchestration-projection.cjs +1221 -0
  1351. package/scripts/build-render-coverage.cjs +637 -0
  1352. package/scripts/build-skill-mirrors.cjs +681 -0
  1353. package/scripts/cache-hitrate-report.cjs +216 -0
  1354. package/scripts/causal-to-graph.cjs +163 -0
  1355. package/scripts/check-abstraction-fixture-neutral.cjs +160 -0
  1356. package/scripts/check-admin-identity.cjs +212 -0
  1357. package/scripts/check-brain-tool-liveness.cjs +502 -0
  1358. package/scripts/check-card-fire.cjs +1809 -0
  1359. package/scripts/check-cirs-declaration.cjs +279 -0
  1360. package/scripts/check-deck-design.cjs +104 -0
  1361. package/scripts/check-dual-graph-health.cjs +645 -0
  1362. package/scripts/check-first-touch-drift.cjs +220 -0
  1363. package/scripts/check-flagship-floor.cjs +391 -0
  1364. package/scripts/check-framework-vocabulary-drift.cjs +563 -0
  1365. package/scripts/check-gate-seam.cjs +178 -0
  1366. package/scripts/check-graph-derive-health.cjs +208 -0
  1367. package/scripts/check-graph-export-typemap.cjs +118 -0
  1368. package/scripts/check-help-coverage.cjs +290 -0
  1369. package/scripts/check-hitl-stages.cjs +219 -0
  1370. package/scripts/check-hook-schema-compatibility.cjs +231 -0
  1371. package/scripts/check-hsi-deps +42 -0
  1372. package/scripts/check-kuzu-reintroduction.cjs +251 -0
  1373. package/scripts/check-onboard +43 -0
  1374. package/scripts/check-onboard-statusline.cjs +363 -0
  1375. package/scripts/check-palette-consistency.cjs +106 -0
  1376. package/scripts/check-pending-ambiguous.cjs +244 -0
  1377. package/scripts/check-pending-breakthrough.cjs +189 -0
  1378. package/scripts/check-pending-breakthrough.test.cjs +166 -0
  1379. package/scripts/check-pending-naming-decision.cjs +97 -0
  1380. package/scripts/check-plugin-path-anchoring.cjs +830 -0
  1381. package/scripts/check-publish-needs.cjs +126 -0
  1382. package/scripts/check-registry-drift.cjs +206 -0
  1383. package/scripts/check-release-payload-ceiling.cjs +269 -0
  1384. package/scripts/check-render-coverage.cjs +558 -0
  1385. package/scripts/check-research-isomorphism.cjs +446 -0
  1386. package/scripts/check-reward-before-investment.cjs +261 -0
  1387. package/scripts/check-room-blueprints.cjs +213 -0
  1388. package/scripts/check-schema-aliases.cjs +532 -0
  1389. package/scripts/check-shape-declaration.cjs +958 -0
  1390. package/scripts/check-shape-declaration.test.cjs +185 -0
  1391. package/scripts/check-skill-spec.cjs +312 -0
  1392. package/scripts/check-skill-vs-code-drift.cjs +178 -0
  1393. package/scripts/check-substrate.cjs +513 -0
  1394. package/scripts/check-tool-honesty.cjs +1601 -0
  1395. package/scripts/check-update +60 -0
  1396. package/scripts/check-version-and-sha.cjs +366 -0
  1397. package/scripts/check-voice-style.cjs +184 -0
  1398. package/scripts/check-worktree-hygiene.cjs +530 -0
  1399. package/scripts/classify-insight +59 -0
  1400. package/scripts/coherence-smoke-test.cjs +489 -0
  1401. package/scripts/collect-cold-install-evidence.cjs +411 -0
  1402. package/scripts/compute-bayesian-surprise.py +175 -0
  1403. package/scripts/compute-blindspot-mass.py +209 -0
  1404. package/scripts/compute-disruption-index.py +211 -0
  1405. package/scripts/compute-element-novelty.py +254 -0
  1406. package/scripts/compute-external-whitespace.py +563 -0
  1407. package/scripts/compute-hsi.py +947 -0
  1408. package/scripts/compute-meetings-intelligence +716 -0
  1409. package/scripts/compute-opportunity-state +17 -0
  1410. package/scripts/compute-state +393 -0
  1411. package/scripts/compute-team +1112 -0
  1412. package/scripts/compute-whitespace-embeddings.py +464 -0
  1413. package/scripts/compute-whitespace-gaps.py +562 -0
  1414. package/scripts/compute_topic_forest.py +684 -0
  1415. package/scripts/consolidate-pinecone.py +265 -0
  1416. package/scripts/context-monitor +943 -0
  1417. package/scripts/create-speaker-profile +311 -0
  1418. package/scripts/cross-room-detect.cjs +195 -0
  1419. package/scripts/csv-to-idea-graph.cjs +755 -0
  1420. package/scripts/ctxl-eval.cjs +846 -0
  1421. package/scripts/curation-132-03-dedup-held-rename.cjs +395 -0
  1422. package/scripts/curation-132-05-pseudonymize.cjs +189 -0
  1423. package/scripts/derive-verb-reach-affinity.cjs +507 -0
  1424. package/scripts/detect-reverse-salients.py +242 -0
  1425. package/scripts/diagnostics-command.cjs +345 -0
  1426. package/scripts/dial-memory-refresh-command.cjs +147 -0
  1427. package/scripts/discover-analogy-whitespace.py +433 -0
  1428. package/scripts/discover-hsi-whitespace.py +352 -0
  1429. package/scripts/discover-rs-whitespace.py +327 -0
  1430. package/scripts/discovery-cycle.cjs +625 -0
  1431. package/scripts/disposition-render-v2.cjs +229 -0
  1432. package/scripts/doctor-preflight-format.cjs +101 -0
  1433. package/scripts/doctor.cjs +4327 -0
  1434. package/scripts/dogfood-bridge/append-to-queue.sh +59 -0
  1435. package/scripts/dogfood-bridge/path-filter.cjs +86 -0
  1436. package/scripts/dogfood-derive.cjs +158 -0
  1437. package/scripts/dogfood-emit.cjs +126 -0
  1438. package/scripts/empathy-observation-emit.cjs +108 -0
  1439. package/scripts/enrichment-queue-append.cjs +157 -0
  1440. package/scripts/ensure-brain-baseline.cjs +117 -0
  1441. package/scripts/entity-extract.cjs +1135 -0
  1442. package/scripts/eureka-command.cjs +687 -0
  1443. package/scripts/eureka-critic-run.cjs +662 -0
  1444. package/scripts/eureka-portfolio-report.cjs +2038 -0
  1445. package/scripts/eureka-room-report.cjs +419 -0
  1446. package/scripts/explain-decision-command.cjs +499 -0
  1447. package/scripts/export-pinecone-embeddings.cjs +516 -0
  1448. package/scripts/extract-room-intelligence.cjs +714 -0
  1449. package/scripts/fetch-brain-baseline.cjs +190 -0
  1450. package/scripts/fetch-brain-baseline.py +360 -0
  1451. package/scripts/feynman-minto-guardian.cjs +672 -0
  1452. package/scripts/feynman-timeline-refresh-command.cjs +270 -0
  1453. package/scripts/file-asset +253 -0
  1454. package/scripts/fire-sensor-event.cjs +83 -0
  1455. package/scripts/first-install-router.cjs +968 -0
  1456. package/scripts/frontmatter-schema-validator.cjs +339 -0
  1457. package/scripts/fts-index-drain.cjs +221 -0
  1458. package/scripts/generate-chat-embed.cjs +179 -0
  1459. package/scripts/generate-deck.cjs +322 -0
  1460. package/scripts/generate-export.cjs +474 -0
  1461. package/scripts/generate-hub.cjs +2553 -0
  1462. package/scripts/generate-lobby.cjs +718 -0
  1463. package/scripts/generate-presentation.cjs +988 -0
  1464. package/scripts/generate-presentation.test.cjs +405 -0
  1465. package/scripts/generate-section-intelligence.cjs +330 -0
  1466. package/scripts/generate-snapshot.cjs +1410 -0
  1467. package/scripts/generate-standalone +159 -0
  1468. package/scripts/git-ops +211 -0
  1469. package/scripts/graph-heal-pipeline.cjs +321 -0
  1470. package/scripts/gsd-artifact-graph-hook.cjs +219 -0
  1471. package/scripts/gsd-graph-derive-drain.cjs +571 -0
  1472. package/scripts/gsd-graph-derive-sweep.cjs +195 -0
  1473. package/scripts/heal-command.cjs +1365 -0
  1474. package/scripts/hedge-refit-pipeline.cjs +207 -0
  1475. package/scripts/help-renderer.cjs +303 -0
  1476. package/scripts/hmi-compliance-poll.cjs +451 -0
  1477. package/scripts/hmi-status-command.cjs +510 -0
  1478. package/scripts/hooked-rescore-117.cjs +224 -0
  1479. package/scripts/hooks/pre-commit +533 -0
  1480. package/scripts/hooks/pre-commit-room-minto-guard.cmd +43 -0
  1481. package/scripts/hooks/pre-commit-room-minto-guard.sh +533 -0
  1482. package/scripts/hsi-to-graph.cjs +194 -0
  1483. package/scripts/huji-batch.cjs +821 -0
  1484. package/scripts/huji-eval.cjs +1158 -0
  1485. package/scripts/huji-intake.cjs +563 -0
  1486. package/scripts/huji-pin-smoketest.cjs +454 -0
  1487. package/scripts/huji-run-one-async.cjs +305 -0
  1488. package/scripts/huji-run-one.cjs +671 -0
  1489. package/scripts/install-pre-commit.sh +113 -0
  1490. package/scripts/intent-classifier +106 -0
  1491. package/scripts/intent-classifier.cjs +3620 -0
  1492. package/scripts/interpret-whitespace.cjs +691 -0
  1493. package/scripts/jtbd-command.cjs +810 -0
  1494. package/scripts/jtbd-update.cjs +299 -0
  1495. package/scripts/label-topic-forest.cjs +667 -0
  1496. package/scripts/learn-from-usage +168 -0
  1497. package/scripts/lib/__pycache__/ensure_ml_deps.cpython-312.pyc +0 -0
  1498. package/scripts/lib/ensure_ml_deps.py +97 -0
  1499. package/scripts/load-embeddings-into-neo4j.cjs +569 -0
  1500. package/scripts/memory-artifact-graph-hook.cjs +191 -0
  1501. package/scripts/memory-command.cjs +687 -0
  1502. package/scripts/memory-completion-detector.cjs +217 -0
  1503. package/scripts/memory-lifecycle.cjs +620 -0
  1504. package/scripts/memory-resume-nudge.cjs +233 -0
  1505. package/scripts/methodology-ingest-local.sh +36 -0
  1506. package/scripts/migrate-brain-mcp-from-http-to-stdio.cjs +190 -0
  1507. package/scripts/migrate-brain-mcp-from-http-to-stdio.test.cjs +242 -0
  1508. package/scripts/migrate-hats-to-roomdb.cjs +217 -0
  1509. package/scripts/migrate-lazygraph.cjs +250 -0
  1510. package/scripts/migrate-minto-schema-v88.cjs +296 -0
  1511. package/scripts/migrate-plugin-root-refs.cjs +168 -0
  1512. package/scripts/migrate-room-sections-v275.cjs +556 -0
  1513. package/scripts/migrate-rooms +417 -0
  1514. package/scripts/migrate-skill-frontmatter.cjs +472 -0
  1515. package/scripts/migrate-stale-user-settings.cjs +296 -0
  1516. package/scripts/migrate-telemetry-v1.cjs +384 -0
  1517. package/scripts/migrate-telemetry-v1.test.cjs +356 -0
  1518. package/scripts/minto-debouncer.cjs +428 -0
  1519. package/scripts/mos-status.cjs +622 -0
  1520. package/scripts/mva-detect.cjs +229 -0
  1521. package/scripts/mva-run.cjs +36 -0
  1522. package/scripts/on-agent-complete +163 -0
  1523. package/scripts/on-cwd-changed +149 -0
  1524. package/scripts/on-file-changed +68 -0
  1525. package/scripts/on-stop +617 -0
  1526. package/scripts/on-task-complete +194 -0
  1527. package/scripts/operator-command.cjs +858 -0
  1528. package/scripts/operator-update.cjs +308 -0
  1529. package/scripts/part8-egress-guard-hook.cjs +229 -0
  1530. package/scripts/pipeline-command.cjs +271 -0
  1531. package/scripts/post-compact +383 -0
  1532. package/scripts/post-update-activation.cjs +339 -0
  1533. package/scripts/post-write +283 -0
  1534. package/scripts/power-demo-prompt.md +621 -0
  1535. package/scripts/pre-compact +273 -0
  1536. package/scripts/preflight-auto-explore.cjs +314 -0
  1537. package/scripts/preflight-doctor.cjs +225 -0
  1538. package/scripts/preflight-release-drift.cjs +168 -0
  1539. package/scripts/preflight-tension-surface.cjs +414 -0
  1540. package/scripts/probe-brain-contract.cjs +502 -0
  1541. package/scripts/publish-ops +346 -0
  1542. package/scripts/query-efficiency-telemetry.cjs +364 -0
  1543. package/scripts/query-semantic-scholar.cjs +545 -0
  1544. package/scripts/reapply-modifications +49 -0
  1545. package/scripts/reassign-primary.cjs +125 -0
  1546. package/scripts/recompile-room-references.cjs +601 -0
  1547. package/scripts/release-beta-preflight.sh +69 -0
  1548. package/scripts/render-pdf +588 -0
  1549. package/scripts/render-viz +190 -0
  1550. package/scripts/research-speaker +239 -0
  1551. package/scripts/resolve-room +204 -0
  1552. package/scripts/restore-post-compact-context.cjs +320 -0
  1553. package/scripts/room-auto-create-nudge.cjs +136 -0
  1554. package/scripts/room-naming-selector.cjs +201 -0
  1555. package/scripts/room-registry +767 -0
  1556. package/scripts/rs-discovery-engine.cjs +725 -0
  1557. package/scripts/rs-engine.py +2073 -0
  1558. package/scripts/rs-experts-command.cjs +300 -0
  1559. package/scripts/rs-explain-command.cjs +323 -0
  1560. package/scripts/rs-fetch-command.cjs +170 -0
  1561. package/scripts/rs-thesis-command.cjs +203 -0
  1562. package/scripts/rs-vector-bridge.cjs +410 -0
  1563. package/scripts/run-harness.cjs +890 -0
  1564. package/scripts/scout-cadence-guard.cjs +323 -0
  1565. package/scripts/scout-cadence-runner.cjs +509 -0
  1566. package/scripts/scout-telemetry-aggregator.cjs +268 -0
  1567. package/scripts/sealed-walker.py +96 -0
  1568. package/scripts/seed-brain-commands.cjs +324 -0
  1569. package/scripts/self-update +105 -0
  1570. package/scripts/sentinel-deadline-monitor +241 -0
  1571. package/scripts/sentinel-health-check +198 -0
  1572. package/scripts/sentinel-snapshot +42 -0
  1573. package/scripts/serve-dashboard +103 -0
  1574. package/scripts/serve-dashboard-live +932 -0
  1575. package/scripts/serve-presentation +97 -0
  1576. package/scripts/serve-wiki +90 -0
  1577. package/scripts/session-end-presence.cjs +79 -0
  1578. package/scripts/session-start +1943 -0
  1579. package/scripts/sessionstart-coordinator.cjs +249 -0
  1580. package/scripts/sessionstart-npm-reconcile.cjs +111 -0
  1581. package/scripts/sessionstart-post-update-preflight.cjs +303 -0
  1582. package/scripts/sessionstart-reference-now-seed.cjs +137 -0
  1583. package/scripts/setup-hooks.sh +112 -0
  1584. package/scripts/skillopt-codereview.cjs +830 -0
  1585. package/scripts/skillopt-eval.cjs +519 -0
  1586. package/scripts/skillopt-funnel.cjs +1076 -0
  1587. package/scripts/skillopt-genqueries.cjs +571 -0
  1588. package/scripts/skillopt-inventory.cjs +319 -0
  1589. package/scripts/skillopt-merge.cjs +656 -0
  1590. package/scripts/skillopt-triggerloop.cjs +1126 -0
  1591. package/scripts/soft-alias-runner.cjs +116 -0
  1592. package/scripts/stamp-artifact-write.cjs +276 -0
  1593. package/scripts/stamp-firing-block.cjs +325 -0
  1594. package/scripts/state-write.cjs +97 -0
  1595. package/scripts/statusline-fallback-echo.cjs +297 -0
  1596. package/scripts/statusline-mos +116 -0
  1597. package/scripts/statusline-mos-dispatch +127 -0
  1598. package/scripts/suggest-next-command.cjs +416 -0
  1599. package/scripts/sweep-skill-descriptions.cjs +263 -0
  1600. package/scripts/sync-rooms-graph +264 -0
  1601. package/scripts/telemetry-command-invocation.cjs +120 -0
  1602. package/scripts/test-doctor-recovery.cjs +242 -0
  1603. package/scripts/test-fresh-install.md +140 -0
  1604. package/scripts/track-analytics +156 -0
  1605. package/scripts/transcribe-audio +148 -0
  1606. package/scripts/transcript-ingest.cjs +225 -0
  1607. package/scripts/update-icm-index +153 -0
  1608. package/scripts/v182-brain-optimize.cypher +1191 -0
  1609. package/scripts/validate-model-profiles +93 -0
  1610. package/scripts/vault-content-reformatter.cjs +581 -0
  1611. package/scripts/vault-export-orchestrator.cjs +400 -0
  1612. package/scripts/vault-footer-injector.cjs +315 -0
  1613. package/scripts/vault-import.cjs +839 -0
  1614. package/scripts/vault-integration-test.cjs +318 -0
  1615. package/scripts/vault-regenerate-all.cjs +216 -0
  1616. package/scripts/vault-regenerate-all.test.cjs +138 -0
  1617. package/scripts/vault-rules-generator.cjs +220 -0
  1618. package/scripts/vault-section-minto-generator.cjs +1642 -0
  1619. package/scripts/vault-section-minto-generator.integration.test.cjs +576 -0
  1620. package/scripts/vault-section-minto-generator.test.cjs +291 -0
  1621. package/scripts/vault-section-state-generator.cjs +385 -0
  1622. package/scripts/vault-welcome-generator.cjs +403 -0
  1623. package/scripts/vault-wikilink-injector.cjs +449 -0
  1624. package/scripts/verify-phase-132.cjs +338 -0
  1625. package/scripts/verify-release +744 -0
  1626. package/scripts/whitespace-command.cjs +831 -0
  1627. package/scripts/whitespace-to-brain.cjs +384 -0
  1628. package/scripts/whitespace-to-graph.cjs +220 -0
  1629. package/scripts/wikilink-batch.cjs +102 -0
  1630. package/scripts/wikilink-file.cjs +139 -0
  1631. package/scripts/write-bridge-artifacts.cjs +140 -0
  1632. package/scripts/write-scope-check +12 -0
  1633. package/scripts/write-scope-check.cjs +461 -0
  1634. package/scripts/write-whitespace-sections.cjs +483 -0
  1635. package/settings.json +7 -0
  1636. package/skills/act/SKILL.md +523 -0
  1637. package/skills/admin/SKILL.md +437 -0
  1638. package/skills/agentshield/SKILL.md +50 -0
  1639. package/skills/analyze-needs/SKILL.md +74 -0
  1640. package/skills/analyze-systems/SKILL.md +69 -0
  1641. package/skills/analyze-timing/SKILL.md +94 -0
  1642. package/skills/auto-explore/SKILL.md +84 -0
  1643. package/skills/beautiful-question/SKILL.md +72 -0
  1644. package/skills/bono/SKILL.md +146 -0
  1645. package/skills/brain-connector/SKILL.md +206 -0
  1646. package/skills/brain-derive/SKILL.md +140 -0
  1647. package/skills/build-knowledge/SKILL.md +118 -0
  1648. package/skills/build-thesis/SKILL.md +90 -0
  1649. package/skills/causal/SKILL.md +265 -0
  1650. package/skills/challenge-assumptions/SKILL.md +74 -0
  1651. package/skills/client-discovery-interview/SKILL.md +94 -0
  1652. package/skills/client-discovery-interview/discovery-scaffold.json +41 -0
  1653. package/skills/client-discovery-interview/question-bank.md +149 -0
  1654. package/skills/compare-ventures/SKILL.md +113 -0
  1655. package/skills/context-engine/SKILL.md +169 -0
  1656. package/skills/conversation-mode/SKILL.md +189 -0
  1657. package/skills/correct-reference-now/SKILL.md +86 -0
  1658. package/skills/dashboard/SKILL.md +142 -0
  1659. package/skills/deck/SKILL.md +113 -0
  1660. package/skills/deep-grade/SKILL.md +210 -0
  1661. package/skills/diagnose/SKILL.md +128 -0
  1662. package/skills/diagnostics/SKILL.md +193 -0
  1663. package/skills/dial-memory-refresh/SKILL.md +116 -0
  1664. package/skills/diffusion/SKILL.md +125 -0
  1665. package/skills/discover/SKILL.md +216 -0
  1666. package/skills/doctor/SKILL.md +269 -0
  1667. package/skills/dogfood-flush/SKILL.md +133 -0
  1668. package/skills/dominant-designs/SKILL.md +72 -0
  1669. package/skills/eureka/SKILL.md +313 -0
  1670. package/skills/explain-decision/SKILL.md +138 -0
  1671. package/skills/explore-domains/SKILL.md +74 -0
  1672. package/skills/explore-futures/SKILL.md +64 -0
  1673. package/skills/explore-opportunity/SKILL.md +168 -0
  1674. package/skills/explore-trends/SKILL.md +74 -0
  1675. package/skills/export/SKILL.md +126 -0
  1676. package/skills/feynman-timeline-refresh/SKILL.md +105 -0
  1677. package/skills/file-meeting/SKILL.md +1190 -0
  1678. package/skills/find-analogies/SKILL.md +360 -0
  1679. package/skills/find-bottlenecks/SKILL.md +104 -0
  1680. package/skills/find-connections/SKILL.md +108 -0
  1681. package/skills/funding/SKILL.md +110 -0
  1682. package/skills/futures/SKILL.md +124 -0
  1683. package/skills/grade/SKILL.md +252 -0
  1684. package/skills/grade-grant/SKILL.md +293 -0
  1685. package/skills/graph/SKILL.md +216 -0
  1686. package/skills/hat-briefing/SKILL.md +160 -0
  1687. package/skills/heal/SKILL.md +69 -0
  1688. package/skills/help/SKILL.md +172 -0
  1689. package/skills/hmi-status/SKILL.md +73 -0
  1690. package/skills/ignite/SKILL.md +225 -0
  1691. package/skills/ingest-methodology/SKILL.md +125 -0
  1692. package/skills/intel-pipeline/SKILL.md +128 -0
  1693. package/skills/intelligence-orchestrator/SKILL.md +262 -0
  1694. package/skills/jtbd/SKILL.md +261 -0
  1695. package/skills/larry-personality/SKILL.md +571 -0
  1696. package/skills/larry-personality/framework-chains.md +92 -0
  1697. package/skills/larry-personality/mode-engine.md +185 -0
  1698. package/skills/leadership/SKILL.md +109 -0
  1699. package/skills/lean-canvas/SKILL.md +70 -0
  1700. package/skills/macro-trends/SKILL.md +84 -0
  1701. package/skills/map-unknowns/SKILL.md +72 -0
  1702. package/skills/memory/SKILL.md +212 -0
  1703. package/skills/memory-cortex-reach/SKILL.md +80 -0
  1704. package/skills/models/SKILL.md +197 -0
  1705. package/skills/mos/SKILL.md +160 -0
  1706. package/skills/mos-deck-engine/SKILL.md +329 -0
  1707. package/skills/mos-reason/SKILL.md +484 -0
  1708. package/skills/mullins/SKILL.md +151 -0
  1709. package/skills/mullins-scaffold/SKILL.md +132 -0
  1710. package/skills/mullins-scaffold/scaffold.json +223 -0
  1711. package/skills/mva-brief/SKILL.md +87 -0
  1712. package/skills/mva-option/SKILL.md +120 -0
  1713. package/skills/mva-pipeline/SKILL.md +158 -0
  1714. package/skills/mva-report/SKILL.md +79 -0
  1715. package/skills/new-project/SKILL.md +629 -0
  1716. package/skills/new-surface/SKILL.md +112 -0
  1717. package/skills/onboard/SKILL.md +485 -0
  1718. package/skills/operator/SKILL.md +167 -0
  1719. package/skills/opportunities/SKILL.md +171 -0
  1720. package/skills/organize/SKILL.md +69 -0
  1721. package/skills/persona/SKILL.md +272 -0
  1722. package/skills/pipeline/SKILL.md +164 -0
  1723. package/skills/present/SKILL.md +123 -0
  1724. package/skills/publish/SKILL.md +224 -0
  1725. package/skills/pws-brain/SKILL.md +152 -0
  1726. package/skills/pws-methodology/SKILL.md +92 -0
  1727. package/skills/qualify-opportunity/SKILL.md +111 -0
  1728. package/skills/query/SKILL.md +68 -0
  1729. package/skills/radar/SKILL.md +106 -0
  1730. package/skills/reanalyze/SKILL.md +122 -0
  1731. package/skills/research/SKILL.md +462 -0
  1732. package/skills/room/SKILL.md +381 -0
  1733. package/skills/room-passive/SKILL.md +174 -0
  1734. package/skills/room-proactive/SKILL.md +344 -0
  1735. package/skills/rooms/SKILL.md +678 -0
  1736. package/skills/root-cause/SKILL.md +73 -0
  1737. package/skills/rs-experts/SKILL.md +151 -0
  1738. package/skills/rs-explain/SKILL.md +130 -0
  1739. package/skills/rs-fetch/SKILL.md +115 -0
  1740. package/skills/rs-thesis/SKILL.md +129 -0
  1741. package/skills/scenario-plan/SKILL.md +76 -0
  1742. package/skills/scheduled-tasks/SKILL.md +301 -0
  1743. package/skills/score-innovation/SKILL.md +74 -0
  1744. package/skills/scout/SKILL.md +370 -0
  1745. package/skills/setup/SKILL.md +654 -0
  1746. package/skills/show/SKILL.md +68 -0
  1747. package/skills/skill/SKILL.md +104 -0
  1748. package/skills/snapshot/SKILL.md +178 -0
  1749. package/skills/speakers/SKILL.md +116 -0
  1750. package/skills/splash/SKILL.md +54 -0
  1751. package/skills/stance/SKILL.md +89 -0
  1752. package/skills/status/SKILL.md +107 -0
  1753. package/skills/structure-argument/SKILL.md +78 -0
  1754. package/skills/suggest-next/SKILL.md +137 -0
  1755. package/skills/systems-thinking/SKILL.md +220 -0
  1756. package/skills/think-hats/SKILL.md +82 -0
  1757. package/skills/trending-to-absurd/SKILL.md +100 -0
  1758. package/skills/ui-system/SKILL.md +474 -0
  1759. package/skills/ui-system/design-system/M-OS-DESIGN-SYSTEM.md +273 -0
  1760. package/skills/ui-system/design-system/SPEC.md +72 -0
  1761. package/skills/ui-system/design-system/image-prompt-style.md +29 -0
  1762. package/skills/ui-system/design-system/mos-design-system.css +240 -0
  1763. package/skills/ui-system/rules/design-system.md +60 -0
  1764. package/skills/ui-system/rules/dual-palette.md +156 -0
  1765. package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
  1766. package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
  1767. package/skills/update/SKILL.md +254 -0
  1768. package/skills/user-needs/SKILL.md +73 -0
  1769. package/skills/validate/SKILL.md +73 -0
  1770. package/skills/value-proposition/SKILL.md +141 -0
  1771. package/skills/vault/SKILL.md +326 -0
  1772. package/skills/visualize/SKILL.md +69 -0
  1773. package/skills/whitespace/SKILL.md +539 -0
  1774. package/skills/wiki/SKILL.md +94 -0
  1775. package/templates/_base.html +20 -0
  1776. package/templates/chat-panel.html +205 -0
  1777. package/templates/constellation-chat-hooks.js +89 -0
  1778. package/templates/constellation.html +1272 -0
  1779. package/templates/destijl-base.css +268 -0
  1780. package/templates/discovery/discovery-brief-template.md +97 -0
  1781. package/templates/due-diligence.html +49 -0
  1782. package/templates/executive-summary.html +40 -0
  1783. package/templates/icm/CLAUDE.md +31 -0
  1784. package/templates/icm/GROUP-CLAUDE.md +33 -0
  1785. package/templates/icm/INDEX.md +26 -0
  1786. package/templates/import/stage-contracts/01-ingest.md +34 -0
  1787. package/templates/import/stage-contracts/02-classify.md +48 -0
  1788. package/templates/import/stage-contracts/03-route.md +42 -0
  1789. package/templates/import/stage-contracts/04-enrich.md +40 -0
  1790. package/templates/investment-thesis.html +25 -0
  1791. package/templates/meeting-report.html +361 -0
  1792. package/templates/presentation/dashboard.html +1150 -0
  1793. package/templates/presentation/deck.html +540 -0
  1794. package/templates/presentation/diagrams.html +437 -0
  1795. package/templates/presentation/graph.html +391 -0
  1796. package/templates/presentation/insights.html +548 -0
  1797. package/templates/presentation/wiki.html +533 -0
  1798. package/templates/profile.html +136 -0
  1799. package/templates/room-context/last-session.md +56 -0
  1800. package/templates/room-context/methodology-history.md +28 -0
  1801. package/templates/room-context/rejection-log.md +25 -0
  1802. package/templates/room-context/weekly-digest.md +54 -0
  1803. package/templates/room-skeleton/MINTO.md.tmpl +19 -0
  1804. package/templates/room-skeleton/ROOM.md.identity.tmpl +13 -0
  1805. package/templates/room-skeleton/ROOM.md.section.tmpl +33 -0
  1806. package/templates/room-skeleton/STATE.md.tmpl +33 -0
  1807. package/templates/room-skeleton/USER.md.tmpl +23 -0
  1808. package/templates/room-skeleton/references/.gitkeep +0 -0
  1809. package/templates/room-skeleton/references/SECTION-SCHEMA.md +213 -0
  1810. package/templates/room-skeleton/references/SUB-SCHEMAS.md +91 -0
  1811. package/templates/room-skeleton/section-contracts/.gitkeep +0 -0
  1812. package/templates/room-skeleton/section-contracts/business-model.md +32 -0
  1813. package/templates/room-skeleton/section-contracts/competitive-analysis.md +33 -0
  1814. package/templates/room-skeleton/section-contracts/financial-model.md +30 -0
  1815. package/templates/room-skeleton/section-contracts/funding.md +41 -0
  1816. package/templates/room-skeleton/section-contracts/legal-ip.md +30 -0
  1817. package/templates/room-skeleton/section-contracts/market-analysis.md +32 -0
  1818. package/templates/room-skeleton/section-contracts/opportunity-bank.md +39 -0
  1819. package/templates/room-skeleton/section-contracts/problem-definition.md +32 -0
  1820. package/templates/room-skeleton/section-contracts/solution-design.md +36 -0
  1821. package/templates/room-skeleton/section-contracts/strategy.md +35 -0
  1822. package/templates/room-skeleton/section-contracts/team-execution.md +36 -0
  1823. package/templates/shared.css +427 -0
  1824. package/templates/showcase/blueprint.html +432 -0
  1825. package/templates/showcase/library.html +571 -0
  1826. package/templates/showcase/narrative.html +361 -0
  1827. package/templates/showcase/overview.html +468 -0
  1828. package/templates/showcase/synthesis.html +473 -0
  1829. package/templates/signature-footer.html +34 -0
@@ -0,0 +1,3620 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Phase 83-07: Mid-session intent classifier (Tier 2).
6
+ *
7
+ * UserPromptSubmit hook. Reads the user message from stdin (JSON payload
8
+ * or raw text), scores it against every registered room plus every sealed
9
+ * room under MindrianRooms, and writes a conversational warning to stdout
10
+ * when the highest-scoring room is NOT the active room. Under the
11
+ * UserPromptSubmit contract stdout is injected as additionalContext into
12
+ * the conversation.
13
+ *
14
+ * Advisory only. Never blocks. Never exits non-zero on scope mismatch.
15
+ * Only exits non-zero on internal error (and even then we prefer exit 0
16
+ * so we do not pollute the conversation with error noise). Hard 200ms
17
+ * budget; if exceeded mid-walk, exits 0 silently. Writes at most one
18
+ * warning block per invocation.
19
+ *
20
+ * Design rules per plan 83-07:
21
+ * - No dependencies. CJS only. Inline helpers.
22
+ * - Speed beats accuracy. False positives are tolerable, 83-06 catches
23
+ * the write-time false negatives.
24
+ * - Sealed room names surface in the warning only, not sealed content;
25
+ * sealed names are already surfaced by the 83-03 SEALED ROOMS block
26
+ * so this is not a new leak vector.
27
+ */
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+ const os = require('node:os');
32
+ const crypto = require('node:crypto');
33
+
34
+ const BUDGET_MS = 200;
35
+
36
+ // Phase 225-01 (PD-3, anti-overfire): the zero-score no-match gate fires only on a
37
+ // SUBSTANTIVE message. Short zero-score turns ("thanks", "do it") are common, and
38
+ // gating each would repeat the Phase-210 over-enforcement mistake that phase reverted.
39
+ // Default floor 8 surviving tokens, env-overridable via
40
+ // MINDRIAN_ZERO_SCORE_GATE_MIN_TOKENS (parseInt; a NaN or < 1 override falls back to 8).
41
+ const ZERO_SCORE_GATE_MIN_TOKENS = (function () {
42
+ const n = parseInt(process.env.MINDRIAN_ZERO_SCORE_GATE_MIN_TOKENS, 10);
43
+ if (Number.isNaN(n) || n < 1) return 8;
44
+ return n;
45
+ })();
46
+
47
+ const STOP_WORDS = new Set([
48
+ 'a', 'an', 'the', 'of', 'to', 'in', 'on', 'at',
49
+ 'and', 'or', 'but', 'is', 'are', 'was', 'were', 'be', 'been',
50
+ ]);
51
+
52
+ // Brand / boilerplate STOP-SET (2026-06-28 fix). Product-branded and paste-chrome
53
+ // tokens are credited by scoreRoom as if they were real room-name or entity
54
+ // signals, so a product-branded paste block (which mentions "mindrianOS", "Larry",
55
+ // "room" ...) spuriously trips the F.8 session-binding gate on nearly every turn.
56
+ // These tokens earn NEITHER name-match NOR entity-match credit: a room-scope
57
+ // interruption must be driven by a genuine venture/entity token, not by the
58
+ // product's own name. A real room-name mention still fires because scoreRoom
59
+ // requires at least one NON-boilerplate name token to have matched.
60
+ const BRAND_STOP_SET = new Set([
61
+ 'mindrianos', 'mindrian', 'larry', 'room', 'rooms',
62
+ 'paste', 'block', 'onboarding', 'mos', 'plugin',
63
+ ]);
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Room root + registry resolution. Mirrors 83-06 helpers (kept inline rather
67
+ // than extracted to lib/core to avoid a retrofit of 83-06 for a tiny helper).
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function resolveMindrianRoomsRoot() {
71
+ const envRoot = process.env.MINDRIAN_ROOMS_ROOT;
72
+ if (envRoot && fs.existsSync(envRoot)) return envRoot;
73
+ const home = process.env.HOME || os.homedir();
74
+ if (!home) return null;
75
+ const defaultRoot = path.join(home, 'MindrianRooms');
76
+ if (fs.existsSync(defaultRoot)) return defaultRoot;
77
+ try {
78
+ const entries = fs.readdirSync(home, { withFileTypes: true });
79
+ for (const e of entries) {
80
+ if (!e.isDirectory()) continue;
81
+ if (e.name === 'MindrianRooms') return path.join(home, e.name);
82
+ const sub = path.join(home, e.name);
83
+ try {
84
+ const subEntries = fs.readdirSync(sub, { withFileTypes: true });
85
+ for (const s of subEntries) {
86
+ if (s.isDirectory() && s.name === 'MindrianRooms') {
87
+ return path.join(sub, s.name);
88
+ }
89
+ }
90
+ } catch (_) { /* ignore */ }
91
+ }
92
+ } catch (_) { /* ignore */ }
93
+ return null;
94
+ }
95
+
96
+ function readRegistry(root) {
97
+ try {
98
+ const regPath = path.join(root, '.rooms', 'registry.json');
99
+ const raw = fs.readFileSync(regPath, 'utf8');
100
+ return JSON.parse(raw);
101
+ } catch (_) {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ function activeRoomFromRegistry(reg) {
107
+ if (reg && typeof reg.active === 'string' && reg.active.length > 0) {
108
+ return reg.active;
109
+ }
110
+ return null;
111
+ }
112
+
113
+ function registeredRoomNames(reg) {
114
+ if (!reg) return [];
115
+ // Support both array form and object form for rooms field.
116
+ if (Array.isArray(reg.rooms)) {
117
+ const out = [];
118
+ for (const r of reg.rooms) {
119
+ if (typeof r === 'string') out.push(r);
120
+ else if (r && typeof r.name === 'string') out.push(r.name);
121
+ }
122
+ return out;
123
+ }
124
+ if (reg.rooms && typeof reg.rooms === 'object') {
125
+ return Object.keys(reg.rooms);
126
+ }
127
+ return [];
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Phase 94-06: Room classifier strict-mode override
132
+ // ---------------------------------------------------------------------------
133
+ //
134
+ // When the user is unambiguous (numeric position, explicit slug, quoted
135
+ // exact name), bypass the similarity heuristic and resolve directly. The
136
+ // override emits a Section-8 decision-trace edge with
137
+ // routing_source: 'strict_mode' per Canon Part 4. Canon Part 3 (10-verb
138
+ // vocabulary) preserved: strict-mode is a routing OVERRIDE, not a new
139
+ // verb. Canon Part 7 (reuse before build): the detector lives in its
140
+ // own pure-function module under lib/core/ so unit tests can require it
141
+ // without booting the classifier hot path.
142
+ //
143
+ // Order of precedence (locked in plan 94-06 CONTEXT.md decisions):
144
+ // 1. numeric 'switch to 8' or '8' -> registry[N-1]
145
+ // 2. explicit slug 'curriculum-redesign-fall-2026' -> exact slug
146
+ // '/mos:rooms <slug>'
147
+ // 3. quoted name '"Beta"' / '"Curriculum Redesign"' -> name OR slug
148
+ // 4. fall through -> existing similarity heuristic (unchanged)
149
+ //
150
+ // First match wins. Lawrence Aronhime's 2026-04-28 callouts 1, 2, 4 are
151
+ // fenced by this layer. Callout 3 ("the curriculum room", natural
152
+ // language) is known-deferred to v1.11.3.
153
+ const strictModeMod = require(
154
+ path.join(__dirname, '..', 'lib', 'core', 'room-classifier-strict-mode.cjs')
155
+ );
156
+
157
+ // Phase 127.3 Plan 02: canonical-single-source for active-room resolution.
158
+ // resolveActiveRoomDir() (defined ~line 680) is now a thin wrapper that
159
+ // delegates to lib/core/resolve-active-room.cjs (the Phase 127.3 Plan 00
160
+ // chokepoint). The wrapper bridges the env-var name MINDRIAN_ROOMS_ROOT
161
+ // (this script's Phase 91 nav convention) to MINDRIAN_ROOMS_HOME (the
162
+ // chokepoint's convention) so existing call sites + test fixtures that
163
+ // set MINDRIAN_ROOMS_ROOT continue to work without modification. Local
164
+ // helpers activeRoomFromRegistry + registeredRoomNames + readRegistry +
165
+ // resolveRoomsRootForNav are PRESERVED because they are consumed
166
+ // elsewhere in this script for scope-matching at multiple call sites.
167
+ // RCA resolve-active-room-cross-session-bleed (D1): resolveActiveRoomDir is
168
+ // MACHINE-WIDE by design ("which room is active on this machine"). Using it as
169
+ // THIS hook's room made every concurrent CLI session on one machine render its
170
+ // F.1 Decision Gate card -- and write its decision traces -- against whichever
171
+ // room an unrelated session last activated. resolveSessionRoom is the shipped
172
+ // session-scoped precedence (session.primary, then the reg.active seed for a
173
+ // genuinely unbound session); the machine-wide resolver stays imported only as
174
+ // the seed/fallback and for the sessionId hash-fallback input.
175
+ const {
176
+ resolveActiveRoomDir: _chokepointResolveActiveRoomDir,
177
+ resolveSessionRoom: _chokepointResolveSessionRoom,
178
+ } = require(
179
+ path.join(__dirname, '..', 'lib', 'core', 'resolve-active-room.cjs')
180
+ );
181
+ const detectStrictMode = strictModeMod.detectStrictMode;
182
+ const STRICT_MODE_ROUTING_SOURCE = strictModeMod.STRICT_MODE_ROUTING_SOURCE;
183
+
184
+ // Phase 194-04 (PSB-04/06): the F.8 binding-gate decision, composed from the shipped
185
+ // session-binding-consumer. runBindingGate is the ONE session-scope test (D-02): the
186
+ // single-equality `best.name === active` silence test is graduated to SET membership
187
+ // in this session's binding. It FAILS OPEN by construction (a poisoned / unreadable
188
+ // binding returns {degraded:true, blocked:false}), so a degraded gate degrades to the
189
+ // legacy advisory and NEVER hard-blocks the turn (the 83-07 never-block contract).
190
+ const { runBindingGate: _runBindingGate } = require(
191
+ path.join(__dirname, '..', 'lib', 'workflow', 'session-binding-consumer.cjs')
192
+ );
193
+ // The reserved dev-repo/no-room binding: a first-class F.8 option (label is human,
194
+ // the slug is the sentinel). Selecting it binds the session to "no room" and kills
195
+ // the false plugin-CLAUDE.md write-block (D-03/D-04).
196
+ const NO_ROOM_SLUG = '__no_room__';
197
+ const NO_ROOM_LABEL = 'dev repo / no room';
198
+
199
+ function isSealed(root, roomName) {
200
+ try {
201
+ return fs.existsSync(path.join(root, roomName, 'GUARDRAIL.md'));
202
+ } catch (_) {
203
+ return false;
204
+ }
205
+ }
206
+
207
+ // Walk MindrianRooms root one level deep looking for GUARDRAIL.md. Matches
208
+ // 83-03 walker logic: sealed rooms are always direct children of the root.
209
+ function discoverSealedRooms(root, deadline) {
210
+ const out = [];
211
+ let entries;
212
+ try {
213
+ entries = fs.readdirSync(root, { withFileTypes: true });
214
+ } catch (_) {
215
+ return out;
216
+ }
217
+ for (const e of entries) {
218
+ if (Date.now() > deadline) return out;
219
+ if (!e.isDirectory()) continue;
220
+ if (e.name.startsWith('.')) continue;
221
+ if (isSealed(root, e.name)) out.push(e.name);
222
+ }
223
+ return out;
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Tokenizer + fingerprint builder
228
+ // ---------------------------------------------------------------------------
229
+
230
+ function tokenize(text) {
231
+ if (!text || typeof text !== 'string') return [];
232
+ const lowered = text.toLowerCase();
233
+ const rough = lowered.split(/[^a-z0-9]+/);
234
+ const out = [];
235
+ for (const t of rough) {
236
+ if (!t) continue;
237
+ if (t.length < 2) continue;
238
+ if (STOP_WORDS.has(t)) continue;
239
+ out.push(t);
240
+ }
241
+ return out;
242
+ }
243
+
244
+ function splitRoomName(name) {
245
+ if (!name || typeof name !== 'string') return [];
246
+ return tokenize(name.replace(/[-_]+/g, ' '));
247
+ }
248
+
249
+ // Read STATE.md frontmatter `project:` plus top-30-line capitalized phrases.
250
+ // The capitalized-phrase heuristic is deliberately crude: grab any two-or-more
251
+ // consecutive Capitalized words and treat them as entity candidates. Single
252
+ // Capitalized words are ignored (too noisy at line starts).
253
+ function buildFingerprint(root, roomName, deadline) {
254
+ const tokens = new Set();
255
+ for (const t of splitRoomName(roomName)) tokens.add(t);
256
+ if (Date.now() > deadline) return tokens;
257
+ let statePath = path.join(root, roomName, 'STATE.md');
258
+ let raw = '';
259
+ try {
260
+ raw = fs.readFileSync(statePath, 'utf8');
261
+ } catch (_) {
262
+ return tokens;
263
+ }
264
+ const lines = raw.split('\n').slice(0, 30);
265
+ // Parse frontmatter project: field if present.
266
+ let inFrontmatter = false;
267
+ for (const line of lines) {
268
+ if (line.trim() === '---') {
269
+ inFrontmatter = !inFrontmatter;
270
+ continue;
271
+ }
272
+ if (inFrontmatter) {
273
+ const m = line.match(/^project\s*:\s*(.+)\s*$/i);
274
+ if (m) {
275
+ const val = m[1].replace(/^["']|["']$/g, '');
276
+ for (const t of tokenize(val)) tokens.add(t);
277
+ }
278
+ continue;
279
+ }
280
+ // Body: capitalized-phrase extraction. Match runs of 2+ Capitalized words.
281
+ const phraseRe = /\b([A-Z][a-z0-9]+(?:\s+[A-Z][a-z0-9]+)+)\b/g;
282
+ let pm;
283
+ while ((pm = phraseRe.exec(line)) !== null) {
284
+ for (const t of tokenize(pm[1])) tokens.add(t);
285
+ }
286
+ }
287
+ return tokens;
288
+ }
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // Scoring
292
+ // ---------------------------------------------------------------------------
293
+
294
+ function scoreRoom(messageTokenSet, roomName, fingerprint) {
295
+ // Room-name exact match: every token of the room name must appear in the
296
+ // message token set. Weight = 5.
297
+ const nameTokens = splitRoomName(roomName);
298
+ let nameHit = nameTokens.length > 0;
299
+ let nameHasNonBrand = false;
300
+ for (const t of nameTokens) {
301
+ if (!messageTokenSet.has(t)) { nameHit = false; break; }
302
+ if (!BRAND_STOP_SET.has(t)) nameHasNonBrand = true;
303
+ }
304
+ // Brand/boilerplate discount (2026-06-28): a name match composed ENTIRELY of
305
+ // product/boilerplate tokens is not a real room signal (a product-branded paste
306
+ // would satisfy it), so require at least one non-boilerplate name token.
307
+ if (nameHit && !nameHasNonBrand) nameHit = false;
308
+ let score = nameHit ? 5 : 0;
309
+ let nameMatch = nameHit;
310
+ let entityMatches = 0;
311
+ for (const t of fingerprint) {
312
+ // Do not double-count the room-name tokens as entity matches.
313
+ if (nameTokens.indexOf(t) !== -1) continue;
314
+ // Brand/boilerplate tokens earn no entity credit: a product-branded paste
315
+ // block must not accumulate entity matches that trip the binding gate.
316
+ if (BRAND_STOP_SET.has(t)) continue;
317
+ if (messageTokenSet.has(t)) {
318
+ score += 1;
319
+ entityMatches += 1;
320
+ }
321
+ }
322
+ return { score: score, nameMatch: nameMatch, entityMatches: entityMatches };
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Message extraction
327
+ // ---------------------------------------------------------------------------
328
+
329
+ function readStdinSync() {
330
+ try {
331
+ return fs.readFileSync(0, 'utf8');
332
+ } catch (_) {
333
+ return '';
334
+ }
335
+ }
336
+
337
+ function extractMessage(raw) {
338
+ if (!raw || !raw.trim()) return '';
339
+ const trimmed = raw.trim();
340
+ // Try JSON first.
341
+ let parsed = null;
342
+ try {
343
+ parsed = JSON.parse(trimmed);
344
+ } catch (_) { /* not json */ }
345
+ if (parsed && typeof parsed === 'object') {
346
+ const fields = ['user_message', 'prompt', 'message', 'text'];
347
+ for (const f of fields) {
348
+ const v = parsed[f];
349
+ if (typeof v === 'string' && v.length > 0) return v;
350
+ }
351
+ // Nested: some hook envelopes put the prompt in tool_input or payload.
352
+ if (parsed.payload && typeof parsed.payload === 'object') {
353
+ for (const f of fields) {
354
+ const v = parsed.payload[f];
355
+ if (typeof v === 'string' && v.length > 0) return v;
356
+ }
357
+ }
358
+ return '';
359
+ }
360
+ // Raw text fallback.
361
+ return trimmed;
362
+ }
363
+
364
+ // reach-sensor-relevance-gap A1 (root cause A): extract the Claude session UUID
365
+ // from the hook stdin payload. Claude Code passes { session_id, prompt, ... } to
366
+ // the UserPromptSubmit hook; extractMessage already parses this envelope for the
367
+ // MESSAGE but DISCARDS session_id. That id is the SAME UUID the MCP room_bind
368
+ // writer keys the session binding under (tool-router.cjs effectiveSessionId =
369
+ // extra.sessionId). Lifting it here lets resolveSessionId align the CLI READ key
370
+ // with the MCP WRITE key, so a bound session is actually seen by the F.8 binding
371
+ // gate (and by Phase 225's zero-score gate, which reads the same key source).
372
+ // Pure + non-throwing: a non-JSON or session_id-less payload yields '' and
373
+ // resolveSessionId falls through to its prior env/hash resolution (byte-identical
374
+ // to the pre-fix behavior when no payload session_id is present).
375
+ function extractSessionId(raw) {
376
+ if (!raw || !raw.trim()) return '';
377
+ let parsed = null;
378
+ try {
379
+ parsed = JSON.parse(raw.trim());
380
+ } catch (_) { return ''; }
381
+ if (!parsed || typeof parsed !== 'object') return '';
382
+ if (typeof parsed.session_id === 'string' && parsed.session_id.length > 0) {
383
+ return parsed.session_id;
384
+ }
385
+ // Nested: some hook envelopes wrap the fields under `payload`.
386
+ if (parsed.payload && typeof parsed.payload === 'object'
387
+ && typeof parsed.payload.session_id === 'string'
388
+ && parsed.payload.session_id.length > 0) {
389
+ return parsed.payload.session_id;
390
+ }
391
+ return '';
392
+ }
393
+
394
+ // ---------------------------------------------------------------------------
395
+ // Main
396
+ // ---------------------------------------------------------------------------
397
+
398
+ // Phase 91-02: stdin is read ONCE at module-init time so the engine
399
+ // integration block can short-circuit on empty messages. The original
400
+ // Phase 83 main() reads it again, but readStdinSync of fd 0 returns ''
401
+ // on second call (EOF) -- which is fine because main() also early-
402
+ // exits on empty message. We pass the raw stdin in via module scope.
403
+ const STDIN_RAW = readStdinSync();
404
+ const STDIN_MESSAGE = extractMessage(STDIN_RAW);
405
+ // reach-sensor-relevance-gap A1: the real Claude session UUID from the hook
406
+ // payload (empty string when absent). resolveSessionId prefers this over the
407
+ // env/hash fallback so the CLI read key matches the MCP room_bind write key.
408
+ const STDIN_SESSION_ID = extractSessionId(STDIN_RAW);
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // Phase 94-06: emitStrictModeOverride
412
+ //
413
+ // Writes the strict-mode override warning to stdout (additionalContext per
414
+ // the UserPromptSubmit hook contract) AND a Section-8 decision-trace edge
415
+ // with routing_source: 'strict_mode' per Canon Part 4. The trace edge is
416
+ // persisted under <activeRoomDir>/.mindrian/decision-traces/<session>.json
417
+ // using the same writer as the Phase 91-02 navigation-engine block so
418
+ // /mos:explain-decision (Plan 91-05) surfaces the override consistently
419
+ // with all other routing decisions.
420
+ //
421
+ // Failure discipline: any throw is swallowed; the user prompt is never
422
+ // disrupted. If trace persistence fails, the stdout warning still emits.
423
+ // ---------------------------------------------------------------------------
424
+ function emitStrictModeOverride(strictMatch, activeSlug, roomsRoot) {
425
+ const overrideWarning =
426
+ 'Strict-mode override: input "' + strictMatch.input + '" matches room ' +
427
+ strictMatch.slug + ' (pattern: ' + strictMatch.pattern + ', position-' +
428
+ 'or-slug-or-quoted-name match). Active room is ' + activeSlug + '. ' +
429
+ 'Acknowledge the explicit room reference in your next reply. Confirm ' +
430
+ 'with the user whether to switch rooms before drafting any artifact ' +
431
+ 'related to ' + strictMatch.slug + '.';
432
+
433
+ const systemMessage =
434
+ 'strict-mode override: input matches room ' + strictMatch.slug +
435
+ ' (pattern: ' + strictMatch.pattern + ') but active room is ' + activeSlug;
436
+
437
+ const envelope = {
438
+ hookSpecificOutput: {
439
+ hookEventName: 'UserPromptSubmit',
440
+ additionalContext: overrideWarning,
441
+ },
442
+ systemMessage: systemMessage,
443
+ };
444
+
445
+ try {
446
+ process.stdout.write(JSON.stringify(envelope) + '\n');
447
+ } catch (_e) {
448
+ // Last-resort fallback: raw text.
449
+ try { process.stdout.write(overrideWarning + '\n'); } catch (_e2) {}
450
+ }
451
+
452
+ // Persist a Section-8 decision-trace edge so /mos:explain-decision
453
+ // surfaces this override alongside engine + legacy + mixed routings.
454
+ // Best-effort; never throws back to main().
455
+ try {
456
+ const activeRoomDir = path.join(roomsRoot, activeSlug);
457
+ if (!fs.existsSync(activeRoomDir)) return;
458
+ const sessionId = resolveSessionId(activeRoomDir);
459
+ const turn = appendTraceTurnNumber(activeRoomDir, sessionId);
460
+ const traceEntry = {
461
+ turn: turn,
462
+ at: new Date().toISOString(),
463
+ routing_source: STRICT_MODE_ROUTING_SOURCE,
464
+ routing_reason: 'strict_mode_pattern_matched',
465
+ strict_mode_pattern: strictMatch.pattern,
466
+ strict_mode_input: strictMatch.input,
467
+ resolved_slug: strictMatch.slug,
468
+ active_slug_before: activeSlug,
469
+ };
470
+ persistDecisionTrace(activeRoomDir, sessionId, traceEntry);
471
+ } catch (_e) { /* fire-and-forget */ }
472
+ }
473
+
474
+ function main() {
475
+ const start = Date.now();
476
+ const deadline = start + BUDGET_MS;
477
+
478
+ const message = STDIN_MESSAGE;
479
+ if (!message) return 0;
480
+
481
+ const root = resolveMindrianRoomsRoot();
482
+ if (!root) return 0;
483
+
484
+ const reg = readRegistry(root);
485
+ const active = activeRoomFromRegistry(reg);
486
+ const registered = registeredRoomNames(reg);
487
+
488
+ if (Date.now() > deadline) return 0;
489
+
490
+ // ----- Phase 94-06: Strict-mode override (top of room-resolution path) -----
491
+ //
492
+ // When the user's input is unambiguous (numeric position, explicit slug,
493
+ // or quoted exact name), bypass the similarity heuristic and resolve
494
+ // directly. The override emits a Section-8 trace edge with
495
+ // routing_source: 'strict_mode' per Canon Part 4. The similarity
496
+ // heuristic stays as fallback for ambiguous natural-language input.
497
+ //
498
+ // This block fires BEFORE the similarity loop and can short-circuit
499
+ // main() with an explicit override warning. The standard similarity
500
+ // path is unchanged when strict-mode does not match.
501
+ const strictMatch = detectStrictMode(message, reg);
502
+ if (strictMatch && active && strictMatch.slug !== active) {
503
+ // User's input matched a strict-mode pattern AND the matched room is
504
+ // NOT the active room. Emit a high-confidence override warning + a
505
+ // Section-8 trace edge that downstream readers can attribute to the
506
+ // strict-mode layer.
507
+ emitStrictModeOverride(strictMatch, active, root);
508
+ return 0;
509
+ }
510
+ // If strictMatch is non-null AND matches the active room, route the silence
511
+ // through session scope too (PSB-04): do NOT leave a second single-active-room
512
+ // path alive. A BOUND session whose strict-named room is on-scope silences (the
513
+ // user is just confirming a bound room). An UNBOUND session falls THROUGH to the
514
+ // similarity path so the F.8 binding gate can bind it (naming the room explicitly
515
+ // is a strong bind signal). Fails open: any error preserves the legacy silence.
516
+ if (strictMatch && active && strictMatch.slug === active) {
517
+ let strictGate = null;
518
+ try {
519
+ const sessionId = resolveSessionId(resolveActiveRoomDir());
520
+ strictGate = _runBindingGate({ sessionId: sessionId, topRoom: strictMatch.slug, home: root });
521
+ } catch (_e) {
522
+ strictGate = null; // fail-open: preserve the legacy silence
523
+ }
524
+ // On-scope OR a degraded/unavailable gate -> legacy silence. Only a healthy
525
+ // off-scope gate falls through to bind.
526
+ if (!strictGate || strictGate.degraded || strictGate.onScope) {
527
+ return 0;
528
+ }
529
+ }
530
+ // strictMatch is null: continue to similarity heuristic (existing path).
531
+ // ----- end Phase 94-06 strict-mode override -----
532
+
533
+ const sealedRooms = discoverSealedRooms(root, deadline);
534
+ if (Date.now() > deadline) return 0;
535
+
536
+ // Deduplicate room corpus.
537
+ const corpus = [];
538
+ const seen = new Set();
539
+ for (const name of registered.concat(sealedRooms)) {
540
+ if (!name || seen.has(name)) continue;
541
+ seen.add(name);
542
+ corpus.push(name);
543
+ }
544
+ if (corpus.length === 0) return 0;
545
+
546
+ const messageTokens = tokenize(message);
547
+ if (messageTokens.length === 0) return 0;
548
+ const messageTokenSet = new Set(messageTokens);
549
+
550
+ let best = null;
551
+ let activeScore = null;
552
+ const scored = [];
553
+ for (const roomName of corpus) {
554
+ if (Date.now() > deadline) return 0;
555
+ const fp = buildFingerprint(root, roomName, deadline);
556
+ const s = scoreRoom(messageTokenSet, roomName, fp);
557
+ scored.push({ name: roomName, score: s.score });
558
+ if (roomName === active) {
559
+ activeScore = s;
560
+ }
561
+ if (!best || s.score > best.score) {
562
+ best = { name: roomName, score: s.score, nameMatch: s.nameMatch, entityMatches: s.entityMatches };
563
+ }
564
+ }
565
+
566
+ // ----- Phase 225-01 (SEED-039 proving_case_2): the zero-score no-match gate -----
567
+ //
568
+ // best.score === 0 means NO room fingerprint matched this message. The pre-225
569
+ // blanket `return 0` here silently suppressed the Phase-194 F.8 gate on a
570
+ // conversational reframe (the Gaurav student-reframe incident, 2026-07-14), so the
571
+ // write landed unchallenged in the old bound primary (threat T-225-01). When the
572
+ // session HAS a bound primary AND the message is substantive, fire a distinct
573
+ // "no room matched" gate offering continue-in-primary / new-project / no-room
574
+ // (REQ-1). On a zero score best.name is corpus[0] (semantically meaningless,
575
+ // Pitfall 1), so the gate NEVER reuses it (REQ-2). Every path terminates in
576
+ // `return 0`; the whole branch is wrapped fail-open (REQ-3, Canon 83-07).
577
+ //
578
+ // IN-01 fix (Phase 225 REVIEW-FIX): `best` is guaranteed non-null here, not
579
+ // merely defensive -- `corpus.length === 0` (line 499) and
580
+ // `messageTokens.length === 0` (line 502) both return early above, so the
581
+ // scoring loop (lines 508-519) always executes at least one iteration and
582
+ // always assigns `best` on its first pass. No `!best` guard needed.
583
+ if (best.score === 0) {
584
+ try {
585
+ // PD-3 substantiality floor: short zero-score messages are common; gating each
586
+ // would repeat the Phase-210 over-enforcement mistake.
587
+ //
588
+ // WR-02 fix (Phase 225 REVIEW-FIX): measure DISTINCT surviving tokens
589
+ // (messageTokenSet.size), not the raw duplicate-inclusive count
590
+ // (messageTokens.length). The floor's stated intent (docs/ENV-TUNING.md
591
+ // PD-3: "a trivial acknowledgement... must NOT fire the gate") is a
592
+ // SUBSTANTIALITY check -- a message needs real distinct content, not just
593
+ // enough words. Counting raw tokens let a trivial, repetitive message like
594
+ // "ok ok ok ok ok ok ok ok" (8 raw tokens, 1 distinct) clear an 8-token
595
+ // floor and fire the interruptive gate, exactly the over-enforcement this
596
+ // floor exists to prevent. messageTokenSet is already built one line above
597
+ // (line 503) for the scoring loop; reusing it here costs nothing extra.
598
+ if (messageTokenSet.size >= ZERO_SCORE_GATE_MIN_TOKENS) {
599
+ // The machine-wide value is resolved FIRST only to seed
600
+ // resolveSessionId's sha256 hash fallback byte-identically to pre-fix;
601
+ // the roomDir actually used is THIS session's (D1).
602
+ const machineWideDir = resolveActiveRoomDir();
603
+ const sessionId = resolveSessionId(machineWideDir);
604
+ const roomDir = resolveSessionRoomDir(sessionId, machineWideDir);
605
+ const binding = require(
606
+ path.join(__dirname, '..', 'lib', 'core', 'session-binding.cjs')
607
+ ).readSessionBinding(sessionId, { home: root });
608
+ // Fire only for a session with a real bound primary. An unbound session
609
+ // (primary null), or one that explicitly chose dev-repo/no-room
610
+ // (primary === __no_room__, where every substantive dev-repo message scores
611
+ // 0), must never be nagged (REQ-3/REQ-6 no-regression). PD-1: this
612
+ // deliberately does NOT check binding.sticky -- a total-miss is a
613
+ // strong-enough new-project signal to prompt ONCE even under sticky.
614
+ if (binding && typeof binding.primary === 'string' && binding.primary.length > 0
615
+ && binding.primary !== NO_ROOM_SLUG
616
+ && !zeroScoreGateAlreadyOffered(roomDir, sessionId)) {
617
+ if (emitNoMatchGate({ primary: binding.primary, roomDir: roomDir, sessionId: sessionId })) {
618
+ return 0; // gate rendered
619
+ }
620
+ // emitNoMatchGate returned false (renderer unavailable) -> legacy silence.
621
+ }
622
+ }
623
+ } catch (_e) {
624
+ // fail-open: any fault falls through to the identical legacy silence (83-07).
625
+ }
626
+ // Unbound session, short message, no-room primary, already-offered, or any fault:
627
+ // ALL take the identical legacy silence (byte-for-byte the pre-225 return 0).
628
+ return 0;
629
+ }
630
+
631
+ // ----- Phase 194-04 (PSB-04): graduate the tripwire to the F.8 binding gate -----
632
+ //
633
+ // The pre-194 silence test was a single-equality `best.name === active`: it went
634
+ // quiet ONLY when the top room WAS the one global active room -- the racy
635
+ // single-active-room brain this phase kills. The graduated test is SET membership
636
+ // in THIS session's binding (runBindingGate -> resolveSessionScope, the ONE
637
+ // on-scope test, D-02). resolveSessionId is COMPOSED (never re-derived). Pitfall 2
638
+ // degenerate case: with no CLAUDE_SESSION_ID the fallback hashes roomDir+day, so a
639
+ // CLI "session" is per-room-per-day -- acceptable per the plan.
640
+ let bindingGate = null;
641
+ try {
642
+ const sessionId = resolveSessionId(resolveActiveRoomDir());
643
+ bindingGate = _runBindingGate({ sessionId: sessionId, topRoom: best.name, home: root });
644
+ } catch (_e) {
645
+ bindingGate = null; // fail-open: fall through to the legacy advisory
646
+ }
647
+
648
+ // On-scope: the top room is a MEMBER of this session's bound SET -> SILENCE. This
649
+ // is the spurious "intent mismatch" warning fix. A degraded gate is NOT on-scope,
650
+ // so it correctly falls through to the legacy advisory below (never a hard block).
651
+ if (bindingGate && !bindingGate.degraded && bindingGate.onScope) return 0;
652
+
653
+ // Unbound / off-scope with a HEALTHY gate: render the F.8 multi-select binding gate
654
+ // (candidate rooms + a first-class 'dev repo / no room' option) instead of the
655
+ // legacy single-active-room nag. Fails open: a render/require error returns 0 and
656
+ // falls through to the legacy advisory (PSB-06).
657
+ if (bindingGate && !bindingGate.degraded && bindingGate.fire) {
658
+ try {
659
+ // D1: the F.8 gate's trace state must live in THIS session's room, not the
660
+ // machine-wide one. When the active room moved under an unbound session,
661
+ // the answered-gate payload became unreadable on the next turn (it was
662
+ // written into a different directory), so the gate re-fired as if never
663
+ // answered -- the "dozens of declined cards" shape this RCA explains.
664
+ const machineWideDir = resolveActiveRoomDir();
665
+ const sessionId = resolveSessionId(machineWideDir);
666
+ const roomDir = resolveSessionRoomDir(sessionId, machineWideDir);
667
+ if (emitBindingGate({ best: best, scored: scored, sealedRooms: sealedRooms, roomDir: roomDir, sessionId: sessionId })) {
668
+ return 0;
669
+ }
670
+ // emitBindingGate returned false (renderer unavailable) -> fall through to legacy.
671
+ } catch (_e) {
672
+ // fail-open: fall through to the legacy advisory.
673
+ }
674
+ }
675
+
676
+ // ----- legacy advisory (fail-open fallback: reached only when the gate degraded or
677
+ // the F.8 render was unavailable). Preserves the pre-194 racy behavior verbatim. --
678
+ if (active && best.name === active) return 0;
679
+
680
+ // Tie-break: prefer active. If active has same score as best, silence.
681
+ if (active && activeScore && activeScore.score >= best.score) return 0;
682
+
683
+ // Weak-match silencing: a single 1-point entity match is not strong enough.
684
+ if (!best.nameMatch && best.entityMatches < 2) return 0;
685
+
686
+ const otherCount = best.score;
687
+ const activeCountDisplay = activeScore ? activeScore.score : 0;
688
+ const activeDisplay = active || '(none)';
689
+
690
+ let warning =
691
+ 'Intent mismatch detected. User message contains ' + otherCount +
692
+ ' tokens matching room ' + best.name + ', only ' + activeCountDisplay +
693
+ ' matching active room ' + activeDisplay + '. ' +
694
+ 'Acknowledge the mismatch in your next reply. Confirm with the user ' +
695
+ 'whether to switch rooms before drafting any artifact related to ' +
696
+ best.name + '.';
697
+
698
+ if (sealedRooms.indexOf(best.name) !== -1) {
699
+ warning += '\nNote: ' + best.name + ' is a sealed room. Any artifact ' +
700
+ 'related to it must be authored from inside that room (see 83-06).';
701
+ }
702
+
703
+ // 88.1-03: systemMessage retrofit. Emit a JSON envelope so Claude Code
704
+ // 2.1.x renders a one-line status alongside the additionalContext
705
+ // injection. LOCAL-only (Canon Part 8): room slugs only, no user prompt
706
+ // text echoed. Silent on the happy path (no message when rooms match).
707
+ const systemMessage = 'intent mismatch: suggested room ' + best.name +
708
+ ' (score ' + otherCount + ') outweighs active room ' + activeDisplay +
709
+ ' (score ' + activeCountDisplay + ')';
710
+ const envelope = {
711
+ hookSpecificOutput: {
712
+ hookEventName: 'UserPromptSubmit',
713
+ additionalContext: warning
714
+ },
715
+ systemMessage: systemMessage
716
+ };
717
+ try {
718
+ process.stdout.write(JSON.stringify(envelope) + '\n');
719
+ } catch (_e) {
720
+ // Last-resort fallback: raw text (preserves pre-retrofit behavior).
721
+ process.stdout.write(warning + '\n');
722
+ }
723
+ return 0;
724
+ }
725
+
726
+ // ---------------------------------------------------------------------------
727
+ // Phase 84-06: graph-findings injection (env-gated, default ON)
728
+ //
729
+ // Reads the active room's .proactive-intelligence.json and writes the top 3
730
+ // non-suppressed insights to stdout as an additionalContext block. The block
731
+ // is appended after any topic-mismatch warning emitted by main().
732
+ //
733
+ // Kill switch: set MINDRIAN_COPILOT_INJECT_FINDINGS=0 for byte-identical
734
+ // Phase 83 behavior. Any non-zero/unset value enables injection.
735
+ //
736
+ // Hard cap of 3 findings IS the suppression mechanism (Dependabot lesson:
737
+ // push channels without built-in caps become noise). No config to raise it.
738
+ //
739
+ // Failure discipline: ALL errors swallowed silently. If anything goes wrong,
740
+ // nothing is written and the hook exits 0. The user prompt is never
741
+ // disrupted. Closes SCOPE-NB-08.
742
+ // ---------------------------------------------------------------------------
743
+
744
+ const FINDINGS_BUDGET_MS = 200;
745
+ const FINDINGS_RESERVED_MS = 50; // require >= 50ms remaining of the 200ms budget
746
+ const FINDINGS_SUPPRESS_THRESHOLD = 3; // mirrors lib/core/proactive-intelligence.cjs
747
+ const FINDINGS_CAP = 3;
748
+
749
+ function injectionEnabled() {
750
+ const v = process.env.MINDRIAN_COPILOT_INJECT_FINDINGS;
751
+ if (v === undefined || v === null || v === '') return true;
752
+ if (v === '0' || v === 'false') return false;
753
+ return true;
754
+ }
755
+
756
+ function confidenceRank(c) {
757
+ if (typeof c !== 'string') return 0;
758
+ const lc = c.toLowerCase();
759
+ if (lc === 'high') return 3;
760
+ if (lc === 'medium' || lc === 'med') return 2;
761
+ if (lc === 'low') return 1;
762
+ return 0;
763
+ }
764
+
765
+ function formatFinding(insight) {
766
+ const type = (insight && typeof insight.type === 'string') ? insight.type : 'insight';
767
+ const message = (insight && typeof insight.message === 'string') ? insight.message : '';
768
+ return type + ': ' + message;
769
+ }
770
+
771
+ function injectGraphFindings(injectionStart) {
772
+ // Budget guard: require enough remaining headroom of the 200ms budget.
773
+ const elapsed = Date.now() - injectionStart;
774
+ if (elapsed > (FINDINGS_BUDGET_MS - FINDINGS_RESERVED_MS)) return;
775
+
776
+ const root = resolveMindrianRoomsRoot();
777
+ if (!root) return;
778
+ const reg = readRegistry(root);
779
+ const active = activeRoomFromRegistry(reg);
780
+ if (!active) return;
781
+
782
+ const filePath = path.join(root, active, '.proactive-intelligence.json');
783
+ let raw;
784
+ try {
785
+ raw = fs.readFileSync(filePath, 'utf8');
786
+ } catch (_) {
787
+ return;
788
+ }
789
+
790
+ let data;
791
+ try {
792
+ data = JSON.parse(raw);
793
+ } catch (_) {
794
+ return;
795
+ }
796
+
797
+ const insights = (data && Array.isArray(data.insights)) ? data.insights : [];
798
+ if (insights.length === 0) return;
799
+
800
+ // Filter: drop suppressed (times_shown >= threshold).
801
+ const live = [];
802
+ for (const ins of insights) {
803
+ if (!ins || typeof ins !== 'object') continue;
804
+ const shown = (typeof ins.times_shown === 'number') ? ins.times_shown : 0;
805
+ if (shown >= FINDINGS_SUPPRESS_THRESHOLD) continue;
806
+ live.push(ins);
807
+ }
808
+ if (live.length === 0) return;
809
+
810
+ // Deterministic sort: confidence desc, then stable original order.
811
+ // (Stable sort is guaranteed in V8 since Node 12.)
812
+ const decorated = live.map(function (ins, idx) {
813
+ return { ins: ins, idx: idx, rank: confidenceRank(ins.confidence) };
814
+ });
815
+ decorated.sort(function (a, b) {
816
+ if (b.rank !== a.rank) return b.rank - a.rank;
817
+ return a.idx - b.idx;
818
+ });
819
+
820
+ const top = decorated.slice(0, FINDINGS_CAP);
821
+ if (top.length === 0) return;
822
+
823
+ // Re-check budget before writing; if we blew it, skip silently.
824
+ if ((Date.now() - injectionStart) > (FINDINGS_BUDGET_MS - FINDINGS_RESERVED_MS)) return;
825
+
826
+ const lines = ['## GRAPH FINDINGS (top 3)', ''];
827
+ for (let i = 0; i < top.length; i++) {
828
+ lines.push((i + 1) + '. ' + formatFinding(top[i].ins));
829
+ }
830
+ process.stdout.write(lines.join('\n') + '\n');
831
+ }
832
+
833
+ // ---------------------------------------------------------------------------
834
+ // Phase 91-02: Navigation Engine integration block.
835
+ //
836
+ // On every UserPromptSubmit hook invocation we resolve the active section,
837
+ // read the quadruple + USER.md, call navigation-engine.decide() with a
838
+ // 1200ms hard timeout, persist a decision_trace under
839
+ // .mindrian/decision-traces/<session-id>.json (atomic + 50-entry rotation),
840
+ // and emit a NAVIGATION DECISION (engine v1) block to additionalContext.
841
+ //
842
+ // Canon Part 8 (Graph Boundary): this block is LOCAL-only. brainAvailable
843
+ // is hard-coded to false in Wave 1; Plan 91-07 (Wave 3) opts into the
844
+ // scalar brain-client.isAvailable() handle. ZERO Brain network surface
845
+ // is added here. The decision-trace JSON file is LOCAL.
846
+ //
847
+ // Risk 2 mitigation (engine latency): Promise.race against a 1200ms
848
+ // timeout leaves 800ms hook headroom before the 2000ms ceiling fires.
849
+ // Risk 6 mitigation (trace budget): persisted to disk, rotated at 50
850
+ // entries (drop oldest 10), atomic tmp+rename writes.
851
+ //
852
+ // Failure discipline: any throw or timeout inside this block returns null
853
+ // engineDecision. The Phase 83 classifier flow continues unchanged. No
854
+ // stderr noise on the happy or failure path.
855
+ // ---------------------------------------------------------------------------
856
+
857
+ const NAV_HARD_TIMEOUT_MS = 1200;
858
+ const TRACE_ROTATE_AT = 50;
859
+ const TRACE_KEEP_AFTER_ROTATE = 40;
860
+
861
+ function resolveActiveSectionPathForRoom(roomDir) {
862
+ if (!roomDir) return null;
863
+ try {
864
+ const activeJson = path.join(roomDir, '.mindrian', 'active-section.json');
865
+ if (fs.existsSync(activeJson)) {
866
+ try {
867
+ const data = JSON.parse(fs.readFileSync(activeJson, 'utf8'));
868
+ if (data && typeof data.section === 'string' && data.section.length > 0) {
869
+ const candidate = path.isAbsolute(data.section)
870
+ ? data.section
871
+ : path.join(roomDir, data.section);
872
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
873
+ return candidate;
874
+ }
875
+ }
876
+ } catch (_e) { /* fall through */ }
877
+ }
878
+ } catch (_e) {}
879
+ // Fallback: most-recently-modified subdir containing MINTO.md.
880
+ try {
881
+ const entries = fs.readdirSync(roomDir, { withFileTypes: true })
882
+ .filter(function (d) { return d.isDirectory() && !d.name.startsWith('.'); });
883
+ let best = null;
884
+ let bestMtime = 0;
885
+ for (const d of entries) {
886
+ const sectionPath = path.join(roomDir, d.name);
887
+ const mintoPath = path.join(sectionPath, 'MINTO.md');
888
+ try {
889
+ if (!fs.existsSync(mintoPath)) continue;
890
+ const st = fs.statSync(mintoPath);
891
+ if (st.mtimeMs > bestMtime) {
892
+ bestMtime = st.mtimeMs;
893
+ best = sectionPath;
894
+ }
895
+ } catch (_e) {}
896
+ }
897
+ return best;
898
+ } catch (_e) {
899
+ return null;
900
+ }
901
+ }
902
+
903
+ function resolveRoomsRootForNav() {
904
+ // Wider env-var acceptance than Phase 83 resolveMindrianRoomsRoot.
905
+ // Phase 83 uses MINDRIAN_ROOMS_ROOT; resolve-room (the bash sibling)
906
+ // uses MINDRIAN_ROOMS_HOME. The Phase 91 hot path accepts either so
907
+ // tests + bash + node converge on the same fixture without divergent
908
+ // env conventions.
909
+ const envRoot = process.env.MINDRIAN_ROOMS_ROOT;
910
+ if (envRoot && fs.existsSync(envRoot)) return envRoot;
911
+ const envHome = process.env.MINDRIAN_ROOMS_HOME;
912
+ if (envHome && fs.existsSync(envHome)) return envHome;
913
+ return resolveMindrianRoomsRoot();
914
+ }
915
+
916
+ function resolveActiveRoomDir() {
917
+ // Phase 127.3 Plan 02: delegate to the canonical chokepoint
918
+ // (lib/core/resolve-active-room.cjs). The chokepoint reads
919
+ // MINDRIAN_ROOMS_HOME; this script's pre-127.3 convention was
920
+ // MINDRIAN_ROOMS_ROOT. Bridge the env-var name BEFORE delegating so
921
+ // existing fixtures + production callers that set ROOT (Phase 91 nav
922
+ // convention) keep working. Guard with the "only if HOME not already
923
+ // set" check so callers that set BOTH (with potentially different
924
+ // values) preserve their explicit HOME choice.
925
+ if (process.env.MINDRIAN_ROOMS_ROOT && !process.env.MINDRIAN_ROOMS_HOME) {
926
+ process.env.MINDRIAN_ROOMS_HOME = process.env.MINDRIAN_ROOMS_ROOT;
927
+ }
928
+ return _chokepointResolveActiveRoomDir();
929
+ }
930
+
931
+ // resolveSessionRoomDir -- THIS session's room directory (RCA
932
+ // resolve-active-room-cross-session-bleed, D1).
933
+ //
934
+ // Composes the shipped chokepoint export rather than re-deriving precedence
935
+ // here (SEED-034, the four-guessers lesson). Falls back to the caller-supplied
936
+ // machine-wide value on any fault or miss, so the degraded path is exactly the
937
+ // pre-fix behavior and the hook can never lose its room to a resolver error.
938
+ //
939
+ // Deliberately calls resolveSessionRoom, NOT resolveWriteRoom: the latter's
940
+ // leg 1 walks up from process.cwd() when given no filePath, which would let a
941
+ // session's shell working directory silently outrank its explicit binding.
942
+ //
943
+ // RCA registry-active-session-unbound-inheritance: the optional third argument
944
+ // { requireOwnership: true } asks the chokepoint to refuse a reg.active that a
945
+ // DIFFERENT, still-live session set. When it does refuse, this returns NULL and
946
+ // the caller must skip -- falling back to machineWideDir there would hand the
947
+ // session the exact stranger's room the gate just declined, making the gate a
948
+ // no-op. A THROWN fault still falls back (a resolver error must never cost a
949
+ // session its room), so only a deliberate decline yields null.
950
+ function resolveSessionRoomDir(sessionId, machineWideDir, opts) {
951
+ const requireOwnership = !!(opts && opts.requireOwnership === true);
952
+ try {
953
+ if (typeof _chokepointResolveSessionRoom === 'function') {
954
+ const home = process.env.MINDRIAN_ROOMS_HOME || undefined;
955
+ const r = _chokepointResolveSessionRoom({
956
+ sessionId: sessionId,
957
+ home: home,
958
+ requireOwnership: requireOwnership,
959
+ });
960
+ if (r && typeof r.abs_path === 'string' && r.abs_path.length > 0) return r.abs_path;
961
+ if (requireOwnership) return null;
962
+ }
963
+ } catch (_) { /* fall through to the machine-wide value */ }
964
+ return machineWideDir;
965
+ }
966
+
967
+ function resolveSessionId(roomDir) {
968
+ // reach-sensor-relevance-gap A1 (root cause A): PREFER the hook stdin payload's
969
+ // session_id -- the real Claude session UUID that the MCP room_bind writer keys
970
+ // the binding under (tool-router.cjs effectiveSessionId = extra.sessionId).
971
+ // CLAUDE_SESSION_ID is UNSET in the UserPromptSubmit hook process, so before
972
+ // this the reader always fell through to the sha256(roomDir+ISO-day) hash -- a
973
+ // DIFFERENT namespace from the UUID writer, so readSessionBinding always missed
974
+ // and the F.8 gate (and Phase 225's zero-score gate) fired every turn. Resolution
975
+ // order: payload.session_id -> CLAUDE_SESSION_ID -> sha256 fallback.
976
+ if (typeof STDIN_SESSION_ID === 'string' && STDIN_SESSION_ID.length > 0) {
977
+ return STDIN_SESSION_ID;
978
+ }
979
+ const env = process.env.CLAUDE_SESSION_ID;
980
+ if (typeof env === 'string' && env.length > 0) return env;
981
+ // Fallback: sha256(roomDir + ISO-date).slice(0,12). Date precision day
982
+ // means same room on same day shares a session file when the env hint
983
+ // is missing -- acceptable per the plan's Section "Session ID
984
+ // resolution".
985
+ try {
986
+ const day = new Date().toISOString().slice(0, 10);
987
+ return crypto.createHash('sha256').update((roomDir || '') + day)
988
+ .digest('hex').slice(0, 12);
989
+ } catch (_) {
990
+ return 'unknown';
991
+ }
992
+ }
993
+
994
+ function persistDecisionTrace(roomDir, sessionId, traceEntry) {
995
+ // Fire-and-forget. Any error is swallowed; the engine block never
996
+ // disrupts the user prompt.
997
+ try {
998
+ const dir = path.join(roomDir, '.mindrian', 'decision-traces');
999
+ try { fs.mkdirSync(dir, { recursive: true }); } catch (_) {}
1000
+ const filePath = path.join(dir, sessionId + '.json');
1001
+ let data = { version: 1, session_id: sessionId, traces: [] };
1002
+ try {
1003
+ const raw = fs.readFileSync(filePath, 'utf8');
1004
+ const parsed = JSON.parse(raw);
1005
+ if (parsed && typeof parsed === 'object' && Array.isArray(parsed.traces)) {
1006
+ data = parsed;
1007
+ if (data.session_id !== sessionId) data.session_id = sessionId;
1008
+ if (data.version !== 1) data.version = 1;
1009
+ }
1010
+ } catch (_) { /* first write OR corruption -> reset */ }
1011
+ data.traces.push(traceEntry);
1012
+ // Rotate per Plan 91-02 contract: when length exceeds 50, drop the
1013
+ // oldest 10 entries before write. Result: keep last 40 + the just-
1014
+ // pushed current = 41 entries on disk after a rotation event.
1015
+ if (data.traces.length > TRACE_ROTATE_AT) {
1016
+ data.traces = data.traces.slice(10);
1017
+ }
1018
+ // Atomic tmp + rename. Defensive open with 'wx' to fail-fast on
1019
+ // stale tmp.
1020
+ const rnd = Math.random().toString(36).slice(2, 10);
1021
+ const tmpPath = filePath + '.tmp.' + process.pid + '.' + rnd + '.trace';
1022
+ let fd;
1023
+ try {
1024
+ fd = fs.openSync(tmpPath, 'wx');
1025
+ } catch (e) {
1026
+ if (e && e.code === 'EEXIST') {
1027
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
1028
+ fd = fs.openSync(tmpPath, 'wx');
1029
+ } else {
1030
+ return;
1031
+ }
1032
+ }
1033
+ try {
1034
+ fs.writeSync(fd, JSON.stringify(data, null, 2));
1035
+ try { fs.fsyncSync(fd); } catch (_) {
1036
+ // ENOTSUP on tmpfs / overlayfs / Windows ImDisk. Best-effort
1037
+ // durability is acceptable for decision-trace.
1038
+ }
1039
+ } finally {
1040
+ try { fs.closeSync(fd); } catch (_) {}
1041
+ }
1042
+ try {
1043
+ fs.renameSync(tmpPath, filePath);
1044
+ } catch (_) {
1045
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
1046
+ }
1047
+ } catch (_) { /* fire-and-forget */ }
1048
+ }
1049
+
1050
+ function formatEngineDecisionBlock(decision, routing, offerLine) {
1051
+ if (!decision || typeof decision !== 'object') return '';
1052
+ const trace = decision.decision_trace || {};
1053
+ const fireSkill = decision.fire_skill === null || decision.fire_skill === undefined
1054
+ ? 'null' : String(decision.fire_skill);
1055
+ const suppressSkills = Array.isArray(decision.suppress_skills)
1056
+ ? '[' + decision.suppress_skills.join(', ') + ']'
1057
+ : '[]';
1058
+ let offer = 'null';
1059
+ if (decision.offer_next_step && typeof decision.offer_next_step === 'object') {
1060
+ const cmd = decision.offer_next_step.command || '';
1061
+ const reason = decision.offer_next_step.reason || '';
1062
+ offer = '{command: ' + cmd + ', reason: ' + reason + '}';
1063
+ }
1064
+ let persona = 'null';
1065
+ if (decision.persona_updates && typeof decision.persona_updates === 'object') {
1066
+ const pu = decision.persona_updates;
1067
+ persona = '{archetype: ' + (pu.archetype || 'null')
1068
+ + ', problem_type: ' + (pu.problem_type || 'null')
1069
+ + ', venture_stage: ' + (pu.venture_stage || 'null') + '}';
1070
+ }
1071
+ const tierMode = trace.brain_md_tier_mode || 'tier_0';
1072
+ const recommended = trace.brain_md_recommended_marker_rendered === true
1073
+ ? 'true' : 'false';
1074
+ const why = trace.chosen_rationale || '';
1075
+ const lines = [
1076
+ '## NAVIGATION DECISION (engine v1)',
1077
+ '',
1078
+ 'fire_skill: ' + fireSkill,
1079
+ 'suppress_skills: ' + suppressSkills,
1080
+ 'offer_next_step: ' + offer,
1081
+ 'persona_updates: ' + persona,
1082
+ 'tier_mode: ' + tierMode,
1083
+ 'brain_md_recommended_marker_rendered: ' + recommended,
1084
+ ];
1085
+ // Phase 91-03: append router output (routing_source + activated_skills)
1086
+ // when the caller has computed it. Two new lines summarize which
1087
+ // activation set won (engine vs legacy vs mixed) and which skills are
1088
+ // active for this turn after composition.
1089
+ if (routing && typeof routing === 'object') {
1090
+ const routingSource = typeof routing.source === 'string' ? routing.source : 'legacy';
1091
+ const activated = Array.isArray(routing.activated_skills)
1092
+ ? '[' + routing.activated_skills.join(', ') + ']'
1093
+ : '[]';
1094
+ lines.push('activated_skills: ' + activated);
1095
+ lines.push('routing_source: ' + routingSource);
1096
+ }
1097
+ lines.push('');
1098
+ lines.push('Why: ' + why);
1099
+ // Phase 91-04: append the offer-presenter output (one-line grounded
1100
+ // suggestion, optional RECOMMENDED marker) after the Why line so it
1101
+ // is the LAST thing Larry sees in additionalContext. Skipped when
1102
+ // null (offer suppressed by grounding rule, per-turn cap, or
1103
+ // consecutive-ignore window).
1104
+ if (typeof offerLine === 'string' && offerLine.length > 0) {
1105
+ lines.push('');
1106
+ lines.push(offerLine);
1107
+ }
1108
+ return lines.join('\n');
1109
+ }
1110
+
1111
+ // Phase 251-01 (CACHE-02a, suppress-when-unchanged): the one-line marker
1112
+ // emitted in place of a byte-identical repeat of the prior turn's rendered
1113
+ // NAVIGATION DECISION block. 251-CACHE-MEASUREMENT.md section 2: session
1114
+ // a396e801 measured 7/7 identical blocks in a row -- 6 of 7 emissions carried
1115
+ // zero new information. Byte-frozen (greppable, like the AskUserQuestion
1116
+ // marker it sits alongside).
1117
+ const NAV_UNCHANGED_MARKER = '[NAV DECISION unchanged - prior block stands]';
1118
+
1119
+ // Phase 251-02 (CACHE-03): the executable block-size budget. 251-CACHE-
1120
+ // MEASUREMENT.md measured a pre-hygiene average NAVIGATION DECISION block of
1121
+ // ~1,275 B; 251-01's hygiene pass (suppress-when-unchanged, skeleton-to-
1122
+ // SessionStart, payload dedup) cut the fixture block to 816 B. 1100 = 816 +
1123
+ // ~284 B headroom, rounded to a clean number, staying below the 1200 B
1124
+ // ceiling (the ceiling itself stays below the old 1,275 B average, so any
1125
+ // future raise past 1200 is a visible, deliberate diff -- T-251-07).
1126
+ //
1127
+ // THE CACHE-03 RIDER RULE: the future Brain reach rides THIS existing rail,
1128
+ // inside THIS budget. Additions to the per-turn block must fit inside
1129
+ // NAV_BLOCK_BUDGET_BYTES or something else on the block must shrink to make
1130
+ // room. The injection mechanism itself never changes to accommodate the
1131
+ // reach -- no cache_control tricks, no prefix moves (measurement section 4
1132
+ // item 4). The headroom (currently ~284 B) IS the Brain-reach allowance.
1133
+ const NAV_BLOCK_BUDGET_BYTES = 1100;
1134
+
1135
+ // Phase 251-01 (CACHE-02a): the per-session, per-room nav-block hash sidecar.
1136
+ // Follows the zeroScoreGateMarkerPath precedent (~L2703): a DEDICATED,
1137
+ // never-rotated marker file (persistDecisionTrace's decision-traces/<session>.json
1138
+ // IS rotated at TRACE_ROTATE_AT, the wrong lifetime for a suppression signal
1139
+ // that must survive the whole session).
1140
+ function navBlockHashPath(roomDir, sessionId) {
1141
+ return path.join(roomDir, '.mindrian', 'decision-traces', sessionId + '.nav-block-hash.json');
1142
+ }
1143
+
1144
+ // T-251-01 (fail OPEN): a missing, corrupt, or unreadable sidecar returns
1145
+ // null, never throws -- suppression is an optimization, never a gate. A null
1146
+ // return always yields full emission at the call site.
1147
+ function readNavBlockHash(roomDir, sessionId) {
1148
+ try {
1149
+ if (!roomDir || !sessionId) return null;
1150
+ const raw = fs.readFileSync(navBlockHashPath(roomDir, sessionId), 'utf8');
1151
+ const parsed = JSON.parse(raw);
1152
+ if (parsed && typeof parsed === 'object' && typeof parsed.sha256 === 'string' && parsed.sha256.length > 0) {
1153
+ return parsed.sha256;
1154
+ }
1155
+ return null;
1156
+ } catch (_e) {
1157
+ return null;
1158
+ }
1159
+ }
1160
+
1161
+ // Atomic tmp + rename write, the persistDecisionTrace idiom (~L1018-1046).
1162
+ // Payload carries ONLY {version, sha256, at, bytes} -- never block text or
1163
+ // user content (Canon Part 8, T-251-03). Fire-and-forget: a write fault
1164
+ // never blocks or crashes the turn (T-251-01), it only means suppression may
1165
+ // not kick in on the NEXT turn -- strictly no worse than pre-fix behavior.
1166
+ function writeNavBlockHash(roomDir, sessionId, sha256, bytes) {
1167
+ try {
1168
+ if (!roomDir || !sessionId || typeof sha256 !== 'string' || sha256.length === 0) return;
1169
+ const filePath = navBlockHashPath(roomDir, sessionId);
1170
+ try { fs.mkdirSync(path.dirname(filePath), { recursive: true }); } catch (_) {}
1171
+ const payload = {
1172
+ version: 1,
1173
+ sha256: sha256,
1174
+ at: new Date().toISOString(),
1175
+ bytes: Number.isFinite(bytes) ? bytes : 0,
1176
+ };
1177
+ const rnd = Math.random().toString(36).slice(2, 10);
1178
+ const tmpPath = filePath + '.tmp.' + process.pid + '.' + rnd;
1179
+ fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2));
1180
+ try {
1181
+ fs.renameSync(tmpPath, filePath);
1182
+ } catch (_e) {
1183
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
1184
+ }
1185
+ } catch (_e) { /* fire-and-forget */ }
1186
+ }
1187
+
1188
+ /**
1189
+ * Phase 150-06 (D-08) -- THE RENDER UNLOCK.
1190
+ *
1191
+ * Wrap formatEngineDecisionBlock and, ON THE ENGINE ARM (routing_source ===
1192
+ * 'engine'), surface the grounded one-move onto the LIVE response block:
1193
+ * 1. build roomState.reachScores from the projected cortex via the new
1194
+ * cortex-reach-adapter.buildReachScoresFromCortex(cortexNodes) (the legD
1195
+ * field, threaded out on the LOCAL routing lane);
1196
+ * 2. rank with the FROZEN buildReachList(roomState) (MAX_K=3 / 0.70/0.15 /
1197
+ * DIAL_REACH_K=6 untouched -- 150 FEEDS the selector, never re-architects it);
1198
+ * 3. present through dial-presenter.renderDial -- buildReachList -> dial-presenter
1199
+ * gains its FIRST production caller (the C2 unlock; the navigator SEES it).
1200
+ *
1201
+ * Best-effort discipline (T-150-06-02): the entire dial render is wrapped so a
1202
+ * render fault NEVER blocks the user turn -- on any error the base engine block
1203
+ * is returned unchanged. The honest-negative (cortex absent) still renders the
1204
+ * engine's one-move from the orchestrator's flat-file fallback (D-02), proving
1205
+ * the wire, not a fixture. Canon Part 8: LOCAL-only, zero Brain calls, presence +
1206
+ * enums only (the adapter never reads cortex prose).
1207
+ *
1208
+ * ctx.cortexNodes: the legD projected cortex (array; defaults to []).
1209
+ * ctx.onRenderNote: optional fault-note callback (150.5-02 DIAL-ATOM-01).
1210
+ */
1211
+ // Phase 209-01 (E3): pick a human display-name off a LOCAL node row for the
1212
+ // {topic} dial slot. relevantNodes (getNeighborhood) rows carry id/type/... and
1213
+ // cortexNodes (legD) rows carry id/type/properties; neither guarantees a name
1214
+ // field, so we probe the common name-ish fields in order and fall back to the
1215
+ // raw node id. Returns a trimmed non-empty string or null (caller omits the slot
1216
+ // when null so the row degrades to the generic JTBD line rather than 'undefined').
1217
+ // Canon Part 8: reads a LOCAL node scalar for a render-time label only; the
1218
+ // egress-audited {framework} slot is NEVER sourced here (composer owns it).
1219
+ function pickNodeDisplayName(node) {
1220
+ if (!node || typeof node !== 'object') return null;
1221
+ const candidates = [node.name, node.label, node.title];
1222
+ const props = (node.properties && typeof node.properties === 'object') ? node.properties : null;
1223
+ if (props) { candidates.push(props.name, props.title, props.label); }
1224
+ candidates.push(node.id);
1225
+ for (let i = 0; i < candidates.length; i++) {
1226
+ const c = candidates[i];
1227
+ if (typeof c === 'string' && c.trim().length > 0) return c.trim();
1228
+ }
1229
+ return null;
1230
+ }
1231
+
1232
+ // reach-sensor-relevance-gap B1 (root cause B): resolve a genuinely DIFFERENT
1233
+ // room for the cross_room reach's {room_name} slot from the live room context.
1234
+ // A cross-room reference in the graph is a node that names ANOTHER room, either
1235
+ // via a `room:<slug>` / `cross_room:<slug>` id or a `room` / `target_room` /
1236
+ // `cross_room` property (getNeighborhood surfaces such nodes on relevantNodes;
1237
+ // legD surfaces them on cortexNodes). Returns the FIRST target slug that differs
1238
+ // from the current room, else null. Pure + non-throwing.
1239
+ function resolveCrossRoomTargetSlug(roomContext, currentRoomName) {
1240
+ try {
1241
+ const rc = (roomContext && typeof roomContext === 'object') ? roomContext : null;
1242
+ if (!rc) return null;
1243
+ const current = (typeof currentRoomName === 'string') ? currentRoomName.toLowerCase() : null;
1244
+ const pools = [];
1245
+ if (Array.isArray(rc.relevantNodes)) pools.push(rc.relevantNodes);
1246
+ if (Array.isArray(rc.cortexNodes)) pools.push(rc.cortexNodes);
1247
+ for (const pool of pools) {
1248
+ for (const node of pool) {
1249
+ if (!node || typeof node !== 'object') continue;
1250
+ let cand = null;
1251
+ // id form: 'room:<slug>' or 'cross_room:<slug>'.
1252
+ if (typeof node.id === 'string') {
1253
+ const m = node.id.match(/^(?:room|cross_room):([a-z0-9][a-z0-9_-]{0,63})$/i);
1254
+ if (m) cand = m[1];
1255
+ }
1256
+ // property form: an explicit room reference.
1257
+ if (!cand && node.properties && typeof node.properties === 'object') {
1258
+ const p = node.properties;
1259
+ const ref = (typeof p.target_room === 'string' && p.target_room)
1260
+ || (typeof p.cross_room === 'string' && p.cross_room)
1261
+ || (typeof p.room === 'string' && p.room)
1262
+ || null;
1263
+ if (ref) cand = ref;
1264
+ }
1265
+ if (typeof cand === 'string' && cand.length > 0
1266
+ && (!current || cand.toLowerCase() !== current)) {
1267
+ return cand;
1268
+ }
1269
+ }
1270
+ }
1271
+ return null;
1272
+ } catch (_e) {
1273
+ return null;
1274
+ }
1275
+ }
1276
+
1277
+ // Phase 209-01 (E3): derive the flat composeLabel slot map from the LIVE room
1278
+ // context so the dial rows resolve {topic}/{room_name} instead of always
1279
+ // degrading to the ELEVATION_DEFAULTS fallback (the RC-3 defect: renderDial was
1280
+ // called with empty opts). Pure + non-throwing: any malformed input yields {}.
1281
+ // header_room <- path.basename(roomDir): the CURRENT room, for the decision-gate
1282
+ // header context label ONLY (dial-presenter reads header_room).
1283
+ // room_name <- reach-sensor-relevance-gap B1: the cross_room reach's slot is a
1284
+ // genuinely DIFFERENT room resolved from the graph, NOT the current
1285
+ // room. OMITTED when no different room resolves, so cross_room can
1286
+ // never "borrow from self" (the observed bug); the relevance gate
1287
+ // then suppresses the target-less cross_room row.
1288
+ // topic <- the top relevantNodes display-name, else the first cortexNodes
1289
+ // display-name; the key is OMITTED entirely when neither resolves.
1290
+ // The {framework} slot is deliberately NOT set here (Part 8: the egress-audited
1291
+ // slot stays owned by dial-label-composer's auditFrameworkSlot chokepoint).
1292
+ function buildDialSlotContext(roomContext, roomDir) {
1293
+ try {
1294
+ const slots = {};
1295
+ let currentRoomName = null;
1296
+ if (typeof roomDir === 'string' && roomDir.length > 0) {
1297
+ const base = path.basename(roomDir);
1298
+ if (base && base.length > 0) {
1299
+ currentRoomName = base;
1300
+ // header_room: the CURRENT room, for the decision-gate header ONLY. This
1301
+ // REPLACES the old room_name=basename that cross_room borrowed from self.
1302
+ slots.header_room = base;
1303
+ }
1304
+ }
1305
+ const rc = (roomContext && typeof roomContext === 'object') ? roomContext : null;
1306
+ if (rc) {
1307
+ const topNode = (Array.isArray(rc.relevantNodes) && rc.relevantNodes.length > 0)
1308
+ ? rc.relevantNodes[0] : null;
1309
+ const cortexNode = (Array.isArray(rc.cortexNodes) && rc.cortexNodes.length > 0)
1310
+ ? rc.cortexNodes[0] : null;
1311
+ const topic = pickNodeDisplayName(topNode) || pickNodeDisplayName(cortexNode);
1312
+ if (typeof topic === 'string' && topic.length > 0) slots.topic = topic;
1313
+ }
1314
+ // B1: cross_room's {room_name} is a genuinely different, relevant room -- never
1315
+ // the current room. OMITTED when none resolves (the relevance gate suppresses
1316
+ // the target-less cross_room; composeLabel would otherwise degrade it to the
1317
+ // lateral elevation default, never borrow-from-self).
1318
+ const crossTarget = resolveCrossRoomTargetSlug(rc, currentRoomName);
1319
+ if (typeof crossTarget === 'string' && crossTarget.length > 0) {
1320
+ slots.room_name = crossTarget;
1321
+ }
1322
+ return slots;
1323
+ } catch (_e) {
1324
+ return {};
1325
+ }
1326
+ }
1327
+
1328
+ // ---------------------------------------------------------------------------
1329
+ // Phase 245-08 (Requirement 1, D-01 corrected plug point per 245-RESEARCH.md
1330
+ // F-1; D-02, D-03, D-04; Pitfall 7 / Open Question 2).
1331
+ //
1332
+ // composeDialReachScores(input) -> { reachScores, suppressedReachIds, tierMode }
1333
+ // | null on a catastrophic fault
1334
+ //
1335
+ // THE ONE score composition both dial consumers use. Before this helper existed
1336
+ // there were two: the live render at renderEngineDecisionWithDial (cortex scores
1337
+ // + the Phase 158-03 reject discount + the structural relevance gate) and the
1338
+ // reach_presented telemetry recompute (cortex scores ONLY). They disagreed, so
1339
+ // the telemetry logged an offered set the navigator may never have seen. That
1340
+ // divergence is PRE-EXISTING (research confirmed it live at HEAD); Requirement 1
1341
+ // would have widened it, so it is closed here rather than documented as debt.
1342
+ //
1343
+ // WHY THIS SEAM AND NOT THE HEDGE RANKER. 245-RESEARCH.md F-1 enumerated every
1344
+ // call site of rankFiredCandidates and buildReachList and found they are SIBLING
1345
+ // consumers of the same buildReachScoresFromCortex output: nothing downstream of
1346
+ // the ranker reaches the dial. A fusion folded into rankFiredCandidates' local
1347
+ // `combined` passes every unit test and moves the dial by exactly nothing
1348
+ // (Pitfall 1, "the single most likely way this phase ships a false success").
1349
+ // The math therefore lives in the ONE shared selection layer
1350
+ // (reach-hedge-ranker.buildSignalNudges, 245-07) and the MERGE lives here, at
1351
+ // the render callsite, on the shipped Phase 158-03 `Object.assign` precedent.
1352
+ // buildReachList itself stays byte-unchanged (D-03): it is contractually pure
1353
+ // and receives only { tierMode, reachScores, suppressedReachIds }, so merging
1354
+ // upstream changes its INPUT without touching its code.
1355
+ //
1356
+ // ORDER IS LOAD-BEARING:
1357
+ // 1. cortex priors (buildReachScoresFromCortex)
1358
+ // 2. the 158-03 reject discount fold (discountedScores over the cortex map)
1359
+ // 3. the Requirement 1 fusion (fused ABSOLUTE scores over the discounted map)
1360
+ // 4. the suppression set (reject hard-suppression + the relevance gate)
1361
+ // The discount is computed upstream against the RAW cortex scores and must stay
1362
+ // step 2, before the fusion: discounting a fused score would compound a reject
1363
+ // penalty onto a signal the navigator never rejected.
1364
+ //
1365
+ // PURITY: no db, no fs, no network, no Brain call, no mutation of any argument
1366
+ // (reachScores is a fresh object the adapter minted; reachPenalties and decision
1367
+ // are read-only). Safe to call from the db-free render seam. Never throws: any
1368
+ // fault returns null and the caller degrades to exactly today's behavior.
1369
+ // ---------------------------------------------------------------------------
1370
+ function composeDialReachScores(input) {
1371
+ try {
1372
+ const inp = (input && typeof input === 'object') ? input : {};
1373
+ const cortexNodes = Array.isArray(inp.cortexNodes) ? inp.cortexNodes : [];
1374
+ const reachPenalties = (inp.reachPenalties && typeof inp.reachPenalties === 'object')
1375
+ ? inp.reachPenalties : null;
1376
+ const decision = (inp.decision && typeof inp.decision === 'object') ? inp.decision : null;
1377
+ const slotContext = (inp.slotContext && typeof inp.slotContext === 'object')
1378
+ ? inp.slotContext : {};
1379
+
1380
+ const adapter = require(
1381
+ path.join(__dirname, '..', 'lib', 'hmi', 'cortex-reach-adapter.cjs')
1382
+ );
1383
+
1384
+ // ---- step 1: the cortex priors (was renderEngineDecisionWithDial's :1274)
1385
+ const reachScores = adapter.buildReachScoresFromCortex(cortexNodes);
1386
+
1387
+ // ---- step 2: the Phase 158-03 (RJP-01..05 / SC-07) reject-discount fold.
1388
+ // reach_penalties is folded on the live engine arm (db open) and threaded
1389
+ // out on ctx; here (db already closed) we MERGE the discounted scores over
1390
+ // the cortex-derived reachScores. When reach_penalties is absent (no penalty
1391
+ // computed) or empty (zero rejections), reachScores is unchanged (RJP-02).
1392
+ if (reachPenalties && reachPenalties.discountedScores
1393
+ && typeof reachPenalties.discountedScores === 'object') {
1394
+ Object.assign(reachScores, reachPenalties.discountedScores);
1395
+ }
1396
+
1397
+ // The tier mode mirrors the engine trace so the frozen 0.70/0.15 gate is
1398
+ // applied exactly as buildReachList already does (mode_a / mode_b / tier_0).
1399
+ const trace = (decision && decision.decision_trace) || {};
1400
+ const tierMode = (typeof trace.brain_md_tier_mode === 'string')
1401
+ ? trace.brain_md_tier_mode : 'tier_0';
1402
+
1403
+ // ---- step 3: the Requirement 1 fusion (NEW in 245-08).
1404
+ // Both inputs are ALREADY on the decision trace at this callsite (D-02): no
1405
+ // new plumbing, no db handle, no Brain call, no second sensor dispatch.
1406
+ //
1407
+ // facts[] the fired sensor reaches, in the order rankFiredCandidates
1408
+ // produced (so facts[0] is the scored winner with 245-07's
1409
+ // SENS_PRIORITY tie-break already applied). Each entry carries
1410
+ // reach_id / posture / evidence / first_seen / last_updated and
1411
+ // is NOT a full reach struct, so buildSignalNudges may only read
1412
+ // reach_id off it, which its 245-07 contract already guarantees.
1413
+ //
1414
+ // brainVerb trace.brain_pattern_verb (245-05), the ONLY correct source.
1415
+ // 245-RESEARCH.md Pitfall 2 is explicit: reading the verb from
1416
+ // trace.fire_skill or from context_assembly.decision_grounding
1417
+ // returns the SENSOR's verb on any sensor-fires turn, which
1418
+ // measures the sensor twice and silently re-implements the D-24
1419
+ // starvation this phase exists to fix.
1420
+ //
1421
+ // buildSignalNudges returns FUSED ABSOLUTE scores (never deltas), bounded
1422
+ // strictly below FUSION_CEILING 0.69 by the shape of its interpolation, so a
1423
+ // plain Object.assign is the correct merge and NO arithmetic belongs at this
1424
+ // callsite. Keeping every scoring decision inside the shared selection layer
1425
+ // is what preserves Canon Part 7 and the connector-spine "no second
1426
+ // selection brain" rule. Absent signal returns {} -> a byte-identical render.
1427
+ try {
1428
+ const ranker = require(
1429
+ path.join(__dirname, '..', 'lib', 'workflow', 'reach-hedge-ranker.cjs')
1430
+ );
1431
+ const contextAssembly = (trace.context_assembly && typeof trace.context_assembly === 'object')
1432
+ ? trace.context_assembly : {};
1433
+ const facts = Array.isArray(contextAssembly.facts) ? contextAssembly.facts : [];
1434
+ const brainVerb = (typeof trace.brain_pattern_verb === 'string')
1435
+ ? trace.brain_pattern_verb : null;
1436
+ const nudges = ranker.buildSignalNudges({
1437
+ baseScores: reachScores,
1438
+ sensorReaches: facts,
1439
+ brainVerb: brainVerb,
1440
+ tierMode: tierMode,
1441
+ });
1442
+ if (nudges && typeof nudges === 'object') {
1443
+ Object.assign(reachScores, nudges);
1444
+ }
1445
+ } catch (_fusionErr) {
1446
+ // Degrade, never break (T-245-36): an UNFUSED render is today's correct
1447
+ // behavior, so reachScores is left exactly as steps 1 and 2 produced it.
1448
+ }
1449
+
1450
+ // ---- step 4: the suppression set (was :1307-1339, moved verbatim).
1451
+ const baseSuppressed = (reachPenalties && Array.isArray(reachPenalties.suppressedReachIds))
1452
+ ? reachPenalties.suppressedReachIds : [];
1453
+
1454
+ // reach-sensor-relevance-gap B1 (root cause B): the STRUCTURAL relevance
1455
+ // gate. Compare the live turn tokens to each cross-room-class reach's
1456
+ // resolved slot content and DROP the off-topic ones through the SAME
1457
+ // suppressedReachIds path buildReachList already honors -- the seam stays
1458
+ // pure (buildReachList does the drop; this seam never mutates the reach bank
1459
+ // or the frozen gate). No-op when there is no live turn (unit tests without
1460
+ // a turn render byte-identically).
1461
+ let gatedSuppressed = baseSuppressed;
1462
+ try {
1463
+ const relevanceGate = require(
1464
+ path.join(__dirname, '..', 'lib', 'hmi', 'reach-relevance-gate.cjs')
1465
+ );
1466
+ const liveTurnText = (typeof inp.liveTurnText === 'string' && inp.liveTurnText.length > 0)
1467
+ ? inp.liveTurnText
1468
+ : STDIN_MESSAGE;
1469
+ const currentRoomName = (typeof inp.currentRoomName === 'string' && inp.currentRoomName.length > 0)
1470
+ ? inp.currentRoomName
1471
+ : null;
1472
+ const offTopic = relevanceGate.computeOffTopicReachIds({
1473
+ slotContext: slotContext,
1474
+ liveTurnText: liveTurnText,
1475
+ currentRoomName: currentRoomName,
1476
+ });
1477
+ if (Array.isArray(offTopic) && offTopic.length > 0) {
1478
+ const merged = baseSuppressed.slice();
1479
+ for (const id of offTopic) {
1480
+ if (merged.indexOf(id) === -1) merged.push(id);
1481
+ }
1482
+ gatedSuppressed = merged;
1483
+ }
1484
+ } catch (_gateErr) {
1485
+ // Fail-open: a gate fault degrades to the un-gated suppression set.
1486
+ gatedSuppressed = baseSuppressed;
1487
+ }
1488
+
1489
+ return {
1490
+ reachScores: reachScores,
1491
+ suppressedReachIds: gatedSuppressed,
1492
+ tierMode: tierMode,
1493
+ };
1494
+ } catch (_composeErr) {
1495
+ // A null return is the documented degrade handle: both call sites fall back
1496
+ // to exactly the behavior they had before this helper existed (the render
1497
+ // returns the base block; the telemetry recompute skips its emit).
1498
+ return null;
1499
+ }
1500
+ }
1501
+
1502
+ // SEED-020 pickShape exemption (150.5-02, documented decision): the dial render
1503
+ // stays on the shipped 143.1 renderShapeF1 resolve/format path and does NOT fold
1504
+ // through pickShape (its install-tier-0 refuse would kill the D-01 sensor-fired
1505
+ // cold card); the trailer construction stays dispatcher-only.
1506
+ function renderEngineDecisionWithDial(decision, routing, offerLine, ctx) {
1507
+ const base = formatEngineDecisionBlock(decision, routing, offerLine);
1508
+ // Only the engine arm surfaces the dial. Legacy / mixed / silent keep the
1509
+ // base block unchanged (no regression; the dial is the engine-decision face).
1510
+ const source = (routing && typeof routing.source === 'string') ? routing.source : null;
1511
+
1512
+ // Phase 251-01 (CACHE-02a): finalBlock is composed EXACTLY as before (base
1513
+ // on the non-engine / fault arms; base + rendered.text + marker + payload
1514
+ // on the engine arm), then BOTH arms funnel through the ONE suppression
1515
+ // gate below. engineArmFired tracks whether this turn is the engine arm's
1516
+ // genuine full-card success path, so the sidechannel record (hoisted below
1517
+ // the gate) fires ONLY when the gate was actually re-presented this turn.
1518
+ let finalBlock = base;
1519
+ let engineArmFired = false;
1520
+ let engineArmSubject = '';
1521
+
1522
+ if (source === 'engine') {
1523
+ try {
1524
+ const orchestrator = require(
1525
+ path.join(__dirname, '..', 'lib', 'hmi', 'dial-reach-orchestrator.cjs')
1526
+ );
1527
+ const presenter = require(
1528
+ path.join(__dirname, '..', 'lib', 'hmi', 'dial-presenter.cjs')
1529
+ );
1530
+
1531
+ const cortexNodes = (ctx && Array.isArray(ctx.cortexNodes)) ? ctx.cortexNodes : [];
1532
+ const reachPenalties = (ctx && ctx.reach_penalties && typeof ctx.reach_penalties === 'object')
1533
+ ? ctx.reach_penalties : null;
1534
+
1535
+ // Phase 209-01 (E3): resolve the LIVE room-context slot map so the dial rows
1536
+ // resolve {topic}/{room_name} instead of degrading to ELEVATION_DEFAULTS.
1537
+ // reach-sensor-relevance-gap B1: this is resolved BEFORE buildReachList so the
1538
+ // structural relevance gate can read the cross_room slot content and drop the
1539
+ // reach BEFORE it ranks. buildDialSlotContext never throws and returns {} on
1540
+ // absent context.
1541
+ const slotContext = buildDialSlotContext(ctx && ctx.roomContext, ctx && ctx.roomDir);
1542
+
1543
+ // Phase 245-08 (Requirement 1): CALL SITE A, the live render. The cortex
1544
+ // priors, the 158-03 reject discount, the Requirement 1 sensor/Brain fusion
1545
+ // and the relevance gate are all composed by the ONE shared helper that the
1546
+ // reach_presented telemetry recompute also uses, so the logged offered set
1547
+ // and the rendered dial can no longer disagree. buildReachList itself is
1548
+ // byte-unchanged (D-03) and still receives only scalars plus an array.
1549
+ const composed = composeDialReachScores({
1550
+ cortexNodes: cortexNodes,
1551
+ reachPenalties: reachPenalties,
1552
+ decision: decision,
1553
+ slotContext: slotContext,
1554
+ liveTurnText: (ctx && typeof ctx.liveTurnText === 'string' && ctx.liveTurnText.length > 0)
1555
+ ? ctx.liveTurnText : null,
1556
+ currentRoomName: (ctx && typeof ctx.roomDir === 'string' && ctx.roomDir.length > 0)
1557
+ ? path.basename(ctx.roomDir) : null,
1558
+ });
1559
+ // A null compose is the catastrophic-fault handle: degrade to the base
1560
+ // block exactly as a throw from the old inline composition already did
1561
+ // (finalBlock stays = base, engineArmFired stays false).
1562
+ if (composed !== null) {
1563
+ const reachList = orchestrator.buildReachList({
1564
+ tierMode: composed.tierMode,
1565
+ reachScores: composed.reachScores,
1566
+ suppressedReachIds: composed.suppressedReachIds,
1567
+ });
1568
+
1569
+ const rendered = presenter.renderDial(reachList, {
1570
+ slotContext: slotContext,
1571
+ });
1572
+ if (rendered && typeof rendered.text === 'string' && rendered.text.length > 0) {
1573
+ // 150.5-02 (DIAL-ATOM-01): thread the AskUserQuestion contract through
1574
+ // the SEED-020 single construction door. The dispatcher mints the
1575
+ // trailer onto the envelope's scalar marker + binding + rendered.contract;
1576
+ // this caller only READS those fields positionally in the concatenation
1577
+ // below (never composes the trailer string, never assigns the marker).
1578
+ // The trailer rides EVERY engine arm including tier_0 -- that arm IS the
1579
+ // D-01 (LOCKED 2026-06-09) sensor-fired cold card: buildReachList's tier_0
1580
+ // path already renders the S4 framing + the no-signal confidence column,
1581
+ // and the live card contract now rides with them. No tier_mode gating.
1582
+ //
1583
+ // Phase 209-01 (E2/E3): the caller now reads marker + binding + contract
1584
+ // positionally. Each appended line is guarded on its own presence so a
1585
+ // missing field degrades to the prior marker-only behavior rather than
1586
+ // emitting 'undefined'. The contract line is built field-by-field from
1587
+ // rendered.contract and JSON.stringify'd -- never string-concat unescaped
1588
+ // label text into the bracket line (T-209-01 tampering mitigation).
1589
+ const dispatcher = require(
1590
+ path.join(__dirname, '..', 'lib', 'hmi', 'selector-dispatcher.cjs')
1591
+ );
1592
+ dispatcher.appendAskUserQuestionTrailer(rendered, 'F.1');
1593
+ const marker = rendered.askuserquestion_marker;
1594
+ if (typeof marker === 'string' && marker.length > 0) {
1595
+ let block = base + '\n\n' + rendered.text + '\n' + marker;
1596
+ // Phase 251-01 (CACHE-02b, skeleton-to-SessionStart): the
1597
+ // rendered.askuserquestion_binding (FIRE-IF-FORK imperative,
1598
+ // ~330 B/turn, byte-identical every turn) no longer rides this
1599
+ // per-turn concatenation -- it now lives ONCE in
1600
+ // scripts/session-start's NAVIGATION CARD-FIRE CONTRACT doctrine,
1601
+ // re-seeded on startup/clear/compact. appendAskUserQuestionTrailer
1602
+ // itself is untouched: it still mints askuserquestion_binding and
1603
+ // zones.footer for every OTHER consumer (the pickShape door,
1604
+ // renderRoomChooserCard) -- only THIS concatenation stopped
1605
+ // reading the field.
1606
+ const contract = rendered.contract;
1607
+ if (contract && typeof contract === 'object') {
1608
+ const verbs = Array.isArray(contract.verbs)
1609
+ ? contract.verbs.filter(function (v) { return typeof v === 'string'; })
1610
+ : [];
1611
+ // Phase 251-01 (CACHE-02c, payload dedup): 251-CACHE-MEASUREMENT.md
1612
+ // section 2 -- the verbs array duplicated the labels already
1613
+ // printed in the option rows above it (~300 B/block of pure
1614
+ // duplication). Drop the array from the INJECTED display line
1615
+ // ONLY; verb_count replaces it (matches the marker's verbs=N)
1616
+ // so the shape/count/recommended contract stays introspectable.
1617
+ // The labels live in the option rows above (rendered.text) plus
1618
+ // the SessionStart payload legend (Task 2's NAV_CARD_FIRE_DOCTRINE).
1619
+ // The prefix '[AskUserQuestion payload: ' stays byte-identical
1620
+ // (greppable contract prefix). The PERSISTED f1_closer_payload
1621
+ // (traceEntry.f1_closer_payload, ~L3505) is a SEPARATE object
1622
+ // built from f1Payload at the call site, never from `compact`
1623
+ // here -- it keeps its full verbs array untouched.
1624
+ const compact = {
1625
+ shape: (typeof contract.shape === 'string') ? contract.shape : 'F.1',
1626
+ mode: (typeof contract.mode === 'string') ? contract.mode : null,
1627
+ verb_count: verbs.length,
1628
+ recommended: (typeof contract.recommended === 'string') ? contract.recommended : null,
1629
+ };
1630
+ block += '\n[AskUserQuestion payload: ' + JSON.stringify(compact) + ']';
1631
+ }
1632
+ finalBlock = block;
1633
+ engineArmFired = true;
1634
+ engineArmSubject = (typeof rendered.text === 'string' ? rendered.text : '');
1635
+ }
1636
+ }
1637
+ }
1638
+ } catch (e) {
1639
+ // A dial render fault NEVER blocks the turn (T-150-06-02), and it no
1640
+ // longer vanishes (the Phase 140 D-03 silent-failure class is closed at
1641
+ // this seam, 150.5-02): classify the fault into a scalar note for the
1642
+ // persisted decision trace via ctx.onRenderNote. Scalars only -- never
1643
+ // e.message or a stack (a message can carry filesystem paths; the note
1644
+ // is a classifier, not a payload). The callback is itself guarded so a
1645
+ // faulting callback cannot throw out of the fault handler.
1646
+ if (ctx && typeof ctx.onRenderNote === 'function') {
1647
+ try {
1648
+ ctx.onRenderNote({
1649
+ event: 'dial_render_fault',
1650
+ error_name: (e && e.name) || 'Error',
1651
+ });
1652
+ } catch (_cbErr) { /* never throw out of the fault handler */ }
1653
+ }
1654
+ finalBlock = base;
1655
+ engineArmFired = false;
1656
+ }
1657
+ }
1658
+
1659
+ // Phase 251-01 (CACHE-02a): the ONE suppression gate BOTH arms funnel
1660
+ // through. An empty/falsy finalBlock (no decision, or the fault arm
1661
+ // degraded to '') bypasses the gate entirely -- there is nothing to hash
1662
+ // or suppress.
1663
+ if (!finalBlock) return finalBlock;
1664
+
1665
+ let hash = null;
1666
+ try {
1667
+ hash = crypto.createHash('sha256').update(finalBlock, 'utf8').digest('hex');
1668
+ if (ctx && typeof ctx.onBlockHash === 'function') {
1669
+ ctx.onBlockHash(hash, Buffer.byteLength(finalBlock, 'utf8'));
1670
+ }
1671
+ } catch (_e) {
1672
+ // T-251-01 (fail OPEN): a hashing fault leaves hash null, so the
1673
+ // suppression check below can never fire -- full emission proceeds.
1674
+ hash = null;
1675
+ }
1676
+
1677
+ if (hash && ctx && ctx.prevBlockHash && ctx.prevBlockHash === hash) {
1678
+ return NAV_UNCHANGED_MARKER;
1679
+ }
1680
+
1681
+ // Phase 209-06 (H3), HOISTED below the suppression gate (T-251-02,
1682
+ // SEED-021 / Stop-gate consistency): the PRIMARY side-channel producer for
1683
+ // the engine arm fires ONLY when the gate was genuinely re-presented this
1684
+ // turn (engineArmFired), never on a suppressed turn where Larry saw only
1685
+ // the one-line marker. Lazy-required inside its own try/catch so a writer
1686
+ // fault never affects the rendered block. sessionId is threaded via ctx
1687
+ // (the call site passes resolveSessionId(roomDir)); when absent the
1688
+ // record files under NO_SESSION_KEY (the module's documented
1689
+ // degenerate-floor bucket, unioned into every session-scoped read).
1690
+ if (engineArmFired) {
1691
+ try {
1692
+ const sidechannel = require(
1693
+ path.join(__dirname, '..', 'lib', 'core', 'card-fire-sidechannel.cjs')
1694
+ );
1695
+ sidechannel.recordReachedGate({
1696
+ sessionId: (ctx && typeof ctx.sessionId === 'string') ? ctx.sessionId : undefined,
1697
+ surface: 'scripts/intent-classifier.cjs',
1698
+ shape: 'F.1',
1699
+ subjectText: engineArmSubject,
1700
+ });
1701
+ } catch (_e) {
1702
+ /* never let a side-channel fault affect the rendered block */
1703
+ }
1704
+ }
1705
+
1706
+ return finalBlock;
1707
+ }
1708
+
1709
+ function navTestSleepMs() {
1710
+ const v = process.env.MOS_NAV_TEST_SLEEP;
1711
+ if (!v) return 0;
1712
+ const n = parseInt(v, 10);
1713
+ if (!isNaN(n) && n > 0) return n;
1714
+ return 0;
1715
+ }
1716
+
1717
+ function navTestThrowing() {
1718
+ return process.env.MOS_NAV_TEST_THROW === '1';
1719
+ }
1720
+
1721
+ // Phase 91-03: integration test stub for engine output. When set, the
1722
+ // run-navigation-engine path overrides the decision's fire_skill (and
1723
+ // optionally suppress_skills) so end-to-end tests can exercise the
1724
+ // router without mocking the engine module. Production behavior is
1725
+ // unchanged when env vars are unset (string equality, never coerced
1726
+ // from absent values).
1727
+ // MOS_NAV_TEST_FIRE_SKILL=<verb> -> set decision.fire_skill
1728
+ // MOS_NAV_TEST_FIRE_SKILL=__NULL__ -> force decision.fire_skill = null
1729
+ // MOS_NAV_TEST_SUPPRESS_SKILLS=a,b,c -> set decision.suppress_skills (csv)
1730
+ function navTestFireSkill() {
1731
+ const v = process.env.MOS_NAV_TEST_FIRE_SKILL;
1732
+ if (typeof v !== 'string' || v.length === 0) return undefined;
1733
+ if (v === '__NULL__') return null;
1734
+ return v;
1735
+ }
1736
+
1737
+ function navTestSuppressSkills() {
1738
+ const v = process.env.MOS_NAV_TEST_SUPPRESS_SKILLS;
1739
+ if (typeof v !== 'string' || v.length === 0) return undefined;
1740
+ return v.split(',').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
1741
+ }
1742
+
1743
+ // Phase 91-04: integration test stubs for offer_next_step. Injected
1744
+ // AFTER decide() returns so end-to-end tests can exercise the offer
1745
+ // presenter without mocking the engine module. Mirrors Plan 91-03's
1746
+ // MOS_NAV_TEST_FIRE_SKILL pattern. Production behavior is unchanged
1747
+ // when env vars are unset.
1748
+ // MOS_NAV_TEST_OFFER_COMMAND=<cmd> -> set decision.offer_next_step.command
1749
+ // MOS_NAV_TEST_OFFER_COMMAND=__NULL__ -> force decision.offer_next_step = null
1750
+ // MOS_NAV_TEST_OFFER_REASON=<reason> -> set decision.offer_next_step.reason
1751
+ function navTestOfferCommand() {
1752
+ const v = process.env.MOS_NAV_TEST_OFFER_COMMAND;
1753
+ if (typeof v !== 'string' || v.length === 0) return undefined;
1754
+ if (v === '__NULL__') return null;
1755
+ return v;
1756
+ }
1757
+
1758
+ function navTestOfferReason() {
1759
+ const v = process.env.MOS_NAV_TEST_OFFER_REASON;
1760
+ if (typeof v !== 'string' || v.length === 0) return undefined;
1761
+ return v;
1762
+ }
1763
+
1764
+ // Phase 91-03: compute the pre-91 (legacy) skill activation set based
1765
+ // on observable file-state and env-var toggles. The router composes
1766
+ // this with the engine decision per Canon Part 7 (Reuse Before Build):
1767
+ // when engine is silent, this is the activation that fires; when
1768
+ // engine is opinionated, this is the input the suppress list operates
1769
+ // on. Skills observed:
1770
+ // - larry-personality always-on (no activation directive)
1771
+ // - context-engine always-on (no activation directive)
1772
+ // - room-passive when active room resolves
1773
+ // - room-proactive when active room resolves
1774
+ // Env-var toggles honored:
1775
+ // MOS_NO_SKILL_<name>=1 -> remove from set
1776
+ // MOS_FORCE_SKILL_<name>=1 -> add to set even if file-state misses
1777
+ function computeLegacyActivation(roomDir) {
1778
+ const set = [];
1779
+ const seen = new Set();
1780
+ function add(name) {
1781
+ if (typeof name !== 'string' || name.length === 0) return;
1782
+ if (seen.has(name)) return;
1783
+ if (process.env['MOS_NO_SKILL_' + name] === '1') return;
1784
+ seen.add(name);
1785
+ set.push(name);
1786
+ }
1787
+ add('larry-personality');
1788
+ add('context-engine');
1789
+ if (roomDir) {
1790
+ add('room-passive');
1791
+ add('room-proactive');
1792
+ }
1793
+ // FORCE_SKILL allows enabling a skill that file-state would not
1794
+ // activate. Iterate env keys looking for the prefix.
1795
+ for (const key of Object.keys(process.env)) {
1796
+ if (key.indexOf('MOS_FORCE_SKILL_') === 0 && process.env[key] === '1') {
1797
+ add(key.slice('MOS_FORCE_SKILL_'.length));
1798
+ }
1799
+ }
1800
+ return set;
1801
+ }
1802
+
1803
+ function navTestCounterPath() {
1804
+ const v = process.env.MOS_NAV_TEST_COUNTER;
1805
+ if (typeof v === 'string' && v.length > 0) return v;
1806
+ return null;
1807
+ }
1808
+
1809
+ function bumpReadCounter(counterFile) {
1810
+ if (!counterFile) return;
1811
+ try {
1812
+ let n = 0;
1813
+ try {
1814
+ const raw = fs.readFileSync(counterFile, 'utf8');
1815
+ const parsed = parseInt(String(raw).trim(), 10);
1816
+ if (!isNaN(parsed)) n = parsed;
1817
+ } catch (_) {}
1818
+ n += 1;
1819
+ try { fs.mkdirSync(path.dirname(counterFile), { recursive: true }); } catch (_) {}
1820
+ fs.writeFileSync(counterFile, String(n));
1821
+ } catch (_) { /* counter is best-effort */ }
1822
+ }
1823
+
1824
+ function deferredSleep(ms) {
1825
+ return new Promise(function (resolve) { setTimeout(resolve, ms); });
1826
+ }
1827
+
1828
+ function callDecideWithTimeout(decideFn, turn, ctx, hardTimeoutMs) {
1829
+ // Promise.race: decide() runs synchronously and returns its result;
1830
+ // we wrap it in a microtask so race can compete cleanly. The sleep
1831
+ // arm resolves to a sentinel symbol so we can distinguish timeout.
1832
+ const TIMEOUT_SENTINEL = '__nav_timeout__';
1833
+ const decidePromise = new Promise(function (resolve) {
1834
+ // Honor MOS_NAV_TEST_SLEEP by deferring decide() execution.
1835
+ const sleepMs = navTestSleepMs();
1836
+ const launch = function () {
1837
+ try {
1838
+ const result = decideFn(turn, ctx);
1839
+ resolve(result);
1840
+ } catch (e) {
1841
+ // Throws inside decide become structured engine_fault decisions
1842
+ // upstream; if it bubbled here treat as null fallback.
1843
+ resolve(null);
1844
+ }
1845
+ };
1846
+ if (sleepMs > 0) {
1847
+ setTimeout(launch, sleepMs);
1848
+ } else {
1849
+ // Defer to next microtask so Promise.race actually races.
1850
+ setImmediate(launch);
1851
+ }
1852
+ });
1853
+ const timeoutPromise = deferredSleep(hardTimeoutMs).then(function () {
1854
+ return TIMEOUT_SENTINEL;
1855
+ });
1856
+ return Promise.race([decidePromise, timeoutPromise]).then(function (val) {
1857
+ if (val === TIMEOUT_SENTINEL) return null;
1858
+ return val;
1859
+ });
1860
+ }
1861
+
1862
+ // RETR-02 / D-03 (Phase 141): derive a LOCAL conversation seed from the last
1863
+ // ~2 conversation turns so retrieval seeds from what the navigator just said,
1864
+ // not venture-state only. This closes the conversation-to-retrieval loop now
1865
+ // that getRoomContext (Plan 03) exists to consume the seed.
1866
+ //
1867
+ // HARD FENCE (D-03a, Canon Part 8): the seed is LOCAL-only. It rides the turn
1868
+ // object into the Navigation Engine (the local seed lane) and feeds getRoomContext
1869
+ // downstream. It is NEVER added to any payload that reaches buildBrainPacket or
1870
+ // the brain client -- the Brain keeps receiving generic handles only.
1871
+ //
1872
+ // Latency (T-141-12): windowed to the last SEED_TURN_COUNT fragments of the most
1873
+ // recent session, each char-capped, so an unbounded fragment dump cannot blow the
1874
+ // 1200ms NAV budget. getSessionHistory reads the caller-owned handle through the
1875
+ // navigation chokepoint (sync-backed sqlite under a resolved Promise); on any
1876
+ // fault the seed degrades to an empty string and the loop runs as before.
1877
+ const SEED_TURN_COUNT = 2;
1878
+ const SEED_CHAR_CAP = 400;
1879
+
1880
+ // Phase 150-04 (MEM-07 link L3): derive lowFillSections from a getRoomContext
1881
+ // result's RAW-LOCAL cortexNodes (legD, Plan Task 2). A section is "low-fill"
1882
+ // when it has a STATE memory_artifact node but NO governing_thought node STATES
1883
+ // it (the MINTO governing-thought is unfilled). Returns an array of LOCAL section
1884
+ // slugs -- never artifact bytes, never prose (D-03a LOCAL-lane fence; Part 8).
1885
+ // Reuses the SAME getRoomContext result the caller already computed -- no second
1886
+ // room.db read. Defensive: returns [] on any malformed/absent input; never throws.
1887
+ function deriveLowFillSections(roomContext) {
1888
+ if (!roomContext || typeof roomContext !== 'object') return [];
1889
+ const nodes = Array.isArray(roomContext.cortexNodes) ? roomContext.cortexNodes : [];
1890
+ if (nodes.length === 0) return [];
1891
+ const stateSections = new Set();
1892
+ const governedSections = new Set();
1893
+ for (const n of nodes) {
1894
+ if (!n || typeof n !== 'object') continue;
1895
+ const props = (n.properties && typeof n.properties === 'object') ? n.properties : {};
1896
+ const section = typeof props.section === 'string' ? props.section : (typeof n.sourceSection === 'string' ? n.sourceSection : '');
1897
+ if (!section) continue;
1898
+ if (n.type === 'memory_artifact' && props.kind === 'STATE') {
1899
+ stateSections.add(section);
1900
+ } else if (n.type === 'governing_thought') {
1901
+ governedSections.add(section);
1902
+ }
1903
+ }
1904
+ const out = [];
1905
+ for (const s of stateSections) {
1906
+ if (!governedSections.has(s)) out.push(s);
1907
+ }
1908
+ return out;
1909
+ }
1910
+
1911
+ function deriveConversationSeed(navMod, db) {
1912
+ // Returns Promise<string>. Never rejects -- a fault yields '' (empty seed).
1913
+ //
1914
+ // reach-gate-stale-turn-input FIX (2026-07-02): the CURRENT user turn
1915
+ // (STDIN_MESSAGE) is the freshest and most decision-relevant fragment, but at
1916
+ // UserPromptSubmit time it is NOT yet persisted to session history -- this hook
1917
+ // (intent-classifier, UserPromptSubmit #2) fires BEFORE operator-update /
1918
+ // jtbd-update store the turn. Without it the routing seed carried ONLY prior
1919
+ // stored turns, so turn.userText fed to decide()/dispatchSensors AND the
1920
+ // getRoomContext seedFragments were turn-independent: the engine decided on
1921
+ // stale input and produced a byte-identical reach gate across materially
1922
+ // different turns (the reach-gate-stale-turn-input defect). We now APPEND the
1923
+ // current message as the freshest seed line so the engine decides on the actual
1924
+ // turn. D-03a LOCAL-lane fence preserved: this string rides turn.userText +
1925
+ // seedFragments (LOCAL routing lane) exactly as the history seed already did; it
1926
+ // is NEVER threaded toward buildBrainPacket (Canon Part 8). When STDIN_MESSAGE is
1927
+ // empty the output is byte-identical to the pre-fix history-only seed.
1928
+ const currentTurn = (typeof STDIN_MESSAGE === 'string' && STDIN_MESSAGE.length > 0)
1929
+ ? (STDIN_MESSAGE.length > SEED_CHAR_CAP ? STDIN_MESSAGE.slice(0, SEED_CHAR_CAP) : STDIN_MESSAGE)
1930
+ : '';
1931
+ function withCurrentTurn(historySeed) {
1932
+ const parts = [];
1933
+ if (typeof historySeed === 'string' && historySeed.length > 0) parts.push(historySeed);
1934
+ if (currentTurn.length > 0) parts.push(currentTurn); // freshest turn last (resolveSeedNode + sensors prefer the tail)
1935
+ return parts.join('\n');
1936
+ }
1937
+ if (!navMod || !db || typeof navMod.getSessionHistory !== 'function') {
1938
+ return Promise.resolve(withCurrentTurn(''));
1939
+ }
1940
+ return Promise.resolve()
1941
+ .then(function () { return navMod.getSessionHistory(db, 1); })
1942
+ .then(function (sessions) {
1943
+ if (!Array.isArray(sessions) || sessions.length === 0) return withCurrentTurn('');
1944
+ const frags = Array.isArray(sessions[0].fragments) ? sessions[0].fragments : [];
1945
+ const recent = frags.slice(-SEED_TURN_COUNT);
1946
+ const seed = recent
1947
+ .map(function (f) {
1948
+ const c = f && typeof f.content === 'string' ? f.content : '';
1949
+ return c.length > SEED_CHAR_CAP ? c.slice(0, SEED_CHAR_CAP) : c;
1950
+ })
1951
+ .filter(function (c) { return c.length > 0; })
1952
+ .join('\n');
1953
+ return withCurrentTurn(seed);
1954
+ })
1955
+ .catch(function () { return withCurrentTurn(''); });
1956
+ }
1957
+
1958
+ function runNavigationEngine(roomDir, sessionId) {
1959
+ // Returns Promise<{decision, elapsed_ms} | null>. Never throws.
1960
+ return new Promise(function (resolve) {
1961
+ const startedAt = Date.now();
1962
+ // Phase 135-01: caller-owned room.db handle + its closer, declared at the
1963
+ // function scope so the outer catch can close the handle if anything throws
1964
+ // synchronously after the handle is opened (no leak on the error path).
1965
+ let roomDb = null;
1966
+ let navigationMod = null;
1967
+ function closeRoomDbHandle() {
1968
+ // Close the caller-owned room.db handle through the chokepoint. Tolerant of
1969
+ // a null handle. Called in the promise finally AND the outer catch so a
1970
+ // decide() fault or a synchronous throw never leaks the handle (Pitfall 5).
1971
+ if (roomDb && navigationMod && typeof navigationMod.closeRoomDbForCaller === 'function') {
1972
+ try {
1973
+ navigationMod.closeRoomDbForCaller(roomDb);
1974
+ } catch (_e) {
1975
+ /* tolerant: already closed */
1976
+ }
1977
+ roomDb = null;
1978
+ }
1979
+ }
1980
+ try {
1981
+ // Test-only fast paths.
1982
+ if (navTestThrowing()) {
1983
+ return resolve(null);
1984
+ }
1985
+ // Lazy-require so missing deps in Tier 0 environments do not blow
1986
+ // up the classifier hot path.
1987
+ let navEngine;
1988
+ let folderMemory;
1989
+ let userMdOps;
1990
+ try {
1991
+ navEngine = require(
1992
+ path.join(__dirname, '..', 'lib', 'core', 'navigation-engine.cjs')
1993
+ );
1994
+ folderMemory = require(
1995
+ path.join(__dirname, '..', 'lib', 'core', 'folder-memory.cjs')
1996
+ );
1997
+ userMdOps = require(
1998
+ path.join(__dirname, '..', 'lib', 'core', 'user-md-ops.cjs')
1999
+ );
2000
+ } catch (_e) {
2001
+ return resolve(null);
2002
+ }
2003
+
2004
+ const sectionPath = resolveActiveSectionPathForRoom(roomDir);
2005
+ if (!sectionPath) {
2006
+ // No section to read; engine returns Tier 0 fallback. Still
2007
+ // useful to emit + persist so /mos:explain-decision has a row.
2008
+ }
2009
+
2010
+ // Per-turn quadruple cache: a single read, observed by tests via
2011
+ // MOS_NAV_TEST_COUNTER.
2012
+ let quadruple = null;
2013
+ if (sectionPath) {
2014
+ const counterFile = navTestCounterPath();
2015
+ bumpReadCounter(counterFile);
2016
+ try {
2017
+ quadruple = folderMemory.readQuadruple(sectionPath);
2018
+ } catch (_e) {
2019
+ quadruple = null;
2020
+ }
2021
+ }
2022
+
2023
+ // USER.md persona read (graceful on absent / malformed).
2024
+ let userMd = null;
2025
+ try {
2026
+ const userPath = path.join(roomDir, 'USER.md');
2027
+ userMd = userMdOps.readUserMd(userPath);
2028
+ } catch (_e) {
2029
+ userMd = null;
2030
+ }
2031
+
2032
+ // Build context.userPersona shape expected by the engine's
2033
+ // buildIntentPersona reader (Plan 91-00).
2034
+ let userPersona = null;
2035
+ if (userMd && typeof userMd === 'object') {
2036
+ userPersona = {
2037
+ archetype: userMd.canonical_role || null,
2038
+ problem_type: userMd.problem_type === 'unknown' ? null : userMd.problem_type,
2039
+ venture_stage: userMd.venture_stage === 'unknown' ? null : userMd.venture_stage,
2040
+ };
2041
+ }
2042
+
2043
+ // RETR-02 / D-03 (Phase 141): the per-turn turn object is now built AFTER
2044
+ // roomDb opens (below) so it can carry a real conversation seed derived from
2045
+ // the last ~2 turns -- it no longer hard-codes a null seed. See the
2046
+ // deriveConversationSeed + turn assembly just before callDecideWithTimeout.
2047
+
2048
+ // Phase 91-07 Wave 3 upgrade: real brain-client.isAvailable()
2049
+ // scalar lookup. Per Canon Part 8 Section 9.3, isAvailable() is
2050
+ // an EXPLICITLY PERMITTED boolean scalar -- no user content
2051
+ // egress, no network when cached. The brain-client query, search,
2052
+ // and smartSearch entry points remain FORBIDDEN from the
2053
+ // Navigation Engine flow (no parentheses on purpose so a literal
2054
+ // grep guard for forbidden-call patterns reports zero matches in
2055
+ // this file).
2056
+ //
2057
+ // Failure modes:
2058
+ // - require fails (module missing in degraded environments)
2059
+ // - isAvailable not a function (older brain-client builds)
2060
+ // - isAvailable throws (unexpected internal fault)
2061
+ // All three default brainAvailable to false, which is the safe
2062
+ // path: the engine gracefully degrades to mode_b (when BRAIN.md
2063
+ // carries brain_offline) or tier_0 (when BRAIN.md is absent).
2064
+ let brainAvailable = false;
2065
+ try {
2066
+ const brainClient = require(
2067
+ path.join(__dirname, '..', 'lib', 'core', 'brain-client.cjs')
2068
+ );
2069
+ if (typeof brainClient.isAvailable === 'function') {
2070
+ brainAvailable = !!brainClient.isAvailable();
2071
+ }
2072
+ } catch (_e) {
2073
+ brainAvailable = false;
2074
+ }
2075
+
2076
+ // Phase 135-01: wire the COMPLETE resolver production input set into the
2077
+ // engine context. Before this, context carried only
2078
+ // { quadruple, brainAvailable, userPersona, intentSignal } and the offer
2079
+ // resolver (Phase 135-02) ran against an empty roomState -- the grounded
2080
+ // reason collapsed to [[undefined]] and the offer loop was dark in
2081
+ // production while in-memory unit tests passed. Every new read uses the
2082
+ // SAME lazy-require + try/catch + safe-default pattern as the
2083
+ // brainClient.isAvailable() read above, so a missing module or an absent
2084
+ // room.db degrades gracefully and never crashes the hot path. The path
2085
+ // stays SYNCHRONOUS and LOCAL-only (A3 LOCKED): no await is introduced,
2086
+ // getCurrentJTBD / openRoomDbForCaller are all sync chokepoint reads.
2087
+
2088
+ // operator: drives the JUST_TALK silence rule (Phase 135 SC6). Default to
2089
+ // the operator.cjs cold-start string 'JUST_TALK' on any fault.
2090
+ let operatorState = 'JUST_TALK';
2091
+ try {
2092
+ const operatorMod = require(
2093
+ path.join(__dirname, '..', 'lib', 'conversation', 'operator.cjs')
2094
+ );
2095
+ if (operatorMod && typeof operatorMod.getCurrent === 'function') {
2096
+ const cur = operatorMod.getCurrent(roomDir);
2097
+ if (cur && typeof cur.current === 'string') {
2098
+ operatorState = cur.current;
2099
+ }
2100
+ }
2101
+ } catch (_e) {
2102
+ operatorState = 'JUST_TALK';
2103
+ }
2104
+
2105
+ // jtbd: the active-JTBD intent leg (Phase 135 SC4). navigation.getCurrentJTBD
2106
+ // is roomDir-taking and opens room.db internally through the chokepoint
2107
+ // (sync, A3-compatible). Take .jtbd when .ok !== false, else null.
2108
+ let jtbd = null;
2109
+ try {
2110
+ navigationMod = require(
2111
+ path.join(__dirname, '..', 'lib', 'core', 'navigation.cjs')
2112
+ );
2113
+ if (navigationMod && typeof navigationMod.getCurrentJTBD === 'function') {
2114
+ const j = navigationMod.getCurrentJTBD(roomDir);
2115
+ if (j && j.ok !== false && typeof j.jtbd === 'string') {
2116
+ jtbd = j.jtbd;
2117
+ }
2118
+ }
2119
+ } catch (_e) {
2120
+ jtbd = null;
2121
+ }
2122
+
2123
+ // roomState.db: a LIVE room.db handle opened via the allow-listed navigation
2124
+ // chokepoint (NOT a direct room-db.cjs require -- the substrate guard fails
2125
+ // that for scripts/intent-classifier.cjs). Guarded by fs.existsSync inside
2126
+ // openRoomDbForCaller; returns null when room.db is absent (Tier 0), in
2127
+ // which case the resolver and shouldExclude treat a null db as empty-state
2128
+ // and abstain gracefully. The handle MUST be CLOSED in a finally after
2129
+ // decide() settles (below) so a thrown decide() never leaks it (Pitfall 5).
2130
+ // roomDb + navigationMod are declared at the function scope above.
2131
+ if (navigationMod && typeof navigationMod.openRoomDbForCaller === 'function') {
2132
+ try {
2133
+ roomDb = navigationMod.openRoomDbForCaller(roomDir);
2134
+ } catch (_e) {
2135
+ roomDb = null;
2136
+ }
2137
+ }
2138
+
2139
+ const context = {
2140
+ quadruple: quadruple,
2141
+ brainAvailable: brainAvailable,
2142
+ userPersona: userPersona,
2143
+ intentSignal: null,
2144
+ // Phase 135-01 resolver production inputs:
2145
+ operator: operatorState,
2146
+ sectionPath: sectionPath, // scope + the [[wikilink]] target the grounded reason needs
2147
+ problemType: userPersona && userPersona.problem_type, // rankForSelector input (null when unknown)
2148
+ jtbd: jtbd,
2149
+ // Phase 150-04 (MEM-07 link L3): thread the STARVED decide() sensor inputs
2150
+ // that decide() already READS (navigation-engine.cjs sensorTuple.stage :618,
2151
+ // sensorCtx.roomDir :621, sensorCtx.lowFillSections :622) but the producer
2152
+ // never SET -- so sensor-lagging-component + sensor-gate-approach abstained.
2153
+ // stage -- the venture_stage from the USER projection (the same
2154
+ // value already on userPersona.venture_stage); decide()
2155
+ // copies it onto sensorTuple.stage so gate-approach fires.
2156
+ // roomDir -- LOCAL room dir for sensorCtx.roomDir.
2157
+ // lowFillSections -- set just-in-time from the cortex projection inside
2158
+ // attachRoomContextAndDecide (reuses the SAME
2159
+ // getRoomContext result; no second room.db read).
2160
+ // D-03a LOCAL-lane fence: these ride context ONLY (the routing lane); they
2161
+ // are NEVER added to the turn object or any path reaching buildBrainPacket.
2162
+ stage: userPersona && userPersona.venture_stage ? userPersona.venture_stage : undefined,
2163
+ roomDir: roomDir,
2164
+ roomState: {
2165
+ db: roomDb,
2166
+ roomDir: roomDir,
2167
+ // invocationsSinceDecision: optional fast-path counter populated by the
2168
+ // closer in Phase 135-03; read by shouldExclude. Left undefined for v1
2169
+ // so shouldExclude falls back to the memory_event tail via roomState.db.
2170
+ },
2171
+ };
2172
+
2173
+ // RETR-02 / D-03a: build the turn with a real conversation seed (last ~2
2174
+ // turns) on the LOCAL lane only, then race decide() inside the existing
2175
+ // 1200ms envelope. deriveConversationSeed never rejects (faults -> '').
2176
+ deriveConversationSeed(navigationMod, roomDb)
2177
+ .then(function (conversationSeed) {
2178
+ // FIX-05 (Phase 150.6-03): stash the seed on the shared context (LOCAL
2179
+ // routing lane only; NEVER threaded toward buildBrainPacket per the
2180
+ // D-03a fence) so the sibling decision .then() can forward it to the
2181
+ // F.1 closer payload. The closer classifies it at the write seam via
2182
+ // recordSelectorDecision and stores ONLY the scalar boolean
2183
+ // venture_classified + source enum (Canon Part 8: the sentence itself
2184
+ // never enters any stored payload).
2185
+ context.conversationSeed =
2186
+ (typeof conversationSeed === 'string' && conversationSeed.length > 0)
2187
+ ? conversationSeed
2188
+ : null;
2189
+ const turn = {
2190
+ userText: conversationSeed, // LOCAL seed lane only (D-03a fence): never threads toward the Brain packet
2191
+ sectionPath: sectionPath,
2192
+ sessionId: sessionId,
2193
+ };
2194
+ // CASC-02 (Phase 142): compute the navigated neighborhood through the
2195
+ // navigation.cjs chokepoint on the CALLER-OWNED roomDb handle (Canon Part 9)
2196
+ // and thread it onto context.roomContext for decide(). The SAME conversation
2197
+ // seed feeds getRoomContext as seedFragments (one seed, both consumers); the
2198
+ // sectionPath rides as sectionContext so getRoomContext's Leg C focus-node
2199
+ // resolver reaches the section neighborhood. The await stays INSIDE this
2200
+ // single deriveConversationSeed().then() chain, so it is bounded by the same
2201
+ // NAV_HARD_TIMEOUT_MS race envelope -- no second timeout, no added await
2202
+ // outside the race (T-142-03: getRoomContext benchmarked ~1ms, ~1200x under
2203
+ // the 1200ms NAV budget). On any fault or absent room.db, context.roomContext
2204
+ // degrades to null and decide() proceeds exactly as before (graceful, no
2205
+ // throw, no leak -- the caller-owned handle is still closed once in finally).
2206
+ // D-03a fence: the neighborhood rides context ONLY (the LOCAL routing lane);
2207
+ // it is NEVER added to the turn object or any path reaching buildBrainPacket.
2208
+ function attachRoomContextAndDecide(roomContext) {
2209
+ context.roomContext = roomContext || null;
2210
+ // Phase 150-04 (MEM-07 link L3): derive lowFillSections from the SAME
2211
+ // getRoomContext cortex projection (no second room.db read). The
2212
+ // low-fill sections are the room sections whose cortex projection is
2213
+ // thin: a memory_artifact STATE node exists for the section but no
2214
+ // governing_thought node STATES it (an unfilled MINTO). LOCAL section
2215
+ // slugs only -- never artifact bytes (D-03a LOCAL-lane fence).
2216
+ context.lowFillSections = deriveLowFillSections(context.roomContext);
2217
+ return callDecideWithTimeout(navEngine.decide, turn, context, NAV_HARD_TIMEOUT_MS);
2218
+ }
2219
+ if (
2220
+ roomDb &&
2221
+ navigationMod &&
2222
+ typeof navigationMod.getRoomContext === 'function'
2223
+ ) {
2224
+ const roomId = path.basename(roomDir || '.') || 'room';
2225
+ const seedFragments = (typeof conversationSeed === 'string' && conversationSeed.length > 0)
2226
+ ? [{ role: 'user', content: conversationSeed, sectionContext: sectionPath || null }]
2227
+ : (sectionPath ? [{ role: 'user', content: '', sectionContext: sectionPath }] : []);
2228
+ return Promise.resolve()
2229
+ .then(function () {
2230
+ return navigationMod.getRoomContext(roomDb, roomId, { seedFragments: seedFragments });
2231
+ })
2232
+ .then(attachRoomContextAndDecide, function () {
2233
+ return attachRoomContextAndDecide(null); // fault -> null, proceed as before
2234
+ });
2235
+ }
2236
+ return attachRoomContextAndDecide(null);
2237
+ })
2238
+ .then(function (decision) {
2239
+ if (decision === null || decision === undefined) {
2240
+ return resolve(null);
2241
+ }
2242
+ // Phase 91-03 integration test stubs: when MOS_NAV_TEST_FIRE_SKILL
2243
+ // is set, override the engine's fire_skill so the router can be
2244
+ // exercised end-to-end without mocking the engine module. The
2245
+ // env vars are never set in production.
2246
+ const stubFire = navTestFireSkill();
2247
+ if (stubFire !== undefined) {
2248
+ decision.fire_skill = stubFire;
2249
+ }
2250
+ const stubSuppress = navTestSuppressSkills();
2251
+ if (stubSuppress !== undefined) {
2252
+ decision.suppress_skills = stubSuppress;
2253
+ }
2254
+ // Phase 91-04: offer_next_step stub injection.
2255
+ const stubOfferCmd = navTestOfferCommand();
2256
+ if (stubOfferCmd !== undefined) {
2257
+ if (stubOfferCmd === null) {
2258
+ decision.offer_next_step = null;
2259
+ } else {
2260
+ const stubOfferReason = navTestOfferReason();
2261
+ decision.offer_next_step = {
2262
+ command: stubOfferCmd,
2263
+ reason: typeof stubOfferReason === 'string' ? stubOfferReason : '',
2264
+ };
2265
+ }
2266
+ }
2267
+ // Quick-task 20260702 (Ruling 3a): persist THIS turn's actionable cue to
2268
+ // the LOCAL next-move side-channel so the statusline "Next:" goes live from
2269
+ // the per-turn decision (fire_skill / offer_next_step), not just the offer-
2270
+ // resolver. Runs HERE where the engine already runs (never in the hot
2271
+ // statusline process). Reuses the shipped writer (no second cache). Fully
2272
+ // guarded: any persist failure never affects the decision or the turn.
2273
+ //
2274
+ // Quick-task 260705-ui4: on the ABSTENTION leg (no routed offer / skill),
2275
+ // surface the active room's highest-priority `proposed` ratification item
2276
+ // as an enum/count-only cue instead of clearing to "Next: --". The provider
2277
+ // is LAZY -- the research/ scan runs only when the engine abstained, and the
2278
+ // scanner require is loaded inside the provider body so the common routed
2279
+ // path never pays for it.
2280
+ try {
2281
+ const nextMoveCache = require(
2282
+ path.join(__dirname, '..', 'lib', 'statusline', 'next-move-cache.cjs')
2283
+ );
2284
+ nextMoveCache.persistFromDecision(decision, {
2285
+ fallbackProvider: function () {
2286
+ try {
2287
+ const ratNext = require(
2288
+ path.join(__dirname, '..', 'lib', 'statusline', 'ratification-next.cjs')
2289
+ );
2290
+ const r = ratNext.scanRatificationNext();
2291
+ return r && typeof r.cue === 'string' ? r.cue : null;
2292
+ } catch (_scanErr) { return null; }
2293
+ },
2294
+ });
2295
+ } catch (_persistErr) { /* graceful: the reader degrades to the jtbd proxy */ }
2296
+ const elapsedMs = Date.now() - startedAt;
2297
+ // Phase 150-06 (D-08 render unlock): thread the projected cortexNodes
2298
+ // (the Wave-2 legD field) out on the LOCAL routing lane so the decide()
2299
+ // tail can build roomState.reachScores via cortex-reach-adapter and
2300
+ // surface the dial render. Presence + enums only -- NEVER prose, NEVER
2301
+ // toward buildBrainPacket (D-03a LOCAL-lane fence; Canon Part 8).
2302
+ const cortexNodes = (context.roomContext
2303
+ && Array.isArray(context.roomContext.cortexNodes))
2304
+ ? context.roomContext.cortexNodes
2305
+ : [];
2306
+ // Phase 158-02 (RJP-06 / SC-06 / D-07): fire one reach_presented
2307
+ // memory_event per OFFERED top-3 reach_id on THIS live engine arm,
2308
+ // keyed by reach_id, while roomDb is still the OPEN handle (it is
2309
+ // closed only in the trailing finally at .then(closeRoomDbHandle)
2310
+ // below). The PURE render seam (renderEngineDecisionWithDial) cannot
2311
+ // do this -- by the time it runs the db is already closed (the dial
2312
+ // render path is db-free). This counter is the enabler of the M-floor
2313
+ // + periodic-parole fences (Plan 03): nothing else records WHICH reach
2314
+ // was offered (selector_presentation is command-anonymous; the render
2315
+ // path is pure). We recompute the would-be-offered top-3 the SAME way
2316
+ // the render seam does so the counter matches what the navigator is
2317
+ // shown. buildReachList stays PURE: it receives only scalars plus an
2318
+ // array; db is NEVER threaded into dial-reach-orchestrator (SC-07).
2319
+ // Best-effort: any fault is swallowed and never blocks resolve, never
2320
+ // throws out of the .then, never prevents the finally
2321
+ // closeRoomDbHandle, never leaks the db handle. Payload is enum/scalar
2322
+ // only (reach_id + source_path + created_by) -- Part 8.
2323
+ //
2324
+ // Phase 245-08 (D-04 / 245-RESEARCH.md Pitfall 7): "the SAME way" used
2325
+ // to be a claim, not a fact. This block called buildReachList with ONLY
2326
+ // {tierMode, reachScores} and already omitted the 158-03 reject
2327
+ // discount, the hard-suppression set AND the structural relevance gate
2328
+ // that the live render applies -- a divergence that was live at HEAD
2329
+ // before this phase, not one Requirement 1 introduced, though the
2330
+ // fusion would have widened it. Both consumers now route through the
2331
+ // ONE composeDialReachScores helper, and this site now passes
2332
+ // suppressedReachIds too, so the logged "what was offered" matches what
2333
+ // actually rendered.
2334
+ //
2335
+ // ONE component is still deliberately omitted here, and the reason is a
2336
+ // genuine circular dependency rather than an oversight: reachPenalties
2337
+ // is computed BELOW this block, because computeReachPenalties counts
2338
+ // reach_presented rows and this block is what WRITES them. Whichever
2339
+ // runs first is deprived. Moving the penalty computation above this
2340
+ // emit would change what the LIVE RENDER sees (its M-floor and parole
2341
+ // counters would stop including this turn's own presentation), which is
2342
+ // a shipped-behavior change this phase did not authorize. So telemetry
2343
+ // passes reachPenalties: null and is short exactly the reject
2344
+ // discountedScores fold and the reject hard-suppression set. The tier
2345
+ // mode, the cortex priors, the Requirement 1 fusion and the relevance
2346
+ // gate are all shared. Stated, never silent.
2347
+ try {
2348
+ if (roomDb
2349
+ && navigationMod
2350
+ && typeof navigationMod.logMemoryEvent === 'function'
2351
+ && Array.isArray(cortexNodes)
2352
+ && cortexNodes.length > 0) {
2353
+ const reachOrchestrator = require(
2354
+ path.join(__dirname, '..', 'lib', 'hmi', 'dial-reach-orchestrator.cjs')
2355
+ );
2356
+ // Phase 245-08: CALL SITE B, the reach_presented telemetry
2357
+ // recompute, on the SAME shared composition helper the live render
2358
+ // uses. See the divergence note above for the one omitted input.
2359
+ const reachComposed = composeDialReachScores({
2360
+ cortexNodes: cortexNodes,
2361
+ reachPenalties: null,
2362
+ decision: decision,
2363
+ slotContext: buildDialSlotContext(context.roomContext, roomDir),
2364
+ liveTurnText: null,
2365
+ currentRoomName: (typeof roomDir === 'string' && roomDir.length > 0)
2366
+ ? path.basename(roomDir) : null,
2367
+ });
2368
+ const reachList = (reachComposed === null) ? null : reachOrchestrator.buildReachList({
2369
+ tierMode: reachComposed.tierMode,
2370
+ reachScores: reachComposed.reachScores,
2371
+ suppressedReachIds: reachComposed.suppressedReachIds,
2372
+ });
2373
+ if (reachList
2374
+ && Array.isArray(reachList.reaches)
2375
+ && Number.isInteger(reachList.offered_count)) {
2376
+ const offered = reachList.reaches.slice(0, reachList.offered_count);
2377
+ for (const reach of offered) {
2378
+ if (reach && typeof reach.reach_id === 'string' && reach.reach_id.length > 0) {
2379
+ navigationMod.logMemoryEvent(roomDb, 'reach_presented', {
2380
+ reach_id: reach.reach_id,
2381
+ source_path: 'dial:presented:' + reach.reach_id,
2382
+ created_by: 'system',
2383
+ });
2384
+ }
2385
+ }
2386
+ // Phase 183-01 METER-01 (gate_reached): one OBSERVATION per gate
2387
+ // render, beside the reach_presented loop, on THIS surface-shared
2388
+ // engine arm (NOT a CLI-only fork) so Desktop/Cowork gate-reaches are
2389
+ // counted too. Exactly ONE per gate (NOT one-per-reach): reach_count
2390
+ // = offered.length. Guarded by offered.length > 0. Deduped on the
2391
+ // turn-start handle (startedAt) so a re-entrant arm cannot
2392
+ // double-count one gate (the 60s logEvent idempotency window). The
2393
+ // payload is enum/scalar only -- counts + enums + the dedupe handle,
2394
+ // never prose, never a framework string, never user content (Part 8).
2395
+ // This is an OBSERVATION beside the render; it changes NOTHING about
2396
+ // what is offered (MAX_K=3, DIAL_REACH_K=6, the 0.70/0.15 gate, the
2397
+ // 6-reach bank, appendAskUserQuestionTrailer all untouched).
2398
+ // framework_invoked fires at ~0 production sites today, so the Gauge-1
2399
+ // density basis leans on reach_presented + gate_reached.
2400
+ if (offered.length > 0) {
2401
+ navigationMod.logMemoryEvent(roomDb, 'gate_reached', {
2402
+ reach_count: offered.length,
2403
+ routing_source: 'engine',
2404
+ source_path: 'gate:reached',
2405
+ created_by: 'system',
2406
+ dedupe_key: 'gate:' + startedAt,
2407
+ });
2408
+ }
2409
+ }
2410
+ }
2411
+ } catch (_reachEmitErr) {
2412
+ // best-effort: a faulting emit never blocks the turn or leaks the db
2413
+ }
2414
+ // Phase 158-03 (RJP-01..05 / SC-07): fold the bounded reject penalty +
2415
+ // the hard-suppression set on THIS live engine arm, while roomDb is
2416
+ // still OPEN. computeReachPenalties reads the reach_id-keyed reject
2417
+ // counts + presentation counts (via the navigation chokepoint, Part 9;
2418
+ // enums/scalars only, Part 8) and returns { discountedScores,
2419
+ // suppressedReachIds }. We thread the result OUT alongside cortex_nodes;
2420
+ // the render seam (renderEngineDecisionWithDial, db already closed)
2421
+ // merges discountedScores over the cortex reachScores and passes
2422
+ // suppressedReachIds into the PURE buildReachList -- db is NEVER threaded
2423
+ // into dial-reach-orchestrator (SC-07). Best-effort: any fault degrades
2424
+ // to an empty penalty set so the render is un-discounted, never blocked.
2425
+ // Zero rejections -> discountedScores is byte-identity (base*(1-0)) and
2426
+ // suppressedReachIds is empty -> byte-identical render (RJP-02).
2427
+ let reachPenalties = { discountedScores: {}, suppressedReachIds: [] };
2428
+ try {
2429
+ if (roomDb && Array.isArray(cortexNodes) && cortexNodes.length > 0) {
2430
+ const reachReader = require(
2431
+ path.join(__dirname, '..', 'lib', 'workflow', 'reach-reject-reader.cjs')
2432
+ );
2433
+ const penAdapter = require(
2434
+ path.join(__dirname, '..', 'lib', 'hmi', 'cortex-reach-adapter.cjs')
2435
+ );
2436
+ const penScores = penAdapter.buildReachScoresFromCortex(cortexNodes);
2437
+ const pen = reachReader.computeReachPenalties(roomDb, {
2438
+ reachScores: penScores,
2439
+ db: roomDb,
2440
+ roomDir: roomDir,
2441
+ });
2442
+ if (pen && typeof pen === 'object') {
2443
+ reachPenalties = {
2444
+ discountedScores: (pen.discountedScores && typeof pen.discountedScores === 'object')
2445
+ ? pen.discountedScores : {},
2446
+ suppressedReachIds: Array.isArray(pen.suppressedReachIds)
2447
+ ? pen.suppressedReachIds : [],
2448
+ };
2449
+ }
2450
+ }
2451
+ } catch (_reachPenErr) {
2452
+ // best-effort: degrade to the un-discounted render, never block resolve
2453
+ reachPenalties = { discountedScores: {}, suppressedReachIds: [] };
2454
+ }
2455
+ resolve({
2456
+ decision: decision,
2457
+ elapsed_ms: elapsedMs,
2458
+ cortex_nodes: cortexNodes,
2459
+ reach_penalties: reachPenalties,
2460
+ // Phase 209-01 (E3): thread the LIVE room context OUT so the render
2461
+ // seam (db already closed) can derive the dial slot map from
2462
+ // relevantNodes/cortexNodes. Presence-only LOCAL fields; never a
2463
+ // Brain wire (Part 8). Absent -> null -> buildDialSlotContext {}.
2464
+ room_context: context.roomContext || null,
2465
+ });
2466
+ })
2467
+ .catch(function () { resolve(null); })
2468
+ .then(closeRoomDbHandle, closeRoomDbHandle); // finally: close the handle on resolve OR reject (no leak)
2469
+ } catch (_e) {
2470
+ closeRoomDbHandle(); // no leak if a synchronous throw occurred after the handle opened
2471
+ resolve(null);
2472
+ }
2473
+ });
2474
+ }
2475
+
2476
+ function emitEngineDecisionBlock(roomDir, sessionId) {
2477
+ // Synchronous-from-caller wrapper that drives the async engine and
2478
+ // blocks the event loop only as long as the hard timeout. Used at the
2479
+ // tail of the classifier process so the user turn never proceeds
2480
+ // without the decision in hand.
2481
+ const result = runNavigationEngine(roomDir, sessionId);
2482
+ // Bridge promise to sync exit by pumping the event loop. Node 18+
2483
+ // does not expose deasync; we rely on process.exit happening from a
2484
+ // .then callback below. The function returns the promise; caller
2485
+ // awaits before exiting.
2486
+ return result;
2487
+ }
2488
+
2489
+ function appendTraceTurnNumber(roomDir, sessionId) {
2490
+ // Compute next turn number based on existing trace file.
2491
+ try {
2492
+ const filePath = path.join(roomDir, '.mindrian', 'decision-traces', sessionId + '.json');
2493
+ const raw = fs.readFileSync(filePath, 'utf8');
2494
+ const parsed = JSON.parse(raw);
2495
+ if (parsed && Array.isArray(parsed.traces) && parsed.traces.length > 0) {
2496
+ const last = parsed.traces[parsed.traces.length - 1];
2497
+ if (last && typeof last.turn === 'number') return last.turn + 1;
2498
+ }
2499
+ } catch (_) {}
2500
+ return 1;
2501
+ }
2502
+
2503
+ // ---------------------------------------------------------------------------
2504
+ // Phase 159-02 (HOW-4, DCW-01/04/06/08): consumePriorF1Pick -- the turn-start
2505
+ // consumer attachment.
2506
+ //
2507
+ // The producer half is wired (the engine arm below persists
2508
+ // decision_trace.f1_closer_payload near :1890). NOTHING reads it back at the
2509
+ // START of the next turn, so the dial loop (accept / defer / reject / Free-Text)
2510
+ // never records in a real room and Phase 158's computeReachPenalties structurally
2511
+ // always reads 0. This helper closes that read seam: at turn start, before the new
2512
+ // turn's dial render, it reads the PRIOR turn's persisted f1_closer_payload, routes
2513
+ // the navigator's pick through the Wave-1 shared-core consumer (consumeF1Pick) over
2514
+ // a navigation-chokepoint-owned room.db handle, and degrades to a no-op on any
2515
+ // non-dial / cold / faulted turn (DCW-04 byte-unchanged invariant).
2516
+ //
2517
+ // Canon binding:
2518
+ // Part 7: thin glue over the SHIPPED captureCliPick + consumeF1Pick + the
2519
+ // closers. Net-new = read-prior-trace + open/close the handle, NOT a
2520
+ // re-implemented recorder.
2521
+ // Part 8: only {verb enum, outcome enum, reach_id enum} reach any row. The raw
2522
+ // navigator text rides the FIX-05 LOCAL sentence lane (captureCliPick
2523
+ // confines answer.text to the optional `sentence`, which consumeF1Pick
2524
+ // forwards to closeOffer({sentence}) for classification-to-scalar at the
2525
+ // write seam). The pick text NEVER enters a row body or a Brain packet.
2526
+ // Part 9: room.db is opened ONLY via navigation.openRoomDbForCaller and CLOSED in
2527
+ // a finally via navigation.closeRoomDbForCaller (no leak). consumeF1Pick
2528
+ // never opens room.db itself; closeOffer routes the write through the
2529
+ // navigation chokepoint. The decision-trace FILE read is the system's own
2530
+ // bookkeeping file (the same file appendTraceTurnNumber reads at :1633),
2531
+ // NOT room data -- so the fs read is Part 9 legal (Part 9 governs room.db).
2532
+ //
2533
+ // Wire-2 precondition (DCW-08): the grounding->verb map on the producer side
2534
+ // (:1823-1834) already keys reachIds by a frozen REACH_IDS member when a sensor
2535
+ // reach drove the fire; non-reach groundings forward as-is and are dropped by the
2536
+ // downstream recordSelectorDecision enum-gate -> unkeyed write, never mis-keyed.
2537
+ // This helper reads the persisted reachIds verbatim, so the keyed-or-unkeyed
2538
+ // guarantee holds by construction.
2539
+ //
2540
+ // currentTurnAnswer: the AskUserQuestion F.1 answer shape captureCliPick accepts
2541
+ // ({ selectedOption, outcome?, text? }). On the live hook the caller passes the
2542
+ // turn's classified pick; the answer's raw text is confined to the LOCAL lane.
2543
+ //
2544
+ // Returns the consumeF1Pick result verbatim ({ ok, recorded?, reason?, outcome?,
2545
+ // reach_id? }); a structured no-op on every cold / unmatched / absent-db / faulted
2546
+ // turn. Wrapped best-effort: a fault returns { ok:false, reason } and NEVER throws.
2547
+ // ---------------------------------------------------------------------------
2548
+ function consumePriorF1Pick(roomDir, sessionId, currentTurnAnswer) {
2549
+ try {
2550
+ if (!roomDir || typeof roomDir !== 'string') {
2551
+ return { ok: false, reason: 'no_room' };
2552
+ }
2553
+
2554
+ // (1) Read the PRIOR turn's persisted trace from the system's own bookkeeping
2555
+ // file (the same file appendTraceTurnNumber reads). Read the LAST entry's
2556
+ // f1_closer_payload. A missing / corrupt / payload-less trace is a non-dial turn.
2557
+ let priorPayload = null;
2558
+ try {
2559
+ const filePath = path.join(
2560
+ roomDir, '.mindrian', 'decision-traces', sessionId + '.json'
2561
+ );
2562
+ const raw = fs.readFileSync(filePath, 'utf8');
2563
+ const parsed = JSON.parse(raw);
2564
+ if (parsed && Array.isArray(parsed.traces) && parsed.traces.length > 0) {
2565
+ const last = parsed.traces[parsed.traces.length - 1];
2566
+ if (last && last.f1_closer_payload && typeof last.f1_closer_payload === 'object') {
2567
+ priorPayload = last.f1_closer_payload;
2568
+ }
2569
+ }
2570
+ } catch (_) { priorPayload = null; }
2571
+
2572
+ // (2) Non-dial / cold turn: no prior payload -> immediate no-op. This is the
2573
+ // byte-unchanged guarantee (DCW-04): the helper emits nothing and writes nothing.
2574
+ if (!priorPayload || !Array.isArray(priorPayload.verbs) || priorPayload.verbs.length === 0) {
2575
+ return { ok: false, reason: 'no_prior_payload' };
2576
+ }
2577
+
2578
+ // (3) Lazy-require the capture adapter + the shared-core consumer + the
2579
+ // navigation chokepoint (best-effort: a load failure degrades to a no-op,
2580
+ // mirroring the producer's :1806-1855 idiom). navigation.cjs is the ONLY door
2581
+ // to room.db (Part 9); this helper opens through it, never room-db.cjs directly.
2582
+ let captureMod;
2583
+ let consumerMod;
2584
+ let nav;
2585
+ try {
2586
+ captureMod = require(
2587
+ path.join(__dirname, '..', 'lib', 'hmi', 'f1-pick-capture-cli.cjs')
2588
+ );
2589
+ consumerMod = require(
2590
+ path.join(__dirname, '..', 'lib', 'workflow', 'f1-pick-consumer.cjs')
2591
+ );
2592
+ nav = require(
2593
+ path.join(__dirname, '..', 'lib', 'core', 'navigation.cjs')
2594
+ );
2595
+ } catch (_) {
2596
+ return { ok: false, reason: 'consumer_unavailable' };
2597
+ }
2598
+ if (!captureMod || typeof captureMod.captureCliPick !== 'function'
2599
+ || !consumerMod || typeof consumerMod.consumeF1Pick !== 'function') {
2600
+ return { ok: false, reason: 'consumer_unavailable' };
2601
+ }
2602
+
2603
+ // Capture the pick from the current turn's answer (deterministic enum match
2604
+ // against priorPayload.verbs; the raw text stays on the LOCAL sentence lane).
2605
+ const captured = captureMod.captureCliPick(currentTurnAnswer, priorPayload);
2606
+ const pick = (captured && captured.pick) ? captured.pick : { verb: null, outcome: null };
2607
+
2608
+ // (4) Open the caller-owned room.db ONLY via the navigation chokepoint, call the
2609
+ // consumer, and ALWAYS close the handle in a finally (Part 9, no leak). The
2610
+ // chokepoint returns null when room.db is absent (Tier 0); consumeF1Pick then
2611
+ // surfaces a structured no-op without crashing.
2612
+ let roomDb = null;
2613
+ try {
2614
+ if (nav && typeof nav.openRoomDbForCaller === 'function') {
2615
+ roomDb = nav.openRoomDbForCaller(roomDir);
2616
+ }
2617
+ } catch (_) { roomDb = null; }
2618
+
2619
+ try {
2620
+ const roomState = { db: roomDb, roomDir: roomDir };
2621
+ // The matched verb completes its offer scaffold from the persisted generic
2622
+ // framework handle (carried on the payload by the producer, DCW-01). The
2623
+ // decision edge target_id is 'framework:' + framework, so the scaffold is what
2624
+ // lets the accept/defer/reject row write. A non-reach / framework-less payload
2625
+ // degrades: the consumer surfaces invalid_framework as a structured no-op.
2626
+ if (typeof priorPayload.framework === 'string' && priorPayload.framework.length > 0) {
2627
+ roomState.offer = { framework: priorPayload.framework };
2628
+ }
2629
+ // Forward the captured LOCAL sentence onto the payload the consumer sees so
2630
+ // closeOffer classifies it to a scalar at the write seam (FIX-05, Part 8).
2631
+ // The persisted payload.sentence already carries the producer-side seed; the
2632
+ // captured sentence (if any) only supplements it and never becomes a value.
2633
+ let payloadForConsumer = priorPayload;
2634
+ if (captured && typeof captured.sentence === 'string' && captured.sentence.length > 0
2635
+ && (typeof priorPayload.sentence !== 'string' || priorPayload.sentence.length === 0)) {
2636
+ payloadForConsumer = Object.assign({}, priorPayload, { sentence: captured.sentence });
2637
+ }
2638
+ return consumerMod.consumeF1Pick({
2639
+ priorPayload: payloadForConsumer,
2640
+ pick: pick,
2641
+ roomState: roomState,
2642
+ });
2643
+ } finally {
2644
+ try {
2645
+ if (nav && typeof nav.closeRoomDbForCaller === 'function') {
2646
+ nav.closeRoomDbForCaller(roomDb);
2647
+ }
2648
+ } catch (_) { /* tolerant: already closed */ }
2649
+ }
2650
+ } catch (_e) {
2651
+ // Best-effort: a fault is a no-op, never a throw -- a non-dial / cold / failed
2652
+ // turn is byte-unchanged from today (DCW-04, the load-bearing invariant).
2653
+ return { ok: false, reason: 'consumer_threw' };
2654
+ }
2655
+ }
2656
+
2657
+ // ---------------------------------------------------------------------------
2658
+ // Phase 194-04 (PSB-04): the F.8 binding-gate PRODUCER + the answer consumer.
2659
+ //
2660
+ // emitBindingGate COMPOSES the shipped shape-f8-renderer.renderShapeF8 (NO new
2661
+ // selector shape -- Part 11 born-wired, one governed path) to render the unbound /
2662
+ // off-scope binding gate: the scored candidate rooms (F.8 PAGES beyond its own
2663
+ // MAX_TOGGLE_N, never truncates) PLUS a first-class 'dev repo / no room' option. It
2664
+ // persists the offered option labels + their label->slug map on the decision trace
2665
+ // (the SAME turn-boundary bookkeeping f1_closer_payload rides) so the NEXT turn's
2666
+ // consumer can capture the confirmed answer. Fails open: a renderer / write fault
2667
+ // returns false so the caller falls through to the legacy advisory.
2668
+ //
2669
+ // consumePriorBindingAnswer mirrors consumePriorF1Pick: it reads the prior turn's
2670
+ // persisted binding-gate payload, COMPOSES captureCliActionSet (verbatim) to turn the
2671
+ // AskUserQuestion answer into the confirmed toggle set, translates the human labels
2672
+ // back to room slugs (the no-room label -> the reserved sentinel), and routes them to
2673
+ // the net-new consumeSessionBinding SINK which WRITES the session binding file. It is
2674
+ // best-effort by construction (a non-gate / cold turn is a no-op) and never throws.
2675
+ // ---------------------------------------------------------------------------
2676
+ function emitBindingGate(args) {
2677
+ const a = (args && typeof args === 'object') ? args : {};
2678
+ const best = a.best;
2679
+ const scored = Array.isArray(a.scored) ? a.scored : [];
2680
+ const sealedRooms = Array.isArray(a.sealedRooms) ? a.sealedRooms : [];
2681
+ const roomDir = a.roomDir;
2682
+ const sessionId = a.sessionId;
2683
+ if (!best || typeof best.name !== 'string') return false;
2684
+
2685
+ // Compose the shipped F.8 renderer (lazy require; a load fault degrades to the
2686
+ // legacy advisory).
2687
+ let renderer = null;
2688
+ try {
2689
+ renderer = require(path.join(__dirname, '..', 'lib', 'hmi', 'shape-f8-renderer.cjs'));
2690
+ } catch (_e) {
2691
+ return false;
2692
+ }
2693
+ if (!renderer || typeof renderer.renderShapeF8 !== 'function') return false;
2694
+
2695
+ // Weak / dev-repo signal (the SAME test the legacy weak-match guard uses): a name
2696
+ // match OR >=2 entity matches is a STRONG room signal; otherwise the top score is a
2697
+ // dev-repo/weak match and the pre-check defaults to the reserved no-room sentinel.
2698
+ const weak = !best.nameMatch && best.entityMatches < 2;
2699
+ const preCheckLabel = weak ? NO_ROOM_LABEL : best.name;
2700
+
2701
+ // Candidate options: the scored rooms (desc, deduped) PLUS the first-class no-room
2702
+ // option. renderShapeF8 pre-checks any option at/above the frozen 0.70 confidence,
2703
+ // so the pre-check target is handed 0.71 (display-only; NEVER auto-applies -- the
2704
+ // consumer only ever sees the CONFIRMED set) and the rest a low value.
2705
+ const ranked = scored.slice().sort(function (x, y) { return y.score - x.score; });
2706
+ const options = [];
2707
+ const labelToSlug = {};
2708
+ const seen = new Set();
2709
+ for (const r of ranked) {
2710
+ if (!r || typeof r.name !== 'string' || r.name.length === 0) continue;
2711
+ if (seen.has(r.name)) continue;
2712
+ seen.add(r.name);
2713
+ labelToSlug[r.name] = r.name;
2714
+ options.push({ label: r.name, confidence: (r.name === preCheckLabel) ? 0.71 : 0.10 });
2715
+ }
2716
+ labelToSlug[NO_ROOM_LABEL] = NO_ROOM_SLUG;
2717
+ options.push({ label: NO_ROOM_LABEL, confidence: (preCheckLabel === NO_ROOM_LABEL) ? 0.71 : 0.10 });
2718
+
2719
+ let rendered = null;
2720
+ try {
2721
+ rendered = renderer.renderShapeF8({
2722
+ options: options,
2723
+ header: '-- mindrianOS -- bind session -- select rooms --',
2724
+ });
2725
+ } catch (_e) {
2726
+ return false;
2727
+ }
2728
+ if (!rendered || !rendered.zones) return false;
2729
+
2730
+ const zones = rendered.zones;
2731
+
2732
+ // Phase 209-01 (E4): mint the AskUserQuestion trailer through the SEED-020
2733
+ // dispatcher-owned door (appendAskUserQuestionTrailer) so the F.8 gate carries
2734
+ // the self-decoding marker + BINDING imperative, exactly like the engine arm.
2735
+ //
2736
+ // SEED-020 pickShape exemption (mirrors the 150.5-02 engine-arm decision): this
2737
+ // gate does NOT route through pickShape('F.8'). pickShape applies the Canon
2738
+ // Part 3 install-tier-0 refuse, which would KILL the session-unbound gate on a
2739
+ // tier-0 room -- but a session that is not yet bound to a room MUST be able to
2740
+ // fire its binding card regardless of tier (same rationale as the D-01 cold
2741
+ // card). Routing through the trailer door reuses the single construction site
2742
+ // with NO new dispatcher branch (Canon Part 7). Capture the F.8 footer BEFORE
2743
+ // the trailer folds the marker + binding into zones.footer so the trailer is
2744
+ // appended to additionalContext exactly once (no duplication).
2745
+ const footerBase = (typeof zones.footer === 'string' && zones.footer.length > 0)
2746
+ ? zones.footer : '';
2747
+ try {
2748
+ const dispatcher = require(path.join(__dirname, '..', 'lib', 'hmi', 'selector-dispatcher.cjs'));
2749
+ dispatcher.appendAskUserQuestionTrailer(rendered, 'F.8');
2750
+ } catch (_e) { /* trailer fault: degrade to the untrailed gate, never block */ }
2751
+
2752
+ // Phase 209-06 (H3): the PRIMARY side-channel producer for the F.8 binding
2753
+ // gate. sessionId is a direct arg here (unlike the pickShape door, which has
2754
+ // none in scope). Lazy-required inside its own try/catch; a writer fault
2755
+ // never affects the rendered gate.
2756
+ try {
2757
+ const sidechannel = require(
2758
+ path.join(__dirname, '..', 'lib', 'core', 'card-fire-sidechannel.cjs')
2759
+ );
2760
+ sidechannel.recordReachedGate({
2761
+ sessionId: sessionId,
2762
+ surface: 'scripts/intent-classifier.cjs',
2763
+ shape: 'F.8',
2764
+ subjectText: [zones.header, zones.body].filter(function (s) {
2765
+ return typeof s === 'string' && s.length > 0;
2766
+ }).join(' '),
2767
+ });
2768
+ } catch (_e) {
2769
+ /* never let a side-channel fault affect the rendered gate */
2770
+ }
2771
+
2772
+ const bodyLines = [];
2773
+ if (zones.header) bodyLines.push(zones.header);
2774
+ if (zones.body) bodyLines.push(zones.body);
2775
+ if (zones.signals) bodyLines.push(zones.signals);
2776
+ if (footerBase) bodyLines.push(footerBase); // E4: the F.8 stat-strip footer
2777
+ const guidance =
2778
+ 'Fire the AskUserQuestion card with these rooms as a multi-select (toggle every ' +
2779
+ 'room this session may write to, pick ONE primary write target, and offer a ' +
2780
+ '"remember for this session" toggle); never reproduce this block as text (SEED-021). ' +
2781
+ 'Selecting "' + NO_ROOM_LABEL + '" binds the session to no room (dev-repo work). ' +
2782
+ 'Confirm the set with the user before writing any room artifact; the confirmed set ' +
2783
+ 'becomes this session binding.';
2784
+
2785
+ // E4: append the trailer marker + binding to additionalContext, each guarded on
2786
+ // its own presence (same idiom as the engine arm) so a missing field never
2787
+ // renders 'undefined'.
2788
+ const trailerLines = [];
2789
+ const bindMarker = rendered.askuserquestion_marker;
2790
+ const bindBinding = rendered.askuserquestion_binding;
2791
+ if (typeof bindMarker === 'string' && bindMarker.length > 0) trailerLines.push(bindMarker);
2792
+ if (typeof bindBinding === 'string' && bindBinding.length > 0) trailerLines.push(bindBinding);
2793
+ const additionalContext = bodyLines.join('\n') + '\n\n' + guidance
2794
+ + (trailerLines.length > 0 ? '\n\n' + trailerLines.join('\n') : '');
2795
+
2796
+ const systemMessage = 'session unbound: choose which room(s) this session writes to';
2797
+
2798
+ // Persist the offered gate payload on the decision trace so the next turn's consumer
2799
+ // can capture the answer (the SAME sink f1_closer_payload rides -- no new file family).
2800
+ try {
2801
+ if (roomDir && sessionId) {
2802
+ persistDecisionTrace(roomDir, sessionId, {
2803
+ ts: new Date().toISOString(),
2804
+ kind: 'binding_gate',
2805
+ binding_gate_payload: {
2806
+ options: options.map(function (o) { return o.label; }),
2807
+ labelToSlug: labelToSlug,
2808
+ preChecked: preCheckLabel,
2809
+ },
2810
+ });
2811
+ }
2812
+ } catch (_e) { /* fire-and-forget: persistence is best-effort */ }
2813
+
2814
+ const envelope = {
2815
+ hookSpecificOutput: {
2816
+ hookEventName: 'UserPromptSubmit',
2817
+ additionalContext: additionalContext,
2818
+ },
2819
+ systemMessage: systemMessage,
2820
+ };
2821
+ try {
2822
+ process.stdout.write(JSON.stringify(envelope) + '\n');
2823
+ } catch (_e) {
2824
+ // Last-resort fallback: raw text (never throws back into the hook).
2825
+ try { process.stdout.write(additionalContext + '\n'); } catch (_) {}
2826
+ }
2827
+ return true;
2828
+ }
2829
+
2830
+ // WR-01 fix (Phase 225 REVIEW-FIX): the PD-1 "once per session per room" marker
2831
+ // used to be a scan for a 'zero_score_gate' entry in the SHARED decision-trace
2832
+ // file. That file also carries the Phase-91 navigation-engine's own trace entry
2833
+ // on essentially every substantive turn (persistDecisionTrace, TRACE_ROTATE_AT =
2834
+ // 50, drop-oldest-10 on rotation), so a 'zero_score_gate' marker had a BOUNDED
2835
+ // lifetime (evicted after exactly 50 further trace-writing turns), not a session
2836
+ // lifetime -- well within a normal working session, silently re-arming the gate
2837
+ // mid-session, contradicting docs/ENV-TUNING.md's "fires at most once per room
2838
+ // per session" claim. Root cause: reusing a rotated, high-churn shared file for a
2839
+ // signal that needs session-lifetime durability. Fix: a DEDICATED marker file
2840
+ // that this suppression check owns exclusively (never touched by
2841
+ // persistDecisionTrace / TRACE_ROTATE_AT), so it cannot be rotated out no matter
2842
+ // how many further turns the session runs. Same atomic tmp+rename durability
2843
+ // idiom already used by persistDecisionTrace / writeSessionBinding in this repo.
2844
+ function zeroScoreGateMarkerPath(roomDir, sessionId) {
2845
+ return path.join(roomDir, '.mindrian', 'decision-traces', sessionId + '.zero-score-gate-offered.json');
2846
+ }
2847
+
2848
+ // Phase 225-01 (PD-1, once-per-session-per-room suppression), WR-01-fixed: read the
2849
+ // dedicated marker file (never rotated -- see zeroScoreGateMarkerPath above). A
2850
+ // missing / corrupt marker file means "not offered" (fail-open to false), so the
2851
+ // gate is NEVER suppressed by a read fault. The whole body is in try/catch
2852
+ // returning false, mirroring the never-block contract.
2853
+ function zeroScoreGateAlreadyOffered(roomDir, sessionId) {
2854
+ try {
2855
+ if (!roomDir || !sessionId) return false;
2856
+ const raw = fs.readFileSync(zeroScoreGateMarkerPath(roomDir, sessionId), 'utf8');
2857
+ const parsed = JSON.parse(raw);
2858
+ return !!(parsed && parsed.offered === true);
2859
+ } catch (_e) {
2860
+ return false;
2861
+ }
2862
+ }
2863
+
2864
+ // WR-01 fix companion: writes the dedicated, never-rotated PD-1 marker. Fire-
2865
+ // and-forget (mirrors persistDecisionTrace): a write fault never blocks or
2866
+ // crashes the gate, it only means PD-1 degrades to "may re-fire" instead of
2867
+ // "definitely re-fires" -- strictly no worse than the pre-fix behavior.
2868
+ function markZeroScoreGateOffered(roomDir, sessionId) {
2869
+ try {
2870
+ if (!roomDir || !sessionId) return;
2871
+ const markerPath = zeroScoreGateMarkerPath(roomDir, sessionId);
2872
+ try { fs.mkdirSync(path.dirname(markerPath), { recursive: true }); } catch (_) {}
2873
+ const rnd = Math.random().toString(36).slice(2, 10);
2874
+ const tmpPath = markerPath + '.tmp.' + process.pid + '.' + rnd;
2875
+ fs.writeFileSync(tmpPath, JSON.stringify({ offered: true, ts: new Date().toISOString() }, null, 2));
2876
+ try {
2877
+ fs.renameSync(tmpPath, markerPath);
2878
+ } catch (_e) {
2879
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
2880
+ }
2881
+ } catch (_e) { /* fire-and-forget: never block or crash the gate */ }
2882
+ }
2883
+
2884
+ // Phase 225-01 (SEED-039 proving_case_2): the "no room matched" F.8 Decision Gate.
2885
+ // Distinct from emitBindingGate: on a zero score best.name is corpus[0] (semantically
2886
+ // meaningless, Pitfall 1 / REQ-2), so this gate NEVER presents a scored room. Its three
2887
+ // options are: continue in the session primary (pre-checked default at 0.71, PD-1),
2888
+ // start a new project (-> the __no_room__ sentinel), and dev repo / no room (-> the same
2889
+ // sentinel). Composition clones emitBindingGate step-for-step (renderer, trailer,
2890
+ // side-channel, trace, envelope). Fails open: any renderer / compose fault returns false
2891
+ // and the caller falls to the legacy return 0 silence (REQ-3, the 83-07 never-block
2892
+ // contract). additionalContext carries room SLUGS only, never message content (Part 8).
2893
+ function emitNoMatchGate(args) {
2894
+ const a = (args && typeof args === 'object') ? args : {};
2895
+ const primary = a.primary;
2896
+ const roomDir = a.roomDir;
2897
+ const sessionId = a.sessionId;
2898
+ if (typeof primary !== 'string' || primary.length === 0) return false;
2899
+
2900
+ // Compose the shipped F.8 renderer (lazy require; a load fault degrades to silence).
2901
+ let renderer = null;
2902
+ try {
2903
+ renderer = require(path.join(__dirname, '..', 'lib', 'hmi', 'shape-f8-renderer.cjs'));
2904
+ } catch (_e) {
2905
+ return false;
2906
+ }
2907
+ if (!renderer || typeof renderer.renderShapeF8 !== 'function') return false;
2908
+
2909
+ // Exactly three options, NEVER a scored room (REQ-2). 'continue in <primary>' is the
2910
+ // pre-checked default at 0.71 (PD-1: primary-as-default, the frozen 0.70 + epsilon).
2911
+ // Both 'start a new project' and the no-room label map to the reserved __no_room__
2912
+ // sentinel: picking either unbinds the write from the old primary (PD-5).
2913
+ const continueLabel = 'continue in ' + primary;
2914
+ const newProjectLabel = 'start a new project';
2915
+ const labelToSlug = {};
2916
+ labelToSlug[continueLabel] = primary;
2917
+ labelToSlug[newProjectLabel] = NO_ROOM_SLUG;
2918
+ labelToSlug[NO_ROOM_LABEL] = NO_ROOM_SLUG;
2919
+ const options = [
2920
+ { label: continueLabel, confidence: 0.71 },
2921
+ { label: newProjectLabel, confidence: 0.10 },
2922
+ { label: NO_ROOM_LABEL, confidence: 0.10 },
2923
+ ];
2924
+
2925
+ let rendered = null;
2926
+ try {
2927
+ rendered = renderer.renderShapeF8({
2928
+ options: options,
2929
+ header: '-- mindrianOS -- no room matched -- new project? --',
2930
+ });
2931
+ } catch (_e) {
2932
+ return false;
2933
+ }
2934
+ if (!rendered || !rendered.zones) return false;
2935
+ const zones = rendered.zones;
2936
+
2937
+ // Capture the F.8 footer BEFORE the trailer folds the marker into zones.footer, so the
2938
+ // trailer is appended to additionalContext exactly once (clone of emitBindingGate).
2939
+ const footerBase = (typeof zones.footer === 'string' && zones.footer.length > 0)
2940
+ ? zones.footer : '';
2941
+ try {
2942
+ const dispatcher = require(path.join(__dirname, '..', 'lib', 'hmi', 'selector-dispatcher.cjs'));
2943
+ dispatcher.appendAskUserQuestionTrailer(rendered, 'F.8');
2944
+ } catch (_e) { /* trailer fault: degrade to the untrailed gate, never block */ }
2945
+
2946
+ // Card-fire side-channel: sessionId is a direct arg. A writer fault never affects the
2947
+ // rendered gate (its own try/catch).
2948
+ try {
2949
+ const sidechannel = require(
2950
+ path.join(__dirname, '..', 'lib', 'core', 'card-fire-sidechannel.cjs')
2951
+ );
2952
+ sidechannel.recordReachedGate({
2953
+ sessionId: sessionId,
2954
+ surface: 'scripts/intent-classifier.cjs',
2955
+ shape: 'F.8',
2956
+ subjectText: [zones.header, zones.body].filter(function (s) {
2957
+ return typeof s === 'string' && s.length > 0;
2958
+ }).join(' '),
2959
+ });
2960
+ } catch (_e) {
2961
+ /* never let a side-channel fault affect the rendered gate */
2962
+ }
2963
+
2964
+ const bodyLines = [];
2965
+ if (zones.header) bodyLines.push(zones.header);
2966
+ if (zones.body) bodyLines.push(zones.body);
2967
+ if (zones.signals) bodyLines.push(zones.signals);
2968
+ if (footerBase) bodyLines.push(footerBase);
2969
+ // additionalContext carries room SLUGS only, never the user's message content (Part 8).
2970
+ const guidance =
2971
+ 'No existing room matched this message. The session primary is ' + primary + '. ' +
2972
+ 'Ask the user whether this is a new project: fire the AskUserQuestion card with ' +
2973
+ 'these three options as a single-select ("' + continueLabel + '", "' + newProjectLabel +
2974
+ '", or "' + NO_ROOM_LABEL + '"); never reproduce this block as text (SEED-021). If the ' +
2975
+ 'user confirms a new project, offer /mos:new-project; NEVER auto-create a room (Part 7 ' +
2976
+ 'reuse before build). Selecting "' + newProjectLabel + '" or "' + NO_ROOM_LABEL + '" ' +
2977
+ 'unbinds this write from ' + primary + '. Confirm the choice with the user before ' +
2978
+ 'writing any room artifact.';
2979
+
2980
+ const trailerLines = [];
2981
+ const bindMarker = rendered.askuserquestion_marker;
2982
+ const bindBinding = rendered.askuserquestion_binding;
2983
+ if (typeof bindMarker === 'string' && bindMarker.length > 0) trailerLines.push(bindMarker);
2984
+ if (typeof bindBinding === 'string' && bindBinding.length > 0) trailerLines.push(bindBinding);
2985
+ const additionalContext = bodyLines.join('\n') + '\n\n' + guidance
2986
+ + (trailerLines.length > 0 ? '\n\n' + trailerLines.join('\n') : '');
2987
+
2988
+ const systemMessage = 'no room matched: is this a new project, or continue in ' + primary + '?';
2989
+
2990
+ // Persist the gate payload under the SAME binding_gate_payload key the shipped
2991
+ // consumePriorBindingAnswer scans for (it keys on e.binding_gate_payload, NOT e.kind),
2992
+ // with kind 'zero_score_gate' for trace disambiguation. This trace entry is the
2993
+ // ANSWER-consumption channel (consumePriorBindingAnswer reads it on the very next
2994
+ // turn); it is NOT the PD-1 suppression signal (WR-01 fix -- see
2995
+ // zeroScoreGateAlreadyOffered / markZeroScoreGateOffered above, a dedicated
2996
+ // never-rotated marker, since this shared trace file rotates at 50 entries).
2997
+ try {
2998
+ if (roomDir && sessionId) {
2999
+ persistDecisionTrace(roomDir, sessionId, {
3000
+ ts: new Date().toISOString(),
3001
+ kind: 'zero_score_gate',
3002
+ binding_gate_payload: {
3003
+ options: options.map(function (o) { return o.label; }),
3004
+ labelToSlug: labelToSlug,
3005
+ preChecked: continueLabel,
3006
+ },
3007
+ });
3008
+ }
3009
+ } catch (_e) { /* fire-and-forget: persistence is best-effort */ }
3010
+
3011
+ // WR-01 fix: stamp the dedicated, never-rotated PD-1 marker so
3012
+ // zeroScoreGateAlreadyOffered's suppression survives arbitrarily many further
3013
+ // trace-writing turns in this session (the shared decision-trace file above
3014
+ // does not -- see the comment on zeroScoreGateMarkerPath).
3015
+ try {
3016
+ if (roomDir && sessionId) {
3017
+ markZeroScoreGateOffered(roomDir, sessionId);
3018
+ }
3019
+ } catch (_e) { /* fire-and-forget: never block or crash the gate */ }
3020
+
3021
+ const envelope = {
3022
+ hookSpecificOutput: {
3023
+ hookEventName: 'UserPromptSubmit',
3024
+ additionalContext: additionalContext,
3025
+ },
3026
+ systemMessage: systemMessage,
3027
+ };
3028
+ try {
3029
+ process.stdout.write(JSON.stringify(envelope) + '\n');
3030
+ } catch (_e) {
3031
+ // Last-resort fallback: raw text (never throws back into the hook).
3032
+ try { process.stdout.write(additionalContext + '\n'); } catch (_) {}
3033
+ }
3034
+ return true;
3035
+ }
3036
+
3037
+ function consumePriorBindingAnswer(roomDir, sessionId, currentTurnAnswer, home) {
3038
+ try {
3039
+ if (!roomDir || typeof roomDir !== 'string') return { ok: false, reason: 'no_room' };
3040
+
3041
+ // Read the most recent UN-consumed binding-gate payload. The producer persists it
3042
+ // early in main(), but the engine block appends its OWN trace entry afterward, so
3043
+ // the gate payload is NOT necessarily the last entry -- scan backwards. Stop at a
3044
+ // 'binding_gate_consumed' marker (a prior turn already bound this offer), so the
3045
+ // same answer is never re-consumed into a duplicate write.
3046
+ let gatePayload = null;
3047
+ // CR-01 (Phase 225 REVIEW-FIX): also capture which gate KIND produced this
3048
+ // payload. 'zero_score_gate' (emitNoMatchGate) offers exactly three fixed
3049
+ // labels (continue-in-primary / new-project / no-room) and NEVER lists the
3050
+ // session's other bound rooms as toggleable options (REQ-2: never a scored
3051
+ // room) -- unlike the pre-existing 'binding_gate' (emitBindingGate), whose
3052
+ // option set is the full scored corpus, giving the user visibility/control
3053
+ // over every room already in scope. That narrower option set is what makes
3054
+ // the zero-score gate's answer-consumption path need the union fix below.
3055
+ let gateKind = null;
3056
+ try {
3057
+ const filePath = path.join(roomDir, '.mindrian', 'decision-traces', sessionId + '.json');
3058
+ const raw = fs.readFileSync(filePath, 'utf8');
3059
+ const parsed = JSON.parse(raw);
3060
+ if (parsed && Array.isArray(parsed.traces)) {
3061
+ for (let i = parsed.traces.length - 1; i >= 0; i--) {
3062
+ const e = parsed.traces[i];
3063
+ if (!e || typeof e !== 'object') continue;
3064
+ if (e.kind === 'binding_gate_consumed') break; // already consumed -> no-op
3065
+ if (e.binding_gate_payload && typeof e.binding_gate_payload === 'object') {
3066
+ gatePayload = e.binding_gate_payload;
3067
+ gateKind = (typeof e.kind === 'string') ? e.kind : null;
3068
+ break;
3069
+ }
3070
+ }
3071
+ }
3072
+ } catch (_) { gatePayload = null; gateKind = null; }
3073
+
3074
+ // Non-gate / cold turn: no prior gate payload -> no-op (byte-unchanged).
3075
+ if (!gatePayload || !Array.isArray(gatePayload.options) || gatePayload.options.length === 0) {
3076
+ return { ok: false, reason: 'no_gate_payload' };
3077
+ }
3078
+
3079
+ // Lazy-require the shipped capture adapter + the net-new sink consumer (a load
3080
+ // fault degrades to a no-op).
3081
+ let captureMod;
3082
+ let consumerMod;
3083
+ try {
3084
+ captureMod = require(path.join(__dirname, '..', 'lib', 'hmi', 'f8-action-capture-cli.cjs'));
3085
+ consumerMod = require(path.join(__dirname, '..', 'lib', 'workflow', 'session-binding-consumer.cjs'));
3086
+ } catch (_) {
3087
+ return { ok: false, reason: 'consumer_unavailable' };
3088
+ }
3089
+ if (!captureMod || typeof captureMod.captureCliActionSet !== 'function'
3090
+ || !consumerMod || typeof consumerMod.consumeSessionBinding !== 'function') {
3091
+ return { ok: false, reason: 'consumer_unavailable' };
3092
+ }
3093
+
3094
+ // Normalize the answer into the { selectedOptions:[...] } shape captureCliActionSet
3095
+ // accepts. A structured AskUserQuestion answer passes through; a raw-text turn maps
3096
+ // to a single selectedOption (best-effort, mirrors consumePriorF1Pick).
3097
+ let answer = currentTurnAnswer;
3098
+ if (typeof currentTurnAnswer === 'string') {
3099
+ try {
3100
+ const p = JSON.parse(currentTurnAnswer);
3101
+ answer = (p && typeof p === 'object') ? p : { selectedOptions: [currentTurnAnswer] };
3102
+ } catch (_) {
3103
+ answer = { selectedOptions: [currentTurnAnswer] };
3104
+ }
3105
+ }
3106
+ if (!answer || !Array.isArray(answer.selectedOptions)) {
3107
+ return { ok: false, reason: 'no_selection' };
3108
+ }
3109
+
3110
+ // COMPOSE captureCliActionSet verbatim: the gate's option labels are the "verbs".
3111
+ const picks = captureMod.captureCliActionSet(answer, { verbs: gatePayload.options });
3112
+ const labelToSlug = (gatePayload.labelToSlug && typeof gatePayload.labelToSlug === 'object')
3113
+ ? gatePayload.labelToSlug : {};
3114
+
3115
+ // Translate the confirmed labels back to room slugs (the no-room label -> the
3116
+ // reserved sentinel). A rejected toggle is dropped from the bound set.
3117
+ const slugs = [];
3118
+ for (const pk of picks) {
3119
+ if (!pk || typeof pk.verb !== 'string' || pk.verb.length === 0) continue;
3120
+ if (pk.outcome === 'reject') continue;
3121
+ const slug = (typeof labelToSlug[pk.verb] === 'string') ? labelToSlug[pk.verb] : pk.verb;
3122
+ if (slugs.indexOf(slug) === -1) slugs.push(slug);
3123
+ }
3124
+ if (slugs.length === 0) return { ok: false, reason: 'empty_confirm' };
3125
+
3126
+ // CR-01 fix (Phase 225 REVIEW-FIX, root cause): consumeSessionBinding's sink
3127
+ // WRITE is a full REPLACE of the session's `bound` array, never a union
3128
+ // (session-binding-consumer.cjs:144-148). That is safe for the pre-existing
3129
+ // 'binding_gate' (Phase 194): its option set is the FULL scored corpus, so
3130
+ // every already-bound room is a visible, re-toggleable option and the
3131
+ // navigator controls what survives. The NEW 'zero_score_gate' (Phase 225)
3132
+ // never offers that visibility -- it renders exactly three fixed labels
3133
+ // (continue-in-primary / new-project / no-room) and NEVER lists sibling
3134
+ // bound rooms at all. So the single most natural answer ("continue in
3135
+ // <primary>") was silently REPLACING a multi-room `bound` set with just
3136
+ // [primary], un-stickying the session in the same stroke -- a room the
3137
+ // session was already bound to (e.g. room-b) became hard-blocked on its
3138
+ // next write (scripts/write-scope-check.cjs) with no warning the gate
3139
+ // answer would do this. Fix: for this gate kind only, union the confirmed
3140
+ // picks with the PRIOR bound set before the sink's replace-write, so
3141
+ // answering this gate can only ADD/confirm rooms, never silently drop one.
3142
+ let priorBindingForZeroScore = null;
3143
+ if (gateKind === 'zero_score_gate') {
3144
+ try {
3145
+ const sb = require(path.join(__dirname, '..', 'lib', 'core', 'session-binding.cjs'));
3146
+ priorBindingForZeroScore = sb.readSessionBinding(sessionId, { home: home });
3147
+ const priorBound = Array.isArray(priorBindingForZeroScore.bound)
3148
+ ? priorBindingForZeroScore.bound : [];
3149
+ for (const s of priorBound) {
3150
+ if (slugs.indexOf(s) === -1) slugs.push(s);
3151
+ }
3152
+ } catch (_e) { /* fail-open: union is best-effort, never blocks the bind */ }
3153
+ }
3154
+
3155
+ // Primary + sticky: honor explicit answer fields, else default the primary to the
3156
+ // first confirmed slug.
3157
+ let primaryPick = null;
3158
+ if (typeof answer.primary === 'string' && answer.primary.length > 0) {
3159
+ primaryPick = (typeof labelToSlug[answer.primary] === 'string')
3160
+ ? labelToSlug[answer.primary] : answer.primary;
3161
+ }
3162
+ if (!primaryPick) primaryPick = slugs[0];
3163
+ // CR-01 fix: the zero-score gate's card has no "remember for this session"
3164
+ // toggle at all (unlike the binding_gate card, whose guidance text explicitly
3165
+ // asks for one), so answer.sticky is structurally always undefined for this
3166
+ // gate kind -- defaulting it to false on every answer would silently
3167
+ // un-sticky an already-sticky multi-room session. Preserve the prior sticky
3168
+ // value in that case instead of stomping it to false.
3169
+ let sticky = answer.sticky === true;
3170
+ if (gateKind === 'zero_score_gate' && typeof answer.sticky === 'undefined') {
3171
+ sticky = !!(priorBindingForZeroScore && priorBindingForZeroScore.sticky === true);
3172
+ }
3173
+
3174
+ // Route to the net-new SINK: consumeSessionBinding WRITES the session binding file.
3175
+ const res = consumerMod.consumeSessionBinding({
3176
+ picks: slugs,
3177
+ primaryPick: primaryPick,
3178
+ sticky: sticky,
3179
+ sessionId: sessionId,
3180
+ home: home,
3181
+ });
3182
+
3183
+ // Clear the consumed gate payload so the same answer is not re-consumed next turn
3184
+ // (append a trace entry WITHOUT the payload -> it is no longer the last entry's).
3185
+ try {
3186
+ persistDecisionTrace(roomDir, sessionId, {
3187
+ ts: new Date().toISOString(),
3188
+ kind: 'binding_gate_consumed',
3189
+ });
3190
+ } catch (_e) { /* fire-and-forget */ }
3191
+
3192
+ return res;
3193
+ } catch (_e) {
3194
+ // Best-effort: a fault is a no-op, never a throw (the never-block contract).
3195
+ return { ok: false, reason: 'consumer_threw' };
3196
+ }
3197
+ }
3198
+
3199
+ // Phase 150-06 (D-08): export the engine-decision render helpers so the
3200
+ // render-unlock suite can drive the dial wiring without self-executing the hook.
3201
+ // The self-execution below is gated on require.main === module so a require()
3202
+ // from a test (or any consumer) loads the module without firing the hook.
3203
+ // Phase 159-02 (HOW-4): export consumePriorF1Pick so the turn-start wiring suite
3204
+ // can drive the consumer without spawning the whole hook.
3205
+ // Phase 194-04 (PSB-04): export the F.8 binding-gate producer + answer consumer.
3206
+ module.exports = {
3207
+ renderEngineDecisionWithDial: renderEngineDecisionWithDial,
3208
+ // Phase 251-01 (CACHE-02a): suppress-when-unchanged. NAV_UNCHANGED_MARKER
3209
+ // and navBlockHashPath are the plan's mandated exports; readNavBlockHash /
3210
+ // writeNavBlockHash are exported additionally so the sidecar's fail-open
3211
+ // and roundtrip behavior is directly testable without a second reimplementation.
3212
+ NAV_UNCHANGED_MARKER: NAV_UNCHANGED_MARKER,
3213
+ navBlockHashPath: navBlockHashPath,
3214
+ readNavBlockHash: readNavBlockHash,
3215
+ writeNavBlockHash: writeNavBlockHash,
3216
+ // Phase 251-02 (CACHE-03): the exported block-size budget + fence anchor.
3217
+ NAV_BLOCK_BUDGET_BYTES: NAV_BLOCK_BUDGET_BYTES,
3218
+ // Phase 245-08 (Requirement 1): the ONE dial score composition, exported so
3219
+ // the two acceptance regressions exercise the PRODUCTION path rather than a
3220
+ // reimplementation of it in a fixture (245-RESEARCH.md Pitfall 1: a test that
3221
+ // can pass without scripts/intent-classifier.cjs being touched is testing the
3222
+ // wrong thing).
3223
+ composeDialReachScores: composeDialReachScores,
3224
+ buildDialSlotContext: buildDialSlotContext,
3225
+ formatEngineDecisionBlock: formatEngineDecisionBlock,
3226
+ consumePriorF1Pick: consumePriorF1Pick,
3227
+ emitBindingGate: emitBindingGate,
3228
+ consumePriorBindingAnswer: consumePriorBindingAnswer,
3229
+ // reach-gate-stale-turn-input: exported so the regression can assert the CURRENT
3230
+ // turn (STDIN_MESSAGE) is threaded into the LOCAL routing seed (never the Brain).
3231
+ deriveConversationSeed: deriveConversationSeed,
3232
+ // reach-sensor-relevance-gap A1: exported so the key-alignment regression can
3233
+ // assert the CLI read key derives from the hook payload session_id (the MCP
3234
+ // room_bind write UUID), not the sha256 hash fallback.
3235
+ extractSessionId: extractSessionId,
3236
+ resolveSessionId: resolveSessionId,
3237
+ };
3238
+
3239
+ if (require.main === module) {
3240
+ try {
3241
+ const injectionStart = Date.now();
3242
+ const code = main();
3243
+ if (injectionEnabled()) {
3244
+ try {
3245
+ injectGraphFindings(injectionStart);
3246
+ } catch (_) {
3247
+ // Fail-silent: never disrupt the prompt.
3248
+ }
3249
+ }
3250
+ // Phase 91-02 navigation engine block (always LAST so the decision
3251
+ // appears below any Phase 83 / Phase 84 emissions in additionalContext).
3252
+ // Skipped on empty stdin to preserve Phase 83's silent-exit contract;
3253
+ // the engine has no useful turn to decide on an empty message.
3254
+ let navP = Promise.resolve(null);
3255
+ try {
3256
+ if (STDIN_MESSAGE && STDIN_MESSAGE.length > 0) {
3257
+ // RCA resolve-active-room-cross-session-bleed (D1). This is THE site that
3258
+ // made the F.1 Decision Gate card cite a foreign room: roomDir flows into
3259
+ // runNavigationEngine -> sensorCtx.roomDir and into buildDialSlotContext,
3260
+ // whose slots.header_room is literally path.basename(roomDir). Taken from
3261
+ // the machine-wide resolver, an unrelated concurrent session's room became
3262
+ // this session's card header, and this session's decision traces were
3263
+ // written into that stranger's room directory (Canon Part 9 misattribution).
3264
+ // The machine-wide value is still resolved first, but ONLY to seed
3265
+ // resolveSessionId's hash fallback exactly as before and to serve as the
3266
+ // unbound-session fallback.
3267
+ //
3268
+ // RCA registry-active-session-unbound-inheritance: this site (and ONLY this
3269
+ // site) opts into the ownership gate. D1 fixed the BOUND half -- a session
3270
+ // with a session.primary now resolves its own room. This closes the UNBOUND
3271
+ // half: when reg.active was set by a DIFFERENT session whose process is
3272
+ // still running, roomDir comes back null and the whole F.1 block is skipped,
3273
+ // so no card claims the stranger's room and no decision trace lands in it.
3274
+ // Every uncertain case (no ownership recorded, owner is me, owner's process
3275
+ // is gone, no session id, any fault) still inherits exactly as before, which
3276
+ // is what keeps the tier-0 single-session user whole.
3277
+ const machineWideDir = resolveActiveRoomDir();
3278
+ const sessionId = machineWideDir ? resolveSessionId(machineWideDir) : null;
3279
+ const roomDir = machineWideDir
3280
+ ? resolveSessionRoomDir(sessionId, machineWideDir, { requireOwnership: true })
3281
+ : null;
3282
+ if (roomDir) {
3283
+ // Phase 159-02 (HOW-4, DCW-01/04): the turn-start consumer. Fire BEFORE the
3284
+ // new turn's dial render, reading the PRIOR turn's persisted f1_closer_payload
3285
+ // and routing the navigator's pick through the navigation-chokepoint-owned
3286
+ // consumer. Best-effort by construction (consumePriorF1Pick never throws and
3287
+ // returns a structured no-op on every non-dial / cold / faulted turn), so a
3288
+ // turn with no prior dial offer is BYTE-UNCHANGED from today (DCW-04). The raw
3289
+ // STDIN_MESSAGE rides ONLY the LOCAL lane (captureCliPick confines it to the
3290
+ // optional sentence); the selected verb is the deterministic enum match.
3291
+ try {
3292
+ consumePriorF1Pick(roomDir, sessionId, {
3293
+ selectedOption: STDIN_MESSAGE,
3294
+ text: STDIN_MESSAGE,
3295
+ });
3296
+ } catch (_) { /* fire-and-forget: never disrupt the prompt (DCW-04) */ }
3297
+ // Phase 194-04 (PSB-05): the F.8 binding-gate ANSWER consumer. Fire BEFORE the
3298
+ // engine block persists its own trace (so the prior binding_gate_payload is
3299
+ // still the last trace entry), reading the PRIOR turn's persisted gate offer and
3300
+ // routing the confirmed set through captureCliActionSet -> consumeSessionBinding
3301
+ // (the net-new session-file SINK). Best-effort: a non-gate / cold turn is a
3302
+ // no-op, never a throw (PSB-06, the never-block contract).
3303
+ try {
3304
+ consumePriorBindingAnswer(roomDir, sessionId, STDIN_MESSAGE);
3305
+ } catch (_) { /* fire-and-forget: never disrupt the prompt (PSB-06) */ }
3306
+ navP = emitEngineDecisionBlock(roomDir, sessionId).then(function (out) {
3307
+ if (!out || !out.decision) return null;
3308
+ // Phase 91-03: compose engine decision with legacy file-state +
3309
+ // env activation through the skill-activation-router. The router
3310
+ // enforces Canon Part 3 closed 10-verb vocabulary on engine
3311
+ // outputs and falls back to legacy when engine is silent.
3312
+ // Lazy-require so missing module degrades gracefully (the engine
3313
+ // block still emits with routing absent).
3314
+ let routing = null;
3315
+ try {
3316
+ const router = require(
3317
+ path.join(__dirname, '..', 'lib', 'core', 'skill-activation-router.cjs')
3318
+ );
3319
+ const legacyActivation = computeLegacyActivation(roomDir);
3320
+ routing = router.routeActivation(out.decision, legacyActivation);
3321
+ } catch (_e) {
3322
+ routing = null;
3323
+ }
3324
+
3325
+ // Phase 91-04: offer presenter integration.
3326
+ //
3327
+ // Step 1 (close the ignore-loop on the previous turn): if the
3328
+ // most recent history entry is 'shown' for this same session,
3329
+ // reclassify it as 'acted' (user invoked the suggested command)
3330
+ // or 'ignored' (user did not). This is the simple Wave-1
3331
+ // heuristic from PLAN: classifyTurnOutcome on the previous
3332
+ // 'shown' record using the current STDIN_MESSAGE.
3333
+ //
3334
+ // Step 2 (present this turn's offer if any): call presentOffer
3335
+ // and, when an offer renders, record a 'shown' outcome so the
3336
+ // next turn can reclassify it. If the engine returned null
3337
+ // offer or grounding rejected it, no shown record is written.
3338
+ //
3339
+ // Lazy-require so missing module degrades gracefully (the
3340
+ // engine + routing block still emits with no Offer line).
3341
+ let offerLine = null;
3342
+ // Phase 135-03: the F.1 Next-Move closer payload (built when an offer
3343
+ // survives grounding). Persisted to the trace so /mos:explain-decision and
3344
+ // the consumer surface render the locked F.1 selector with the Free-Text
3345
+ // escape. Null when no offer renders.
3346
+ let f1Payload = null;
3347
+ try {
3348
+ const presenter = require(
3349
+ path.join(__dirname, '..', 'lib', 'core', 'offer-presenter.cjs')
3350
+ );
3351
+
3352
+ // Step 1: reclassify previous 'shown' record (if any) using
3353
+ // the user's current message as the acted/ignored signal.
3354
+ try {
3355
+ const history = presenter.readOfferHistory(roomDir);
3356
+ const arr = (history && Array.isArray(history.history)) ? history.history : [];
3357
+ // Walk backwards to find the most recent entry for this
3358
+ // session; reclassify if and only if its outcome is 'shown'.
3359
+ for (let i = arr.length - 1; i >= 0; i -= 1) {
3360
+ const e = arr[i];
3361
+ if (!e || typeof e !== 'object') continue;
3362
+ if (e.session_id === sessionId && e.outcome === 'shown') {
3363
+ const newOutcome = presenter.classifyTurnOutcome(
3364
+ { command: e.command }, STDIN_MESSAGE
3365
+ );
3366
+ // In-place update + atomic rewrite.
3367
+ e.outcome = newOutcome;
3368
+ try {
3369
+ const fp = path.join(roomDir, '.mindrian', 'offer-history.json');
3370
+ const tmpDir = path.join(roomDir, '.mindrian');
3371
+ try { fs.mkdirSync(tmpDir, { recursive: true }); } catch (_) {}
3372
+ const rnd = Math.random().toString(36).slice(2, 10);
3373
+ const tmpPath = fp + '.tmp.' + process.pid + '.' + rnd;
3374
+ fs.writeFileSync(tmpPath, JSON.stringify(history, null, 2));
3375
+ fs.renameSync(tmpPath, fp);
3376
+ } catch (_) { /* best-effort reclassification */ }
3377
+ break;
3378
+ }
3379
+ // Stop walking once we hit any non-'shown' entry for this
3380
+ // session (older turns are already classified).
3381
+ if (e.session_id === sessionId) break;
3382
+ }
3383
+ } catch (_) { /* ignore-loop is best-effort */ }
3384
+
3385
+ // Step 2: present this turn's offer (if engine produced one).
3386
+ let history2;
3387
+ try { history2 = presenter.readOfferHistory(roomDir); } catch (_) {
3388
+ history2 = { version: 1, history: [] };
3389
+ }
3390
+ const sessionCtx = { offeredThisTurn: false, sessionId: sessionId };
3391
+ let presented;
3392
+ try {
3393
+ presented = presenter.presentOffer(out.decision, history2, sessionCtx);
3394
+ } catch (_) { presented = null; }
3395
+ if (presented && typeof presented.offerLine === 'string' && presented.offerLine.length > 0) {
3396
+ offerLine = presented.offerLine;
3397
+ // Record 'shown' so next turn can reclassify.
3398
+ try {
3399
+ const offer = (out.decision && out.decision.offer_next_step) || {};
3400
+ presenter.recordOfferOutcome(roomDir, {
3401
+ outcome: 'shown',
3402
+ session_id: sessionId,
3403
+ command: typeof offer.command === 'string' ? offer.command : null,
3404
+ reason: typeof offer.reason === 'string' ? offer.reason : null,
3405
+ });
3406
+ } catch (_) { /* fire-and-forget */ }
3407
+
3408
+ // Phase 135-03: the reliable F.1 Next-Move closer.
3409
+ //
3410
+ // The offer survived grounding + suppression + cap (offerLine is
3411
+ // non-empty). Fire the F.1 Next-Move selector for it via the SHIPPED
3412
+ // selector-dispatcher pickShape({requestedShape:'F.1'}) (the
3413
+ // cross-surface AskUserQuestion primitive) so
3414
+ // the offer becomes a CHOICE with the Free-Text escape (the 10th
3415
+ // canonical verb) -- never a bespoke prompt string (the
3416
+ // test-no-bespoke-brain-prompts.sh tripwire). The rendered F.1 surface
3417
+ // is persisted to the decision trace so the consumer surface (Larry on
3418
+ // CLI / Desktop / Cowork) renders the locked selector identically (SC8;
3419
+ // no surface-specific branch).
3420
+ //
3421
+ // The pick lands on the NEXT turn (this hook emits additionalContext;
3422
+ // it does not block on a user answer). When the user picks, the same
3423
+ // closer.closeOffer routes it: accept/defer/reject -> a typed decision
3424
+ // edge via recordSelectorDecision; Free-Text -> a miss memory_event via
3425
+ // recordSelectorMiss (user_intent stays LOCAL per Part 8). When an
3426
+ // accepted offer files an artifact, the closer injects the wikilink
3427
+ // footer idempotently via the Phase 76 injector
3428
+ // wikilink-builder.injectFiledToFooter (dedupes via content.includes;
3429
+ // SC5 -- the decision edge carries the [[wikilink]] reason as
3430
+ // provenance). A reject
3431
+ // writes the f_selector_decision row the resolver's shouldExclude reads
3432
+ // next turn -- the SC6 backoff loop closes through this pair (the
3433
+ // offer-closer.test.cjs backoff-persistence case proves it). The closer
3434
+ // NEVER opens room.db itself (substrate guard): the consumer populates
3435
+ // roomState.db via the navigation chokepoint when recording the pick.
3436
+ //
3437
+ // Lazy-require + try/catch: a missing closer module degrades gracefully
3438
+ // -- the offerLine alone still emits (no crash, no surface branch).
3439
+ try {
3440
+ const closer = require(
3441
+ path.join(__dirname, '..', 'lib', 'workflow', 'offer-closer.cjs')
3442
+ );
3443
+ const offerForF1 = (out.decision && out.decision.offer_next_step) || {};
3444
+ const decisionTrace = (out.decision && out.decision.decision_trace) || null;
3445
+ // Phase 158-01 (RJP-07 / SC-02): the two-turn offer->close keying
3446
+ // pin. The reach_id that GROUNDED this offer is named in the LOCAL
3447
+ // decision trace (context_assembly.decision_grounding -- a frozen
3448
+ // reach_id when a sensor reach drove the fire, else 'wicked' /
3449
+ // 'brain_verb' / 'neighborhood' / null). Map it onto the offer's
3450
+ // command verb so buildF1Payload carries a parallel per-verb reachIds
3451
+ // map; the persisted f1_closer_payload then survives the turn boundary
3452
+ // and the next-turn pick routes back to closeReach keyed by reach_id.
3453
+ // Non-reach groundings ('wicked'/'brain_verb'/...) are forwarded as-is
3454
+ // and dropped by the recordSelectorDecision enum-gate downstream, so
3455
+ // only a real REACH_IDS member ever lands on the persisted payload.
3456
+ let reachIdByVerb = null;
3457
+ try {
3458
+ const grounding = decisionTrace
3459
+ && decisionTrace.context_assembly
3460
+ && decisionTrace.context_assembly.decision_grounding;
3461
+ const offerCmd = (offerForF1 && typeof offerForF1.command === 'string')
3462
+ ? offerForF1.command : null;
3463
+ if (typeof grounding === 'string' && grounding.length > 0 && offerCmd) {
3464
+ reachIdByVerb = {};
3465
+ reachIdByVerb[offerCmd] = grounding;
3466
+ }
3467
+ } catch (_) { reachIdByVerb = null; }
3468
+ const rendered = closer.renderF1(offerForF1, {
3469
+ roomDir: roomDir,
3470
+ operator: operatorState,
3471
+ decisionTrace: decisionTrace,
3472
+ reachIdByVerb: reachIdByVerb,
3473
+ });
3474
+ if (rendered && rendered.payload) {
3475
+ f1Payload = rendered.payload;
3476
+ // FIX-05 (Phase 150.6-03): carry the LOCAL conversation seed on
3477
+ // the persisted F.1 payload so the consumer surface forwards it
3478
+ // as closeOffer({ sentence }) on the NEXT turn (the seam where
3479
+ // the pick is routed to recordSelectorDecision). The closer
3480
+ // classifies it at the write seam and stores ONLY the scalar
3481
+ // boolean + source enum; the sentence never enters any payload
3482
+ // (Canon Part 8). This is the LOCAL routing lane only -- the
3483
+ // seed is NEVER threaded toward buildBrainPacket (D-03a fence).
3484
+ if (context.conversationSeed) {
3485
+ f1Payload.sentence = context.conversationSeed;
3486
+ }
3487
+ // Phase 159-02 (DCW-01): carry the offer's GENERIC framework handle
3488
+ // onto the persisted payload so the turn-start consumer
3489
+ // (consumePriorF1Pick, next turn) can complete the matched verb's
3490
+ // offer scaffold and write the f_selector_decision edge whose
3491
+ // target_id is 'framework:' + framework (selector-decisions.cjs:277).
3492
+ // The framework is a generic methodology name (never user content,
3493
+ // Canon Part 8); this is the IDENTICAL additive post-build mutation
3494
+ // idiom as the FIX-05 sentence carry directly above (the persist
3495
+ // mechanism + the reachIds/verbs keying are untouched). Without it the
3496
+ // accept/defer/reject row cannot be written and the dial loop stays
3497
+ // dark, so this is a load-bearing additive field.
3498
+ if (typeof offerForF1.framework === 'string' && offerForF1.framework.length > 0) {
3499
+ f1Payload.framework = offerForF1.framework;
3500
+ }
3501
+ }
3502
+ } catch (_) { /* closer is best-effort; offerLine still emits */ }
3503
+ }
3504
+ } catch (_e) {
3505
+ offerLine = null;
3506
+ }
3507
+
3508
+ // Compose trace entry. Persist routing source/notes alongside
3509
+ // engine trace so /mos:explain-decision (Plan 91-05) surfaces
3510
+ // which activation set won.
3511
+ const turnNo = appendTraceTurnNumber(roomDir, sessionId);
3512
+ const baseTrace = (out.decision && out.decision.decision_trace) || {};
3513
+ const traceEntry = Object.assign({}, baseTrace, {
3514
+ turn: turnNo,
3515
+ at: new Date().toISOString(),
3516
+ elapsed_ms: out.elapsed_ms,
3517
+ });
3518
+ if (routing && typeof routing === 'object') {
3519
+ traceEntry.routing_source = routing.source;
3520
+ traceEntry.routing_reason = routing.reason;
3521
+ traceEntry.routing_activated_skills = routing.activated_skills;
3522
+ traceEntry.routing_suppressed_skills = routing.suppressed_skills;
3523
+ if (Array.isArray(routing.trace_notes) && routing.trace_notes.length > 0) {
3524
+ traceEntry.routing_trace_notes = routing.trace_notes;
3525
+ }
3526
+ }
3527
+ // Phase 91-04: persist the rendered offer line (or null) into
3528
+ // the decision trace for /mos:explain-decision (Plan 91-05).
3529
+ if (typeof offerLine === 'string' && offerLine.length > 0) {
3530
+ traceEntry.offer_rendered = offerLine;
3531
+ }
3532
+ // Phase 135-03: persist the F.1 closer payload (verbs ending with the
3533
+ // Free-Text escape + the Mode A recommendedVerb marker) so the consumer
3534
+ // surface renders the locked selector and the pick on the next turn routes
3535
+ // back through closer.closeOffer.
3536
+ if (f1Payload && Array.isArray(f1Payload.verbs) && f1Payload.verbs.length > 0) {
3537
+ traceEntry.f1_closer_payload = f1Payload;
3538
+ }
3539
+ // Emit additionalContext block. Phase 150-06 (D-08 render unlock): on the
3540
+ // engine arm, surface buildReachList -> dial-presenter onto the live
3541
+ // response block, grounded by the projected cortex (the legD cortex_nodes
3542
+ // threaded out on the LOCAL routing lane). Best-effort: the render never
3543
+ // blocks the turn (renderEngineDecisionWithDial wraps + degrades).
3544
+ // 150.5-02 (DIAL-ATOM-01): the render runs BEFORE persistDecisionTrace
3545
+ // so a dial_render_fault note (ctx.onRenderNote) merges into the SAME
3546
+ // persisted decision trace routing_trace_notes already rides (the
3547
+ // shipped Phase-91 surface; zero new write paths, threat T-150.5-07).
3548
+ //
3549
+ // Phase 251-01 (CACHE-02a, suppress-when-unchanged): read the PRIOR
3550
+ // turn's block hash (fail-open to null on any read fault -- a null
3551
+ // prevBlockHash always yields full emission) unless the operator has
3552
+ // set the kill switch MINDRIAN_NAV_NO_SUPPRESS, in which case the
3553
+ // sidecar is simply never read and every turn renders in full. The
3554
+ // render seam captures {hash, bytes} via onBlockHash so this closure
3555
+ // can persist them onto the SAME decision trace + write the sidecar
3556
+ // AFTER the block is composed, without adding a second render pass.
3557
+ const prevNavHash = process.env.MINDRIAN_NAV_NO_SUPPRESS
3558
+ ? null
3559
+ : readNavBlockHash(roomDir, sessionId);
3560
+ let navBlockHashLocal = null;
3561
+ let navBlockBytesLocal = 0;
3562
+ const block = renderEngineDecisionWithDial(
3563
+ out.decision, routing, offerLine, {
3564
+ cortexNodes: out.cortex_nodes,
3565
+ // Phase 209-01 (E3): the live room context + active room dir, so the
3566
+ // dial slot map resolves {topic}/{room_name} from live nodes.
3567
+ roomContext: out.room_context,
3568
+ roomDir: roomDir,
3569
+ // Phase 209-06 (H3): threaded so the engine-arm PRIMARY side-channel
3570
+ // producer can key its record by session (sessionId is already in
3571
+ // scope in this closure via resolveSessionId(roomDir) above).
3572
+ sessionId: sessionId,
3573
+ // Phase 158-03 (SC-07): the upstream-computed bounded reject penalty +
3574
+ // hard-suppression set, folded on the live arm (db open) and threaded
3575
+ // out on the resolved object. The render seam merges discountedScores
3576
+ // over reachScores + passes suppressedReachIds into the PURE
3577
+ // buildReachList. Absent/empty -> un-discounted byte-identical render.
3578
+ reach_penalties: out.reach_penalties,
3579
+ onRenderNote: function (note) { traceEntry.dial_render_note = note; },
3580
+ prevBlockHash: prevNavHash,
3581
+ onBlockHash: function (hash, bytes) {
3582
+ navBlockHashLocal = hash;
3583
+ navBlockBytesLocal = bytes;
3584
+ },
3585
+ }
3586
+ );
3587
+ // Forensics for /mos:explain-decision: the hash + byte count of THIS
3588
+ // turn's rendered block, and whether it suppressed to the marker.
3589
+ // Absent (navBlockHashLocal null) on the empty-block bypass, which
3590
+ // never reaches this line's traceEntry fields.
3591
+ if (navBlockHashLocal) {
3592
+ traceEntry.nav_block_sha256 = navBlockHashLocal;
3593
+ traceEntry.nav_block_bytes = navBlockBytesLocal;
3594
+ traceEntry.nav_suppressed = (block === NAV_UNCHANGED_MARKER);
3595
+ }
3596
+ persistDecisionTrace(roomDir, sessionId, traceEntry);
3597
+ if (block && block.length > 0) {
3598
+ try { process.stdout.write(block + '\n'); } catch (_) {}
3599
+ }
3600
+ // Idempotent on a suppressed turn (writes the SAME hash back).
3601
+ if (navBlockHashLocal) {
3602
+ writeNavBlockHash(roomDir, sessionId, navBlockHashLocal, navBlockBytesLocal);
3603
+ }
3604
+ return null;
3605
+ }).catch(function () { return null; });
3606
+ }
3607
+ }
3608
+ } catch (_) { /* engine block is fire-and-forget */ }
3609
+ // Wait for engine to settle (bounded by NAV_HARD_TIMEOUT_MS) before
3610
+ // exiting so the additionalContext write lands before stdout closes.
3611
+ navP.then(function () {
3612
+ process.exit(typeof code === 'number' ? code : 0);
3613
+ }, function () {
3614
+ process.exit(typeof code === 'number' ? code : 0);
3615
+ });
3616
+ } catch (_) {
3617
+ // Fail-silent on any unexpected error. Classifier is advisory.
3618
+ process.exit(0);
3619
+ }
3620
+ }