@mindrian_os/cli 2.0.0-beta.3 → 2.0.0-beta.31
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.
- package/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +14 -0
- package/CHANGELOG.md +467 -0
- package/agents/analogy-query-fetcher.md +119 -0
- package/agents/brain-query.md +91 -0
- package/agents/competitor-watch-fetcher.md +94 -0
- package/agents/framework-runner.md +258 -0
- package/agents/grading.md +216 -0
- package/agents/grant-reviewer.md +191 -0
- package/agents/investor.md +141 -0
- package/agents/larry-extended.md +215 -0
- package/agents/meeting-perspective-extractor.md +94 -0
- package/agents/opportunity-scanner.md +105 -0
- package/agents/persona-analyst.md +201 -0
- package/agents/research.md +103 -0
- package/agents/reverse-salient-agent.md +42 -0
- package/agents/vault-section-reviewer.md +107 -0
- package/assets/banner-showcase.html +537 -0
- package/assets/fonts/BebasNeue-Regular.ttf +0 -0
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/logo.svg +10 -0
- package/bin/local-chain-recommender.cjs +256 -0
- package/bin/mindrian-brain-mcp-client.cjs +319 -0
- package/bin/mindrian-mcp-server.cjs +445 -0
- package/bin/mindrian-mcp-shim.cjs +100 -0
- package/bin/mindrian-tools.cjs +784 -0
- package/commands/act.md +529 -0
- package/commands/admin.md +439 -0
- package/commands/agentshield.md +49 -0
- package/commands/analyze-needs.md +78 -0
- package/commands/analyze-systems.md +73 -0
- package/commands/analyze-timing.md +98 -0
- package/commands/auto-explore.md +85 -0
- package/commands/beautiful-question.md +76 -0
- package/commands/bono.md +152 -0
- package/commands/brain-derive.md +137 -0
- package/commands/build-knowledge.md +121 -0
- package/commands/build-thesis.md +94 -0
- package/commands/causal.md +269 -0
- package/commands/challenge-assumptions.md +78 -0
- package/commands/compare-ventures.md +117 -0
- package/commands/correct-reference-now.md +88 -0
- package/commands/dashboard.md +144 -0
- package/commands/deck.md +117 -0
- package/commands/deep-grade.md +214 -0
- package/commands/diagnose.md +131 -0
- package/commands/diagnostics.md +195 -0
- package/commands/dial-memory-refresh.md +113 -0
- package/commands/diffusion.md +129 -0
- package/commands/discover.md +221 -0
- package/commands/doctor.md +271 -0
- package/commands/dogfood-flush.md +130 -0
- package/commands/dominant-designs.md +76 -0
- package/commands/eureka.md +315 -0
- package/commands/explain-decision.md +135 -0
- package/commands/explore-domains.md +78 -0
- package/commands/explore-futures.md +67 -0
- package/commands/explore-opportunity.md +172 -0
- package/commands/explore-trends.md +78 -0
- package/commands/export.md +128 -0
- package/commands/feynman-timeline-refresh.md +102 -0
- package/commands/file-meeting.md +1195 -0
- package/commands/find-analogies.md +369 -0
- package/commands/find-bottlenecks.md +108 -0
- package/commands/find-connections.md +111 -0
- package/commands/funding.md +114 -0
- package/commands/futures.md +128 -0
- package/commands/grade-grant.md +297 -0
- package/commands/grade.md +257 -0
- package/commands/graph.md +218 -0
- package/commands/hat-briefing.md +163 -0
- package/commands/heal.md +72 -0
- package/commands/help.md +176 -0
- package/commands/hmi-status.md +75 -0
- package/commands/ignite.md +229 -0
- package/commands/ingest-methodology.md +128 -0
- package/commands/intel-pipeline.md +133 -0
- package/commands/jtbd.md +262 -0
- package/commands/leadership.md +113 -0
- package/commands/lean-canvas.md +74 -0
- package/commands/macro-trends.md +88 -0
- package/commands/map-unknowns.md +76 -0
- package/commands/memory-cortex-reach.md +79 -0
- package/commands/memory.md +213 -0
- package/commands/models.md +200 -0
- package/commands/mos-reason.md +489 -0
- package/commands/mos.md +162 -0
- package/commands/mullins.md +155 -0
- package/commands/mva-brief.md +86 -0
- package/commands/mva-option.md +119 -0
- package/commands/mva-report.md +76 -0
- package/commands/new-project.md +633 -0
- package/commands/new-surface.md +116 -0
- package/commands/onboard.md +489 -0
- package/commands/operator.md +168 -0
- package/commands/opportunities.md +177 -0
- package/commands/organize.md +74 -0
- package/commands/persona.md +276 -0
- package/commands/pipeline.md +167 -0
- package/commands/present.md +125 -0
- package/commands/publish.md +227 -0
- package/commands/pws-brain.md +154 -0
- package/commands/qualify-opportunity.md +113 -0
- package/commands/query.md +70 -0
- package/commands/radar.md +110 -0
- package/commands/reanalyze.md +124 -0
- package/commands/research.md +467 -0
- package/commands/room.md +385 -0
- package/commands/rooms.md +682 -0
- package/commands/root-cause.md +77 -0
- package/commands/rs-experts.md +153 -0
- package/commands/rs-explain.md +134 -0
- package/commands/rs-fetch.md +119 -0
- package/commands/rs-thesis.md +131 -0
- package/commands/scenario-plan.md +80 -0
- package/commands/scheduled-tasks.md +308 -0
- package/commands/score-innovation.md +78 -0
- package/commands/scout.md +379 -0
- package/commands/setup.md +658 -0
- package/commands/show.md +71 -0
- package/commands/skill.md +108 -0
- package/commands/snapshot.md +179 -0
- package/commands/speakers.md +118 -0
- package/commands/splash.md +55 -0
- package/commands/stance.md +91 -0
- package/commands/status.md +109 -0
- package/commands/structure-argument.md +82 -0
- package/commands/suggest-next.md +141 -0
- package/commands/systems-thinking.md +224 -0
- package/commands/think-hats.md +86 -0
- package/commands/trending-to-absurd.md +151 -0
- package/commands/update.md +256 -0
- package/commands/user-needs.md +77 -0
- package/commands/validate.md +77 -0
- package/commands/value-proposition.md +116 -0
- package/commands/vault.md +330 -0
- package/commands/visualize.md +71 -0
- package/commands/whitespace.md +541 -0
- package/commands/wiki.md +96 -0
- package/data/ROOM.md +47 -0
- package/data/brain-census.generated.json +2617 -0
- package/data/brain-orchestration-projection.json +4973 -0
- package/data/brain-packet-schema.json +545 -0
- package/data/brain-surface-contract.json +32 -0
- package/data/capability-ledger.json +391 -0
- package/data/capability-map-registry.json +23 -0
- package/data/command-registry.json +2433 -0
- package/data/connector-coverage-ledger.json +1618 -0
- package/data/connector-out-of-spine-allowlist.json +71 -0
- package/data/connector-registry.json +3637 -0
- package/data/corpus-stats-source.json +11 -0
- package/data/corpus-stats.generated.json +10 -0
- package/data/cross-domain-analogues.json +15 -0
- package/data/deck-aliases.json +6 -0
- package/data/deck-styles.json +85 -0
- package/data/deployment-surfaces.json +69 -0
- package/data/dispatch-framework-map.json +18 -0
- package/data/doctor-modules.json +206 -0
- package/data/dual-graph-health-baseline.json +12 -0
- package/data/eureka-critic-tags.json +32 -0
- package/data/first-reward-surfaces.json +66 -0
- package/data/first-touch-surfaces.json +60 -0
- package/data/flagship-floor-set.json +37 -0
- package/data/framework-names.json +121 -0
- package/data/grant-rubric-fixtures/early-stage-pre-seed.json +18 -0
- package/data/grant-rubric-fixtures/early-stage-round-a.json +18 -0
- package/data/grant-rubric-fixtures/early-stage-seed.json +18 -0
- package/data/grant-rubric-fixtures/hybrid-seed-incentive.json +18 -0
- package/data/grant-rubric-fixtures/rd-fund.json +18 -0
- package/data/grant-rubric-fixtures/regional-innovation-incubators.json +18 -0
- package/data/grant-rubric-fixtures/technology-incubators.json +18 -0
- package/data/grant-rubric-fixtures/tnufa.json +167 -0
- package/data/grant-rubric-schema.json +33 -0
- package/data/harness-fixtures/converged-room/.context/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/.intelligence/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/.snapshots/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/MINTO.md +19 -0
- package/data/harness-fixtures/converged-room/STATE.md +73 -0
- package/data/harness-fixtures/converged-room/USER.md +23 -0
- package/data/harness-fixtures/converged-room/assets/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/business-model/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/business-model/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/competitive-analysis/CONTEXT.md +33 -0
- package/data/harness-fixtures/converged-room/competitive-analysis/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/financial-model/CONTEXT.md +30 -0
- package/data/harness-fixtures/converged-room/financial-model/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/funding/CONTEXT.md +41 -0
- package/data/harness-fixtures/converged-room/funding/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/legal-ip/CONTEXT.md +30 -0
- package/data/harness-fixtures/converged-room/legal-ip/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/market-analysis/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/market-analysis/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/opportunity-bank/CONTEXT.md +39 -0
- package/data/harness-fixtures/converged-room/opportunity-bank/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/problem-definition/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/problem-definition/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/references/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/references/SECTION-SCHEMA.md +213 -0
- package/data/harness-fixtures/converged-room/references/SUB-SCHEMAS.md +91 -0
- package/data/harness-fixtures/converged-room/solution-design/CONTEXT.md +36 -0
- package/data/harness-fixtures/converged-room/solution-design/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/strategy/CONTEXT.md +35 -0
- package/data/harness-fixtures/converged-room/strategy/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/team/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/team-execution/CONTEXT.md +36 -0
- package/data/harness-fixtures/converged-room/team-execution/ROOM.md +35 -0
- package/data/harness-manifest.json +74 -0
- package/data/harness-policies/CONTEXT.md +55 -0
- package/data/harness-policies/_schema.json +42 -0
- package/data/harness-policies/contract-parity-larry.json +63 -0
- package/data/harness-policies/gate-card-fire.json +13 -0
- package/data/harness-policies/gate-graph-derive-health.json +13 -0
- package/data/harness-policies/gate-shape-declaration.json +13 -0
- package/data/harness-policies/gate-tool-honesty.json +13 -0
- package/data/harness-policies/gate-worktree-hygiene.json +13 -0
- package/data/harness-policies/memory-write-policy.json +28 -0
- package/data/harness-policies/registry-drift.json +13 -0
- package/data/harness-policies/release-payload-ceiling.json +13 -0
- package/data/harness-policies/voice-backend-noun-free.json +13 -0
- package/data/harness-policies/voice-fork-as-card.json +13 -0
- package/data/harness-policies/voice-glyph-present.json +13 -0
- package/data/harness-policies/voice-hyphens-only.json +13 -0
- package/data/help-groups.json +236 -0
- package/data/hitl-shape-backfill.json +777 -0
- package/data/hitl-shape-declaration-fixtures/invalid-bad-shape.json +5 -0
- package/data/hitl-shape-declaration-fixtures/invalid-missing-why.json +4 -0
- package/data/hitl-shape-declaration-fixtures/invalid-ranker-contradiction.json +6 -0
- package/data/hitl-shape-declaration-fixtures/invalid-skill-gap.json +3 -0
- package/data/hitl-shape-declaration-fixtures/valid-multi-stage.json +7 -0
- package/data/hitl-shape-declaration-fixtures/valid-single-shape.json +5 -0
- package/data/hitl-shape-declaration-fixtures/valid-skill-exempt.json +5 -0
- package/data/hitl-shape-declaration-schema.json +34 -0
- package/data/hitl-stages-fixtures/bono.json +10 -0
- package/data/hitl-stages-fixtures/pipeline-1.json +9 -0
- package/data/hitl-stages-fixtures/pipeline-2.json +9 -0
- package/data/hitl-stages-fixtures/pipeline-3.json +10 -0
- package/data/hitl-stages-fixtures/research.json +10 -0
- package/data/hitl-stages-fixtures/rs-convergence.json +10 -0
- package/data/hitl-stages-fixtures/rs-family.json +11 -0
- package/data/hitl-stages-fixtures/rs-reverse-salient.json +10 -0
- package/data/hitl-stages-fixtures/trending-to-absurd.json +11 -0
- package/data/hitl-stages-schema.json +26 -0
- package/data/mcp-tool-connectors.json +464 -0
- package/data/mva-agent-prompts.json +31 -0
- package/data/mva-deck-template.html +15 -0
- package/data/mva-heuristic-keywords.json +35 -0
- package/data/orchestration-command-ledger.json +576 -0
- package/data/orchestration-unwired-allowlist.json +6 -0
- package/data/portfolio-ahp-matrix.json +16 -0
- package/data/publish-needs.json +76 -0
- package/data/render-coverage-registry.json +1152 -0
- package/data/research-sources.json +58 -0
- package/data/roadmap-type-chains.json +28 -0
- package/data/room-blueprints.json +156 -0
- package/data/subagent-dispatch-grants.json +150 -0
- package/hooks/hooks.json +493 -0
- package/hooks/run-hook.cmd +64 -0
- package/lib/__init__.py +0 -0
- package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/agents/auto-explore-agent.cjs +1163 -0
- package/lib/agents/mva/brain-classic-traps.cjs +55 -0
- package/lib/agents/mva/brain-cross-domain.cjs +79 -0
- package/lib/agents/mva/brain-similar-ventures.cjs +93 -0
- package/lib/agents/mva/dashboard-graph-neighborhood.cjs +72 -0
- package/lib/agents/mva/index.cjs +42 -0
- package/lib/agents/mva/six-hats-red-black.cjs +137 -0
- package/lib/agents/mva/tavily-funding-scan.cjs +147 -0
- package/lib/agents/mva/test-all-six-agents.cjs +467 -0
- package/lib/agents/reverse-salient-agent.cjs +757 -0
- package/lib/agents/tension-hook-agent.cjs +586 -0
- package/lib/brain/ROOM.md +44 -0
- package/lib/brain/chain-recommender.cjs +784 -0
- package/lib/brain/curation-batch.cjs +519 -0
- package/lib/brain/framework-chain-slice.cjs +252 -0
- package/lib/brain/hypergraph-event-schema.cjs +184 -0
- package/lib/chat/chat-context.js +185 -0
- package/lib/chat/chat-panel.js +721 -0
- package/lib/chat/fabric-chat.cjs +288 -0
- package/lib/chat/generative-tools.js +219 -0
- package/lib/conversation/ROOM.md +39 -0
- package/lib/conversation/classifier-rules.json +38 -0
- package/lib/conversation/classifier.cjs +264 -0
- package/lib/conversation/operator-epistemic-cap.test.cjs +156 -0
- package/lib/conversation/operator.cjs +492 -0
- package/lib/conversation/operator.test.cjs +160 -0
- package/lib/copy/115-spec-strings.cjs +55 -0
- package/lib/core/__init__.py +0 -0
- package/lib/core/__nav-stub.cjs +14 -0
- package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus_exclude.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
- package/lib/core/abstraction-gate.cjs +133 -0
- package/lib/core/act-jtbd-blurb.cjs +231 -0
- package/lib/core/artifact-id.cjs +148 -0
- package/lib/core/asset-ops.cjs +151 -0
- package/lib/core/auto-commit-throttle.cjs +129 -0
- package/lib/core/bearer-token.cjs +199 -0
- package/lib/core/behavioral/observation-schema.cjs +86 -0
- package/lib/core/bono/cell-fanout.cjs +305 -0
- package/lib/core/bono/debate-composition.cjs +495 -0
- package/lib/core/bono/hat-governance.cjs +367 -0
- package/lib/core/bono/persona-research.cjs +288 -0
- package/lib/core/bono/reviewer-governance.cjs +331 -0
- package/lib/core/brain-client.cjs +2365 -0
- package/lib/core/brain-derivation-prompts.cjs +331 -0
- package/lib/core/brain-derivation-queue.cjs +535 -0
- package/lib/core/brain-derivation.cjs +740 -0
- package/lib/core/brain-md-schema.cjs +528 -0
- package/lib/core/brain-md-staleness.cjs +357 -0
- package/lib/core/brain-response-sanitize.cjs +396 -0
- package/lib/core/breakthrough/canary.cjs +134 -0
- package/lib/core/breakthrough/canary.test.cjs +136 -0
- package/lib/core/breakthrough/detectors.cjs +359 -0
- package/lib/core/breakthrough/detectors.test.cjs +333 -0
- package/lib/core/breakthrough/ethics-fence.cjs +129 -0
- package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
- package/lib/core/breakthrough/resurfacing.cjs +150 -0
- package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
- package/lib/core/breakthrough/review-queue.cjs +168 -0
- package/lib/core/breakthrough/review-queue.test.cjs +160 -0
- package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
- package/lib/core/breakthrough/scanner.cjs +435 -0
- package/lib/core/breakthrough/scanner.test.cjs +267 -0
- package/lib/core/breakthrough/schema.cjs +180 -0
- package/lib/core/breakthrough/schema.test.cjs +256 -0
- package/lib/core/breakthrough/scoring.cjs +293 -0
- package/lib/core/breakthrough/scoring.test.cjs +423 -0
- package/lib/core/breakthrough/verb-dispatch.cjs +230 -0
- package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
- package/lib/core/breakthrough/voice-scaffold.cjs +249 -0
- package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
- package/lib/core/bridge-writer.cjs +477 -0
- package/lib/core/cache-prune.cjs +314 -0
- package/lib/core/card-fire-gate.cjs +107 -0
- package/lib/core/card-fire-sidechannel.cjs +518 -0
- package/lib/core/chain-executor.cjs +939 -0
- package/lib/core/chain-retry.cjs +214 -0
- package/lib/core/chain-step-dispatcher.cjs +397 -0
- package/lib/core/chat-context-builder.cjs +253 -0
- package/lib/core/check-plugin-enabled.cjs +145 -0
- package/lib/core/close-loop-writer.cjs +492 -0
- package/lib/core/command-registration-check.cjs +246 -0
- package/lib/core/correlation-label-index.cjs +230 -0
- package/lib/core/correlation.cjs +112 -0
- package/lib/core/coverage-monitor.cjs +183 -0
- package/lib/core/coverage-rollup.cjs +232 -0
- package/lib/core/cross-room-aggregator.cjs +1127 -0
- package/lib/core/cross-room-store.cjs +292 -0
- package/lib/core/cross-room-triggers.cjs +222 -0
- package/lib/core/daily-briefing.cjs +438 -0
- package/lib/core/decision-axes.cjs +178 -0
- package/lib/core/decision-capture.cjs +618 -0
- package/lib/core/deck-design-rules.cjs +211 -0
- package/lib/core/deep-links.cjs +82 -0
- package/lib/core/directive-envelope.cjs +217 -0
- package/lib/core/directive-envelope.test.cjs +225 -0
- package/lib/core/dispatch-optimizer.cjs +354 -0
- package/lib/core/doc-text-extractor.cjs +133 -0
- package/lib/core/doctor/capability-ledger-module.cjs +170 -0
- package/lib/core/doctor/card-fire-health-module.cjs +159 -0
- package/lib/core/doctor/cascade-rooms-active-module.cjs +127 -0
- package/lib/core/doctor/cascade-rooms-module.cjs +184 -0
- package/lib/core/doctor/class-m-brain-smoke.cjs +479 -0
- package/lib/core/doctor/class-m-brain-smoke.test.cjs +448 -0
- package/lib/core/doctor/class-s-eureka-smoke.cjs +380 -0
- package/lib/core/doctor/deployment-surfaces-module.cjs +408 -0
- package/lib/core/doctor/deprecated-usage-module.cjs +155 -0
- package/lib/core/doctor/eureka-fts-health-module.cjs +236 -0
- package/lib/core/doctor/graph-derive-heal-retrofit-module.cjs +148 -0
- package/lib/core/doctor/graph-derive-health-module.cjs +486 -0
- package/lib/core/doctor/install-incomplete-module.cjs +320 -0
- package/lib/core/doctor/install-state-module.cjs +503 -0
- package/lib/core/doctor/mcp-surface-module.cjs +369 -0
- package/lib/core/doctor/mode-select-checkpoint-module.cjs +113 -0
- package/lib/core/doctor/plugin-enabled-state-module.cjs +86 -0
- package/lib/core/doctor/room-graph-density-module.cjs +181 -0
- package/lib/core/doctor/room-md-module.cjs +198 -0
- package/lib/core/doctor/shared.cjs +555 -0
- package/lib/core/doctor/stale-first-touch-copy-module.cjs +83 -0
- package/lib/core/doctor/statusline-visibility-module.cjs +295 -0
- package/lib/core/doctor/ui-compliance-module.cjs +223 -0
- package/lib/core/doctor/umbilical-module.cjs +486 -0
- package/lib/core/doctor/verify-surface-module.cjs +82 -0
- package/lib/core/doctor/voice-style-log-module.cjs +146 -0
- package/lib/core/domain-insight-sweep.cjs +224 -0
- package/lib/core/drift-baseline.cjs +362 -0
- package/lib/core/drift-runtime-reachability.cjs +283 -0
- package/lib/core/dual-path-detector.cjs +84 -0
- package/lib/core/dual-path-detector.test.cjs +334 -0
- package/lib/core/enrichment-queue.cjs +636 -0
- package/lib/core/eureka/ahp-weights.cjs +232 -0
- package/lib/core/eureka/analogy-fitness.cjs +326 -0
- package/lib/core/eureka/compression-meter.cjs +165 -0
- package/lib/core/eureka/embedding-classifier.cjs +226 -0
- package/lib/core/eureka/embedding-spine.cjs +563 -0
- package/lib/core/eureka/entity-classifier.cjs +232 -0
- package/lib/core/eureka/entity-extractor.cjs +356 -0
- package/lib/core/eureka/eureka-enable.cjs +228 -0
- package/lib/core/eureka/eureka-offer.cjs +225 -0
- package/lib/core/eureka/eureka-reach-runner.cjs +430 -0
- package/lib/core/eureka/explore-chain.cjs +834 -0
- package/lib/core/eureka/explored-artifact.cjs +456 -0
- package/lib/core/eureka/fts-index-lifecycle.cjs +265 -0
- package/lib/core/eureka/grade-grant-examine.cjs +544 -0
- package/lib/core/eureka/grade-grant.cjs +596 -0
- package/lib/core/eureka/hybrid-retrieve.cjs +256 -0
- package/lib/core/eureka/lateral-engine-adapter.cjs +118 -0
- package/lib/core/eureka/lexical-overlap.cjs +98 -0
- package/lib/core/eureka/online-pattern-query.cjs +142 -0
- package/lib/core/eureka/opportunity-harvest.cjs +752 -0
- package/lib/core/eureka/opportunity-statement.cjs +519 -0
- package/lib/core/eureka/portfolio-dimensions.cjs +255 -0
- package/lib/core/eureka/qualify-opportunity.cjs +607 -0
- package/lib/core/eureka/reasoning-mode.cjs +481 -0
- package/lib/core/eureka/report-html.cjs +368 -0
- package/lib/core/eureka/research-filing.cjs +547 -0
- package/lib/core/eureka/room-native-substrate.cjs +343 -0
- package/lib/core/eureka/tail-quadrant.cjs +177 -0
- package/lib/core/eureka/tri-modal-index.cjs +564 -0
- package/lib/core/eureka/vector-store.cjs +414 -0
- package/lib/core/eureka-critic.cjs +733 -0
- package/lib/core/eureka-deps-resolver.cjs +179 -0
- package/lib/core/expert-genesis.cjs +437 -0
- package/lib/core/expert-library.cjs +303 -0
- package/lib/core/exports-log.cjs +79 -0
- package/lib/core/feynman/ROOM.md +25 -0
- package/lib/core/feynman/dial-memory-renderer.cjs +279 -0
- package/lib/core/feynman/dial-memory-runner.cjs +173 -0
- package/lib/core/feynman/feynman-seed-writer.cjs +187 -0
- package/lib/core/feynman/timeline-renderer.cjs +197 -0
- package/lib/core/feynman/timeline-runner.cjs +281 -0
- package/lib/core/feynman-minto-invariants.cjs +609 -0
- package/lib/core/findings-wirer.cjs +360 -0
- package/lib/core/first-touch-version-stamper.cjs +113 -0
- package/lib/core/folder-memory-async.cjs +424 -0
- package/lib/core/folder-memory-shared.cjs +890 -0
- package/lib/core/folder-memory.cjs +546 -0
- package/lib/core/framework-chain-composer.cjs +583 -0
- package/lib/core/frontmatter-schemas.cjs +468 -0
- package/lib/core/fusion-router.cjs +540 -0
- package/lib/core/futures/causal-cue.cjs +91 -0
- package/lib/core/futures/orchestrator.cjs +1391 -0
- package/lib/core/futures/subsystem-render.cjs +164 -0
- package/lib/core/gate-relevance.cjs +278 -0
- package/lib/core/git-ops.cjs +141 -0
- package/lib/core/graph-backfill.cjs +562 -0
- package/lib/core/graph-candidate-producer.cjs +235 -0
- package/lib/core/graph-derivation.cjs +546 -0
- package/lib/core/graph-derive-classifier.cjs +352 -0
- package/lib/core/graph-ops.cjs +274 -0
- package/lib/core/graph-refine-loop.cjs +194 -0
- package/lib/core/graph-self-heal.cjs +289 -0
- package/lib/core/greeting-intent-detector.cjs +201 -0
- package/lib/core/grill-engine.cjs +459 -0
- package/lib/core/hat-persistence.cjs +274 -0
- package/lib/core/hsi-engine.cjs +439 -0
- package/lib/core/hsi-lsa.cjs +246 -0
- package/lib/core/hsi-spectral.cjs +554 -0
- package/lib/core/hsi-to-graph.test.cjs +168 -0
- package/lib/core/icm-forest.cjs +703 -0
- package/lib/core/ignite-branch-gate.cjs +66 -0
- package/lib/core/index.cjs +60 -0
- package/lib/core/insight-sensors.cjs +1000 -0
- package/lib/core/install-state.cjs +242 -0
- package/lib/core/integration-registry.cjs +232 -0
- package/lib/core/intel-pipeline.cjs +545 -0
- package/lib/core/intelligence-cascade.cjs +852 -0
- package/lib/core/issue-tree.cjs +269 -0
- package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
- package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
- package/lib/core/lazygraph-ops.cjs +1453 -0
- package/lib/core/lens-engine.cjs +434 -0
- package/lib/core/leverage-scan.cjs +218 -0
- package/lib/core/llm-name-suggester.cjs +194 -0
- package/lib/core/llm-name-suggester.test.cjs +132 -0
- package/lib/core/lru-cache.cjs +139 -0
- package/lib/core/mcp-dep-heal.cjs +475 -0
- package/lib/core/mcp-dep-heal.test.cjs +253 -0
- package/lib/core/mcp-profiles.cjs +185 -0
- package/lib/core/meeting-ops.cjs +54 -0
- package/lib/core/memory/governance-candidate-raiser.cjs +205 -0
- package/lib/core/memory/governance-candidate.cjs +95 -0
- package/lib/core/memory/reconcile-memory-runner.cjs +653 -0
- package/lib/core/memory-ops.cjs +641 -0
- package/lib/core/meter/gate-density-reader.cjs +112 -0
- package/lib/core/meter/transfer-reader.cjs +276 -0
- package/lib/core/meter/two-gauge.cjs +245 -0
- package/lib/core/methodology-ingest.cjs +346 -0
- package/lib/core/metrics/larryreach-loop-health.cjs +142 -0
- package/lib/core/migration-snapshot.cjs +287 -0
- package/lib/core/migration-snapshot.test.cjs +174 -0
- package/lib/core/migrations/ROOM.md +33 -0
- package/lib/core/migrations/phase-109-nodes-provenance.cjs +400 -0
- package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
- package/lib/core/migrations/phase-160-nodes-bitemporal.cjs +168 -0
- package/lib/core/migrations/phase-160-nodes-bitemporal.test.cjs +140 -0
- package/lib/core/migrations/phase-162-section-nodes.cjs +156 -0
- package/lib/core/migrations/phase-222-ranker-weights.cjs +62 -0
- package/lib/core/migrations/phase-224-edge-review-status.cjs +87 -0
- package/lib/core/mindrian-brain-shim.test.cjs +221 -0
- package/lib/core/mode-select-sidechannel.cjs +259 -0
- package/lib/core/model-profiles.cjs +246 -0
- package/lib/core/mullins-scaffold.cjs +258 -0
- package/lib/core/mullins-scaffold.test.cjs +128 -0
- package/lib/core/mva-agent-contract.cjs +170 -0
- package/lib/core/mva-agent-contract.test.cjs +169 -0
- package/lib/core/mva-budget.cjs +75 -0
- package/lib/core/mva-budget.test.cjs +68 -0
- package/lib/core/mva-classifier.cjs +392 -0
- package/lib/core/mva-classifier.test.cjs +248 -0
- package/lib/core/mva-deck-builder.cjs +452 -0
- package/lib/core/mva-deck-builder.test.cjs +287 -0
- package/lib/core/mva-detect.smoke.test.cjs +209 -0
- package/lib/core/mva-dispatcher.cjs +110 -0
- package/lib/core/mva-dispatcher.test.cjs +216 -0
- package/lib/core/mva-option-router.cjs +414 -0
- package/lib/core/mva-option-router.test.cjs +483 -0
- package/lib/core/mva-orchestrator.cjs +365 -0
- package/lib/core/mva-orchestrator.test.cjs +916 -0
- package/lib/core/mva-progressive-renderer.cjs +194 -0
- package/lib/core/mva-progressive-renderer.test.cjs +157 -0
- package/lib/core/mva-rule-linter.cjs +458 -0
- package/lib/core/mva-rule-linter.test.cjs +484 -0
- package/lib/core/mva-state.cjs +159 -0
- package/lib/core/mva-telemetry.cjs +58 -0
- package/lib/core/mva-telemetry.test.cjs +196 -0
- package/lib/core/mva-vercel-deploy.cjs +168 -0
- package/lib/core/mva-vercel-deploy.test.cjs +239 -0
- package/lib/core/nav-dial.cjs +440 -0
- package/lib/core/navigation/ROOM.md +15 -0
- package/lib/core/navigation/abstraction-claim.cjs +185 -0
- package/lib/core/navigation/calibration-gate.cjs +284 -0
- package/lib/core/navigation/calibration-log.cjs +141 -0
- package/lib/core/navigation/confirm-node.cjs +73 -0
- package/lib/core/navigation/dashboard-helpers.cjs +145 -0
- package/lib/core/navigation/edges.cjs +1150 -0
- package/lib/core/navigation/evidence-claim.cjs +186 -0
- package/lib/core/navigation/explanation.cjs +48 -0
- package/lib/core/navigation/file-evidence-readback.cjs +242 -0
- package/lib/core/navigation/focus.cjs +155 -0
- package/lib/core/navigation/get-domains-for-trends.cjs +277 -0
- package/lib/core/navigation/governance.cjs +101 -0
- package/lib/core/navigation/grant-rubric.cjs +218 -0
- package/lib/core/navigation/graph-export.cjs +325 -0
- package/lib/core/navigation/ingestion.cjs +109 -0
- package/lib/core/navigation/insights-stale.test.cjs +127 -0
- package/lib/core/navigation/insights.cjs +574 -0
- package/lib/core/navigation/lens-nodes.cjs +315 -0
- package/lib/core/navigation/memory-artifacts.cjs +437 -0
- package/lib/core/navigation/memory-cortex-packet.cjs +274 -0
- package/lib/core/navigation/memory-events.cjs +811 -0
- package/lib/core/navigation/neighborhood.cjs +79 -0
- package/lib/core/navigation/packet.cjs +420 -0
- package/lib/core/navigation/planning-artifacts.cjs +219 -0
- package/lib/core/navigation/projections.cjs +295 -0
- package/lib/core/navigation/ranker-weights.cjs +133 -0
- package/lib/core/navigation/reasoning-write.cjs +211 -0
- package/lib/core/navigation/reconcile-guard.cjs +98 -0
- package/lib/core/navigation/reified-claim.cjs +287 -0
- package/lib/core/navigation/research-preflight.cjs +156 -0
- package/lib/core/navigation/room-birth.cjs +1213 -0
- package/lib/core/navigation/room-context.cjs +380 -0
- package/lib/core/navigation/room-home.cjs +142 -0
- package/lib/core/navigation/spine-events.cjs +548 -0
- package/lib/core/navigation/synthetic-expert.cjs +177 -0
- package/lib/core/navigation/transitions.cjs +253 -0
- package/lib/core/navigation/typed-claim.cjs +208 -0
- package/lib/core/navigation/typed-domain.cjs +249 -0
- package/lib/core/navigation/typed-entity.cjs +236 -0
- package/lib/core/navigation/typed-frame.cjs +257 -0
- package/lib/core/navigation/typed-open-question.cjs +130 -0
- package/lib/core/navigation/typed-opportunity.cjs +418 -0
- package/lib/core/navigation-engine-offer.cjs +776 -0
- package/lib/core/navigation-engine-shared.cjs +256 -0
- package/lib/core/navigation-engine.cjs +1696 -0
- package/lib/core/navigation.cjs +686 -0
- package/lib/core/nl-graph-queries.cjs +164 -0
- package/lib/core/node-insert-epistemic.test.cjs +212 -0
- package/lib/core/node-insert-overrides.test.cjs +273 -0
- package/lib/core/node-insert.cjs +274 -0
- package/lib/core/npm-cli-resolve.cjs +151 -0
- package/lib/core/npm-cli-resolve.test.cjs +153 -0
- package/lib/core/npm-install-lock.cjs +326 -0
- package/lib/core/npm-install-lock.test.cjs +379 -0
- package/lib/core/numeric/sklearn-stopwords-v1.json +325 -0
- package/lib/core/numeric/svd.cjs +377 -0
- package/lib/core/numeric/tfidf.cjs +241 -0
- package/lib/core/offer-presenter.cjs +406 -0
- package/lib/core/opportunity-extractor.cjs +183 -0
- package/lib/core/opportunity-ops.cjs +1423 -0
- package/lib/core/orchestration-candidate-lift.cjs +284 -0
- package/lib/core/part8-egress-guard.cjs +536 -0
- package/lib/core/part8-egress-guard.test.cjs +194 -0
- package/lib/core/part8-egress-ontology.cjs +174 -0
- package/lib/core/part8-egress-ontology.test.cjs +133 -0
- package/lib/core/persona-ops.cjs +692 -0
- package/lib/core/persona-override.cjs +327 -0
- package/lib/core/persona-taxonomy.cjs +328 -0
- package/lib/core/pinecone-inference.cjs +176 -0
- package/lib/core/pitch-feedback-schemas.cjs +303 -0
- package/lib/core/planning/artifact-brain-packet.cjs +212 -0
- package/lib/core/planning/reconcile-runner.cjs +347 -0
- package/lib/core/platform-gates.cjs +120 -0
- package/lib/core/platform.cjs +257 -0
- package/lib/core/proactive-intelligence.cjs +528 -0
- package/lib/core/problem-type-router.cjs +315 -0
- package/lib/core/publish-needs-default-lane.cjs +110 -0
- package/lib/core/ralph-loop-gate.cjs +48 -0
- package/lib/core/reader/ab-harness.cjs +177 -0
- package/lib/core/reader/decide-projection-reader.cjs +358 -0
- package/lib/core/reasoning-ops.cjs +639 -0
- package/lib/core/recipe-maps.cjs +442 -0
- package/lib/core/recovery/case-file.cjs +358 -0
- package/lib/core/recovery/controller.cjs +1008 -0
- package/lib/core/recovery/dispatcher.cjs +533 -0
- package/lib/core/recovery/result-semantics.cjs +488 -0
- package/lib/core/recovery/stage-envelope.cjs +338 -0
- package/lib/core/refusal-messaging.cjs +569 -0
- package/lib/core/refusal-messaging.test.cjs +220 -0
- package/lib/core/repo-version.cjs +134 -0
- package/lib/core/research-cache.cjs +165 -0
- package/lib/core/research-cache.test.cjs +137 -0
- package/lib/core/research-context-extractor.cjs +283 -0
- package/lib/core/research-corpus.cjs +814 -0
- package/lib/core/research-corpus.test.cjs +280 -0
- package/lib/core/research-filing-selector.cjs +189 -0
- package/lib/core/resolve-active-room.cjs +602 -0
- package/lib/core/resolve-brain-key.cjs +273 -0
- package/lib/core/resolve-umbilical-target.cjs +288 -0
- package/lib/core/resolve-vercel-key.cjs +107 -0
- package/lib/core/resolve-vercel-key.test.cjs +137 -0
- package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
- package/lib/core/room-auto-create.cjs +318 -0
- package/lib/core/room-auto-create.test.cjs +198 -0
- package/lib/core/room-chooser.cjs +244 -0
- package/lib/core/room-classifier-strict-mode.cjs +235 -0
- package/lib/core/room-db.cjs +348 -0
- package/lib/core/room-discard-cascade.cjs +238 -0
- package/lib/core/room-discard-cascade.test.cjs +135 -0
- package/lib/core/room-name-validator.cjs +132 -0
- package/lib/core/room-name-validator.test.cjs +156 -0
- package/lib/core/room-naming-selector.cjs +357 -0
- package/lib/core/room-naming-selector.test.cjs +277 -0
- package/lib/core/room-open.cjs +259 -0
- package/lib/core/room-ops-async.cjs +92 -0
- package/lib/core/room-ops-shared.cjs +64 -0
- package/lib/core/room-ops-sync.cjs +70 -0
- package/lib/core/room-ops.cjs +32 -0
- package/lib/core/room-receipt-emit.cjs +63 -0
- package/lib/core/room-root.cjs +93 -0
- package/lib/core/room-skeleton-scaffold.cjs +669 -0
- package/lib/core/room-skeleton-scaffold.test.cjs +296 -0
- package/lib/core/room-type-detector.cjs +386 -0
- package/lib/core/rs-backend-dispatch.cjs +64 -0
- package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
- package/lib/core/rs-brain-substrate.cjs +570 -0
- package/lib/core/rs-breakthrough-scorer.cjs +255 -0
- package/lib/core/rs-canon-violations.cjs +82 -0
- package/lib/core/rs-chain-feeder.cjs +381 -0
- package/lib/core/rs-commercial-assessor.cjs +280 -0
- package/lib/core/rs-corpus-quality-gate.cjs +62 -0
- package/lib/core/rs-differential-scorer.cjs +629 -0
- package/lib/core/rs-domain-analyzer.cjs +385 -0
- package/lib/core/rs-egress-prompts.cjs +113 -0
- package/lib/core/rs-egress-telemetry.cjs +225 -0
- package/lib/core/rs-egress-violations.cjs +53 -0
- package/lib/core/rs-engine.cjs +597 -0
- package/lib/core/rs-expert-brain-projection.cjs +280 -0
- package/lib/core/rs-expert-mapper.cjs +467 -0
- package/lib/core/rs-fetcher-academic.cjs +697 -0
- package/lib/core/rs-fetcher-experts.cjs +314 -0
- package/lib/core/rs-fetcher-industry.cjs +731 -0
- package/lib/core/rs-fetcher-patents.cjs +564 -0
- package/lib/core/rs-innovation-classifier.cjs +194 -0
- package/lib/core/rs-math.cjs +458 -0
- package/lib/core/rs-mind-map.cjs +656 -0
- package/lib/core/rs-neo4j-writer.cjs +388 -0
- package/lib/core/rs-nl-to-query.cjs +435 -0
- package/lib/core/rs-pinecone-bridge.cjs +344 -0
- package/lib/core/rs-preprocessor.cjs +350 -0
- package/lib/core/rs-query-matrix.cjs +316 -0
- package/lib/core/rs-query-to-text.cjs +438 -0
- package/lib/core/rs-sqlite-mirror.cjs +495 -0
- package/lib/core/rs-thesis-generator.cjs +188 -0
- package/lib/core/rs_cache.py +635 -0
- package/lib/core/rs_corpus.py +562 -0
- package/lib/core/rs_corpus_exclude.py +62 -0
- package/lib/core/rs_hybrid.py +589 -0
- package/lib/core/rs_math.py +287 -0
- package/lib/core/rs_rooms.py +196 -0
- package/lib/core/salient-governance.cjs +315 -0
- package/lib/core/scheduled-scanner.cjs +658 -0
- package/lib/core/scratchpad-ops.cjs +316 -0
- package/lib/core/seam-liveness.cjs +191 -0
- package/lib/core/seam-liveness.test.cjs +177 -0
- package/lib/core/section-8-trace-schema.cjs +138 -0
- package/lib/core/section-registry.cjs +167 -0
- package/lib/core/security/agentshield-adapters.test.cjs +104 -0
- package/lib/core/security/agentshield-run.cjs +343 -0
- package/lib/core/security/agentshield-scanner.cjs +314 -0
- package/lib/core/security/agentshield-scanner.test.cjs +259 -0
- package/lib/core/sensors/hat-scoping-table.cjs +103 -0
- package/lib/core/sensors/sensor-circularity.cjs +278 -0
- package/lib/core/sensors/sensor-content-relevance.cjs +407 -0
- package/lib/core/sensors/sensor-diffusion-adoption.cjs +230 -0
- package/lib/core/sensors/sensor-eureka.cjs +208 -0
- package/lib/core/sensors/sensor-expert-skill.cjs +293 -0
- package/lib/core/sensors/sensor-external-fact.cjs +184 -0
- package/lib/core/sensors/sensor-gate-approach.cjs +104 -0
- package/lib/core/sensors/sensor-jtbd-reweight.cjs +141 -0
- package/lib/core/sensors/sensor-lagging-component.cjs +115 -0
- package/lib/core/sensors/sensor-memory-cortex.cjs +111 -0
- package/lib/core/sensors/sensor-methodology-decision.cjs +131 -0
- package/lib/core/sensors/sensor-opportunity-harvest.cjs +195 -0
- package/lib/core/sensors/sensor-perspective-lock.cjs +174 -0
- package/lib/core/sensors/sensor-priority.cjs +183 -0
- package/lib/core/sensors/sensor-recency.cjs +151 -0
- package/lib/core/sensors/sensor-roadmap-type.cjs +421 -0
- package/lib/core/sensors/sensor-room-pick.cjs +238 -0
- package/lib/core/sensors/sensor-show-share.cjs +215 -0
- package/lib/core/sensors/sensor-temporal-blindness.cjs +123 -0
- package/lib/core/sensors/sensor-types.cjs +294 -0
- package/lib/core/sensors/sensor-url-ingest.cjs +234 -0
- package/lib/core/session-binding.cjs +208 -0
- package/lib/core/session-presence.cjs +404 -0
- package/lib/core/session-register.cjs +138 -0
- package/lib/core/session-state.cjs +144 -0
- package/lib/core/shallow-doc-parser.cjs +353 -0
- package/lib/core/shallow-doc-parser.test.cjs +226 -0
- package/lib/core/skill-activation-router.cjs +284 -0
- package/lib/core/skillopt-schemas.cjs +509 -0
- package/lib/core/stale-copy-scanner.cjs +190 -0
- package/lib/core/stance-state.cjs +153 -0
- package/lib/core/state-aware-router.cjs +78 -0
- package/lib/core/state-ops.cjs +80 -0
- package/lib/core/state-version.cjs +350 -0
- package/lib/core/statusline-cache.cjs +266 -0
- package/lib/core/statusline-liveness-gate.cjs +105 -0
- package/lib/core/surface-fence.cjs +127 -0
- package/lib/core/synthesizers/comparison-matrix.cjs +82 -0
- package/lib/core/synthesizers/convergence-map.cjs +107 -0
- package/lib/core/synthesizers/domain-hierarchy.cjs +138 -0
- package/lib/core/synthesizers/source-comparison.cjs +56 -0
- package/lib/core/synthesizers/tension-map.cjs +119 -0
- package/lib/core/synthetic-expert-behavior-gate.cjs +99 -0
- package/lib/core/synthetic-expert-construction-gate.cjs +170 -0
- package/lib/core/telemetry/schema.cjs +168 -0
- package/lib/core/telemetry/schema.test.cjs +124 -0
- package/lib/core/telemetry/validator.cjs +200 -0
- package/lib/core/telemetry/validator.test.cjs +188 -0
- package/lib/core/telemetry/writer.cjs +141 -0
- package/lib/core/telemetry/writer.test.cjs +331 -0
- package/lib/core/temporal/date-sync-gate.cjs +256 -0
- package/lib/core/temporal/date-sync-gate.test.cjs +191 -0
- package/lib/core/temporal/dual-stamp.cjs +172 -0
- package/lib/core/temporal/dual-stamp.test.cjs +172 -0
- package/lib/core/temporal/point-in-time.cjs +76 -0
- package/lib/core/temporal/point-in-time.test.cjs +169 -0
- package/lib/core/temporal/recency-decay.cjs +119 -0
- package/lib/core/temporal/recency-decay.test.cjs +140 -0
- package/lib/core/temporal/reference-now.cjs +281 -0
- package/lib/core/temporal/reference-now.test.cjs +225 -0
- package/lib/core/temporal/resolve-relative-time.cjs +116 -0
- package/lib/core/temporal/resolve-relative-time.test.cjs +138 -0
- package/lib/core/temporal/supersession.cjs +250 -0
- package/lib/core/temporal/supersession.test.cjs +103 -0
- package/lib/core/terminal-capability.cjs +88 -0
- package/lib/core/token-estimator.cjs +348 -0
- package/lib/core/tone-filter.cjs +147 -0
- package/lib/core/trending-to-absurd/orchestrator.cjs +341 -0
- package/lib/core/trending-to-absurd/stage7-roadmap.cjs +317 -0
- package/lib/core/trending-to-absurd/variance.cjs +169 -0
- package/lib/core/unknowns/bandit.cjs +332 -0
- package/lib/core/unknowns/corpus-adapter.cjs +327 -0
- package/lib/core/unknowns/dsp.cjs +356 -0
- package/lib/core/unknowns/edge-writer.cjs +211 -0
- package/lib/core/unknowns/iface.cjs +212 -0
- package/lib/core/unknowns/orchestrator.cjs +375 -0
- package/lib/core/unknowns/pattern-miner.cjs +214 -0
- package/lib/core/unknowns/rumsfeld-matrix.cjs +155 -0
- package/lib/core/unknowns/verdict.cjs +254 -0
- package/lib/core/update-path.cjs +57 -0
- package/lib/core/url-ingest.cjs +568 -0
- package/lib/core/user-archetype.cjs +284 -0
- package/lib/core/user-md-ops.cjs +578 -0
- package/lib/core/venture-shape-nudge.cjs +178 -0
- package/lib/core/venture-shape-nudge.test.cjs +266 -0
- package/lib/core/verb-reach-affinity.cjs +299 -0
- package/lib/core/visual-ops.cjs +692 -0
- package/lib/core/voice-transition-detector.cjs +125 -0
- package/lib/core/watched-sources.cjs +237 -0
- package/lib/core/write-lock.cjs +149 -0
- package/lib/graph/canvas-graph.js +467 -0
- package/lib/graph/constellation-config.cjs +299 -0
- package/lib/graph/graph-detail-panel.js +165 -0
- package/lib/hmi/ROOM.md +47 -0
- package/lib/hmi/across-session-memory.cjs +669 -0
- package/lib/hmi/agentshield-drift-gate.cjs +142 -0
- package/lib/hmi/brain-review-packet.cjs +221 -0
- package/lib/hmi/cortex-reach-adapter.cjs +271 -0
- package/lib/hmi/cross-room-memory.cjs +618 -0
- package/lib/hmi/decoy-tier.cjs +395 -0
- package/lib/hmi/depth-state.cjs +164 -0
- package/lib/hmi/dial-label-composer.cjs +410 -0
- package/lib/hmi/dial-presenter.cjs +508 -0
- package/lib/hmi/dial-reach-orchestrator.cjs +377 -0
- package/lib/hmi/dial-selector.cjs +239 -0
- package/lib/hmi/f1-pick-capture-cli.cjs +131 -0
- package/lib/hmi/f3-depth-capture-cli.cjs +77 -0
- package/lib/hmi/f4-scope-capture-cli.cjs +86 -0
- package/lib/hmi/f8-action-capture-cli.cjs +145 -0
- package/lib/hmi/f8-capture.test.cjs +77 -0
- package/lib/hmi/f9-ordered-capture-cli.cjs +138 -0
- package/lib/hmi/harvest-scope-state.cjs +202 -0
- package/lib/hmi/hats-persona-cache.cjs +142 -0
- package/lib/hmi/jtbd-classifier.cjs +219 -0
- package/lib/hmi/jtbd-state.cjs +263 -0
- package/lib/hmi/jtbd-taxonomy.json +402 -0
- package/lib/hmi/mindrian-ascii-logo.cjs +96 -0
- package/lib/hmi/part8-egress-gate.cjs +149 -0
- package/lib/hmi/reach-component-map.json +47 -0
- package/lib/hmi/reach-relevance-gate.cjs +147 -0
- package/lib/hmi/selector-dispatcher.cjs +1201 -0
- package/lib/hmi/selector-dispatcher.test.cjs +141 -0
- package/lib/hmi/selector-telemetry.cjs +269 -0
- package/lib/hmi/shape-f0-renderer.cjs +145 -0
- package/lib/hmi/shape-f1-fallback.cjs +80 -0
- package/lib/hmi/shape-f1-renderer.cjs +219 -0
- package/lib/hmi/shape-f2-renderer.cjs +132 -0
- package/lib/hmi/shape-f3-parity.test.cjs +64 -0
- package/lib/hmi/shape-f3-renderer.cjs +66 -0
- package/lib/hmi/shape-f4-parity.test.cjs +72 -0
- package/lib/hmi/shape-f4-renderer.cjs +72 -0
- package/lib/hmi/shape-f5-renderer.cjs +155 -0
- package/lib/hmi/shape-f6-plan-review-renderer.cjs +324 -0
- package/lib/hmi/shape-f6-renderer.cjs +144 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.cjs +234 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
- package/lib/hmi/shape-f8-renderer.cjs +150 -0
- package/lib/hmi/shape-f8-renderer.test.cjs +70 -0
- package/lib/hmi/shape-f9-renderer.cjs +187 -0
- package/lib/hmi/shape-f9-renderer.test.cjs +80 -0
- package/lib/hmi/shape-g-renderer.cjs +219 -0
- package/lib/hmi/shape-h-renderer.cjs +222 -0
- package/lib/hmi/tier-check.cjs +63 -0
- package/lib/hmi/turn-text.cjs +268 -0
- package/lib/hmi/voice-color-mark.cjs +318 -0
- package/lib/hmi/voice-style-log.cjs +276 -0
- package/lib/import/PRECONDITIONS.md +41 -0
- package/lib/import/branding.cjs +210 -0
- package/lib/import/branding.test.cjs +235 -0
- package/lib/import/classifications-sync.cjs +104 -0
- package/lib/import/classifications-sync.test.cjs +129 -0
- package/lib/import/enricher.cjs +296 -0
- package/lib/import/enricher.test.cjs +273 -0
- package/lib/import/integration.test.cjs +376 -0
- package/lib/import/manifest.cjs +129 -0
- package/lib/import/manifest.schema.json +185 -0
- package/lib/import/manifest.test.cjs +123 -0
- package/lib/import/meeting-detector.cjs +92 -0
- package/lib/import/meeting-detector.test.cjs +100 -0
- package/lib/import/person-detector.cjs +229 -0
- package/lib/import/person-detector.test.cjs +149 -0
- package/lib/import/report.cjs +186 -0
- package/lib/import/report.test.cjs +186 -0
- package/lib/import/room-md-scaffolder.cjs +49 -0
- package/lib/import/router.cjs +224 -0
- package/lib/import/router.test.cjs +356 -0
- package/lib/import/run-all-tests.cjs +36 -0
- package/lib/import/smoke-test.cjs +213 -0
- package/lib/import/smoke-test.test.cjs +148 -0
- package/lib/import/undo.test.cjs +199 -0
- package/lib/import/vault-scanner.cjs +105 -0
- package/lib/import/vault-scanner.test.cjs +67 -0
- package/lib/lens-engine/correlation-resolver.cjs +174 -0
- package/lib/lens-engine/source-lens-driver.cjs +736 -0
- package/lib/mcp/adapter-client.cjs +103 -0
- package/lib/mcp/app-html/dashboard.html +316 -0
- package/lib/mcp/app-html/graph.html +428 -0
- package/lib/mcp/app-html/mindrian-platform.html +1841 -0
- package/lib/mcp/app-html/wiki.html +383 -0
- package/lib/mcp/app-views.cjs +322 -0
- package/lib/mcp/brain-composition-census.cjs +184 -0
- package/lib/mcp/brain-router.cjs +530 -0
- package/lib/mcp/capability-registry.cjs +62 -0
- package/lib/mcp/contract-version.cjs +59 -0
- package/lib/mcp/daemon-lifecycle.cjs +324 -0
- package/lib/mcp/gate-dedup.cjs +120 -0
- package/lib/mcp/gate-ledger.cjs +145 -0
- package/lib/mcp/gate-render.cjs +483 -0
- package/lib/mcp/hook-adapter-audit.cjs +199 -0
- package/lib/mcp/larry-context.cjs +42 -0
- package/lib/mcp/mcp-first-flag.cjs +147 -0
- package/lib/mcp/no-instructions.test.cjs +270 -0
- package/lib/mcp/pipeline-state.cjs +424 -0
- package/lib/mcp/prompts.cjs +421 -0
- package/lib/mcp/register-core-tools.cjs +78 -0
- package/lib/mcp/resources.cjs +359 -0
- package/lib/mcp/runtime-instructions.cjs +38 -0
- package/lib/mcp/session-catchup.cjs +349 -0
- package/lib/mcp/session-registry.cjs +113 -0
- package/lib/mcp/session-room.cjs +124 -0
- package/lib/mcp/sse-event-bus.cjs +98 -0
- package/lib/mcp/stop-gate-handler.cjs +589 -0
- package/lib/mcp/surface-detect.cjs +186 -0
- package/lib/mcp/tool-router.cjs +2113 -0
- package/lib/mcp/tools/chain.cjs +674 -0
- package/lib/mcp/tools/claim.cjs +180 -0
- package/lib/mcp/tools/context.cjs +92 -0
- package/lib/mcp/tools/dual-path.cjs +79 -0
- package/lib/mcp/tools/gate.cjs +392 -0
- package/lib/mcp/tools/graph-reason.cjs +130 -0
- package/lib/mcp/tools/graph.cjs +321 -0
- package/lib/mcp/tools/identity.cjs +174 -0
- package/lib/mcp/tools/room.cjs +449 -0
- package/lib/mcp/tools/sensors.cjs +450 -0
- package/lib/mcp/tools/status.cjs +183 -0
- package/lib/mcp/tools/stop-gate.cjs +103 -0
- package/lib/mcp/tools/views.cjs +382 -0
- package/lib/mcp/tree-watcher.cjs +199 -0
- package/lib/memory/.mindrian/auto-explore-mat-test-001.json +11 -0
- package/lib/memory/aaak-compress.cjs +403 -0
- package/lib/memory/aaak-compress.test.cjs +288 -0
- package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
- package/lib/memory/bearer-token.test.cjs +315 -0
- package/lib/memory/body-shape-coverage.test.cjs +268 -0
- package/lib/memory/brain-cache-lru.test.cjs +259 -0
- package/lib/memory/brain-client-query-shape.test.cjs +160 -0
- package/lib/memory/brain-cypher-chain-slice.test.cjs +368 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
- package/lib/memory/brain-derivation-queue.test.cjs +553 -0
- package/lib/memory/brain-derivation.test.cjs +639 -0
- package/lib/memory/brain-derive-command.test.cjs +534 -0
- package/lib/memory/brain-derive-curation.test.cjs +243 -0
- package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
- package/lib/memory/brain-md-schema.test.cjs +467 -0
- package/lib/memory/brain-md-staleness.test.cjs +525 -0
- package/lib/memory/brain-server-resolution.test.cjs +314 -0
- package/lib/memory/brain-suggestion-template.test.cjs +200 -0
- package/lib/memory/chain-recommender-canonical.test.cjs +208 -0
- package/lib/memory/chain-recommender.test.cjs +233 -0
- package/lib/memory/chat-context.test.cjs +128 -0
- package/lib/memory/command-registry.test.cjs +220 -0
- package/lib/memory/connector-registry.test.cjs +94 -0
- package/lib/memory/correlation-label-index.test.cjs +119 -0
- package/lib/memory/correlation.test.cjs +253 -0
- package/lib/memory/cross-room-aggregator.test.cjs +909 -0
- package/lib/memory/curation-132-03-dedup-held-rename.test.cjs +251 -0
- package/lib/memory/curation-132-05-pseudonymize.test.cjs +205 -0
- package/lib/memory/curation-batch.test.cjs +213 -0
- package/lib/memory/dashboard-server.test.cjs +256 -0
- package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
- package/lib/memory/decision-capture.test.cjs +632 -0
- package/lib/memory/decision-capture.worker.cjs +70 -0
- package/lib/memory/doctor-deprecation-surface.test.cjs +188 -0
- package/lib/memory/dual-graph-health.test.cjs +485 -0
- package/lib/memory/explain-decision-command.test.cjs +521 -0
- package/lib/memory/explain-decision-footer.test.cjs +316 -0
- package/lib/memory/explored-materials-store.cjs +392 -0
- package/lib/memory/f-selector-ranker.test.cjs +593 -0
- package/lib/memory/feynman-minto-guardian.test.cjs +863 -0
- package/lib/memory/feynman-minto-invariants.test.cjs +554 -0
- package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
- package/lib/memory/feynman-prompts.cjs +151 -0
- package/lib/memory/feynman-prompts.test.cjs +96 -0
- package/lib/memory/first-touch-version.test.cjs +198 -0
- package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
- package/lib/memory/folder-memory.test.cjs +503 -0
- package/lib/memory/framework-chain-composer.test.cjs +549 -0
- package/lib/memory/frontmatter-schema-validator.test.cjs +344 -0
- package/lib/memory/guardian-onstop-reaches-user.test.cjs +396 -0
- package/lib/memory/heal-command.test.cjs +604 -0
- package/lib/memory/help-coverage.test.cjs +108 -0
- package/lib/memory/help-renderer.test.cjs +145 -0
- package/lib/memory/huji-run-one-async-parity.test.cjs +242 -0
- package/lib/memory/hypergraph-event-schema.test.cjs +116 -0
- package/lib/memory/index-artifact-transaction.test.cjs +333 -0
- package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
- package/lib/memory/local-chain-recommender.test.cjs +172 -0
- package/lib/memory/mcp-input-validation.test.cjs +240 -0
- package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
- package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
- package/lib/memory/minto-debounce-consumer-census.test.cjs +567 -0
- package/lib/memory/minto-debouncer.test.cjs +407 -0
- package/lib/memory/minto-debouncer.worker.cjs +46 -0
- package/lib/memory/minto-migration-v88.test.cjs +265 -0
- package/lib/memory/minto-schema-v88.test.cjs +390 -0
- package/lib/memory/mos-status-renderer.test.cjs +631 -0
- package/lib/memory/narrative-schema.cjs +376 -0
- package/lib/memory/narrative-schema.test.cjs +209 -0
- package/lib/memory/nav-dial.test.cjs +414 -0
- package/lib/memory/navigation-engine-core.test.cjs +712 -0
- package/lib/memory/navigation-engine-offer.test.cjs +404 -0
- package/lib/memory/navigation-hook-resolver.test.cjs +177 -0
- package/lib/memory/navigation-invariants.test.cjs +483 -0
- package/lib/memory/navigation-projections.test.cjs +241 -0
- package/lib/memory/navigation-write-edge.test.cjs +206 -0
- package/lib/memory/offer-closer.test.cjs +258 -0
- package/lib/memory/offer-presenter.test.cjs +554 -0
- package/lib/memory/on-stop-snapshot.test.cjs +404 -0
- package/lib/memory/orchestration-projection.test.cjs +706 -0
- package/lib/memory/packet-chain-hint.test.cjs +407 -0
- package/lib/memory/packet-schema-validation.test.cjs +317 -0
- package/lib/memory/palette-consistency.test.cjs +127 -0
- package/lib/memory/pending-tension-store.cjs +453 -0
- package/lib/memory/per-command-jtbd-derivation.test.cjs +130 -0
- package/lib/memory/per-command-teaching.test.cjs +110 -0
- package/lib/memory/post-compact-reinjection.test.cjs +854 -0
- package/lib/memory/post-write-triple.test.cjs +317 -0
- package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
- package/lib/memory/precommit-real-commit.test.cjs +301 -0
- package/lib/memory/problem-type-router.test.cjs +656 -0
- package/lib/memory/query-efficiency-telemetry.test.cjs +612 -0
- package/lib/memory/recompile-room-references.test.cjs +392 -0
- package/lib/memory/recompile-room-references.worker.cjs +42 -0
- package/lib/memory/record-decision-dual-write.test.cjs +454 -0
- package/lib/memory/render-v2-disposition.test.cjs +199 -0
- package/lib/memory/room-classifier-strict-mode.test.cjs +430 -0
- package/lib/memory/room-minto-hook.test.cjs +398 -0
- package/lib/memory/rs-discovery-engine.test.cjs +323 -0
- package/lib/memory/run-feynman-tests.cjs +2101 -0
- package/lib/memory/security-trifecta.test.cjs +329 -0
- package/lib/memory/selector-alias-map.test.cjs +172 -0
- package/lib/memory/selector-decisions.test.cjs +417 -0
- package/lib/memory/selector-miss.test.cjs +290 -0
- package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
- package/lib/memory/session-start-triple-injection.test.cjs +514 -0
- package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
- package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
- package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
- package/lib/memory/skill-activation-router.test.cjs +465 -0
- package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
- package/lib/memory/soft-alias.test.cjs +144 -0
- package/lib/memory/spine-events-correlation.test.cjs +198 -0
- package/lib/memory/stale-copy-scanner.test.cjs +294 -0
- package/lib/memory/stamp-artifact-write.test.cjs +304 -0
- package/lib/memory/state-aware-router.test.cjs +90 -0
- package/lib/memory/statusline-active-room-write.test.cjs +245 -0
- package/lib/memory/statusline-active-room.test.cjs +315 -0
- package/lib/memory/statusline-minto-segment.test.cjs +292 -0
- package/lib/memory/statusline-two-row.test.cjs +338 -0
- package/lib/memory/suggest-next-workflow.test.cjs +176 -0
- package/lib/memory/sync-async-entry-points.test.cjs +204 -0
- package/lib/memory/terminal-capability.test.cjs +155 -0
- package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
- package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
- package/lib/memory/test-rs-brain-substrate.cjs +636 -0
- package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
- package/lib/memory/test-rs-canon-violations.cjs +218 -0
- package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
- package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
- package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
- package/lib/memory/test-rs-differential-scorer.cjs +480 -0
- package/lib/memory/test-rs-discovery-engine.cjs +648 -0
- package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
- package/lib/memory/test-rs-egress-primitives.cjs +420 -0
- package/lib/memory/test-rs-expert-mapper.cjs +547 -0
- package/lib/memory/test-rs-explain-command.cjs +443 -0
- package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
- package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
- package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
- package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
- package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
- package/lib/memory/test-rs-mind-map.cjs +646 -0
- package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
- package/lib/memory/test-rs-nl-to-query.cjs +449 -0
- package/lib/memory/test-rs-pinecone-bridge.cjs +282 -0
- package/lib/memory/test-rs-preprocessor.cjs +433 -0
- package/lib/memory/test-rs-query-matrix.cjs +391 -0
- package/lib/memory/test-rs-query-to-text.cjs +551 -0
- package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
- package/lib/memory/test-rs-thesis-generator.cjs +360 -0
- package/lib/memory/triple-context-formatter.cjs +473 -0
- package/lib/memory/triple-context-formatter.test.cjs +442 -0
- package/lib/memory/user-md-persona.test.cjs +565 -0
- package/lib/memory/userpromptsubmit-integration.test.cjs +787 -0
- package/lib/memory/validators/README.md +157 -0
- package/lib/memory/validators/brain-md-invariants.cjs +475 -0
- package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
- package/lib/memory/validators/external-academic-invariants.cjs +249 -0
- package/lib/memory/validators/external-industry-invariants.cjs +271 -0
- package/lib/memory/validators/external-patents-invariants.cjs +266 -0
- package/lib/memory/validators/minto-invariants.cjs +62 -0
- package/lib/memory/validators/navigation-invariants.cjs +340 -0
- package/lib/memory/validators/queue-health.cjs +95 -0
- package/lib/memory/validators/snapshot-integrity.cjs +129 -0
- package/lib/memory/validators/stale-lifecycle.cjs +116 -0
- package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
- package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
- package/lib/memory/workflow-layer-e2e.test.cjs +262 -0
- package/lib/memory/write-lock-atomic.test.cjs +137 -0
- package/lib/memory/write-lock-atomic.worker.cjs +55 -0
- package/lib/parity/check-parity.cjs +83 -0
- package/lib/presentation/presentation-server.cjs +101 -0
- package/lib/presentation/presentation-watcher.cjs +123 -0
- package/lib/quickview/hub-server.cjs +719 -0
- package/lib/quickview/server.cjs +533 -0
- package/lib/render/JTBD-PALETTES.md +145 -0
- package/lib/render/ROOM.md +111 -0
- package/lib/render/render-v2.cjs +532 -0
- package/lib/render/render-v2.test.cjs +267 -0
- package/lib/render/render.cjs +65 -0
- package/lib/sessionstart/budget-compressor.cjs +130 -0
- package/lib/sessionstart/contributor-interface.cjs +134 -0
- package/lib/sessionstart/contributor-isolator.cjs +128 -0
- package/lib/sessionstart/precedence-ladder.cjs +47 -0
- package/lib/state/ROOM.md +46 -0
- package/lib/state/state-md-parser.cjs +215 -0
- package/lib/statusline/ROOM.md +38 -0
- package/lib/statusline/banner-suppression.cjs +50 -0
- package/lib/statusline/cockpit-renderer.cjs +443 -0
- package/lib/statusline/cockpit-signals.cjs +312 -0
- package/lib/statusline/cockpit-telemetry.cjs +152 -0
- package/lib/statusline/ctx-window.cjs +135 -0
- package/lib/statusline/governing-thought-truncator.cjs +45 -0
- package/lib/statusline/next-move-cache.cjs +235 -0
- package/lib/statusline/ratification-next.cjs +164 -0
- package/lib/statusline/room-health-cache.cjs +200 -0
- package/lib/statusline/surface-detect.cjs +85 -0
- package/lib/statusline/two-row-renderer.cjs +207 -0
- package/lib/statusline/version-resolver.cjs +81 -0
- package/lib/ui/design-system.cjs +19 -0
- package/lib/update-bootstrap.sh.template +145 -0
- package/lib/vault/frontmatter-schema.cjs +297 -0
- package/lib/vault/room-scanner.cjs +365 -0
- package/lib/vault/wikilink-builder.cjs +231 -0
- package/lib/vault/wikilink-builder.test.cjs +182 -0
- package/lib/wiki/briefing.cjs +193 -0
- package/lib/wiki/editor-dist/wiki-editor.css +1 -0
- package/lib/wiki/editor-dist/wiki-editor.js +1327 -0
- package/lib/wiki/graph-links.cjs +281 -0
- package/lib/wiki/page-renderer.cjs +229 -0
- package/lib/wiki/room-home.cjs +353 -0
- package/lib/wiki/wiki-chat.cjs +81 -0
- package/lib/wiki/wiki-export.cjs +364 -0
- package/lib/wiki/wiki-layout.cjs +1143 -0
- package/lib/wiki/wiki-search.cjs +142 -0
- package/lib/wiki/wiki-server.cjs +748 -0
- package/lib/wiki/wiki-watcher.cjs +105 -0
- package/lib/workflow/ROOM.md +47 -0
- package/lib/workflow/chain-source.cjs +179 -0
- package/lib/workflow/command-resolver.cjs +155 -0
- package/lib/workflow/command-resolver.test.cjs +235 -0
- package/lib/workflow/cross-room-umbilical-closer.cjs +364 -0
- package/lib/workflow/dial-close-reach.cjs +405 -0
- package/lib/workflow/f-selector-ranker.cjs +1148 -0
- package/lib/workflow/f1-pick-consumer.cjs +232 -0
- package/lib/workflow/f3-depth-consumer.cjs +98 -0
- package/lib/workflow/f4-scope-consumer.cjs +112 -0
- package/lib/workflow/f8-consumer.test.cjs +82 -0
- package/lib/workflow/f8-fanout-consumer.cjs +208 -0
- package/lib/workflow/f9-consumer.test.cjs +71 -0
- package/lib/workflow/f9-ordered-consumer.cjs +199 -0
- package/lib/workflow/local-chain-recommender.cjs +330 -0
- package/lib/workflow/memory-cascade-f9-adapter.cjs +273 -0
- package/lib/workflow/memory-governance-closer.cjs +384 -0
- package/lib/workflow/offer-closer.cjs +407 -0
- package/lib/workflow/reach-hedge-ranker.cjs +809 -0
- package/lib/workflow/reach-reject-reader.cjs +372 -0
- package/lib/workflow/reconcile-f9-adapter.cjs +299 -0
- package/lib/workflow/selector-decisions.cjs +478 -0
- package/lib/workflow/session-binding-consumer.cjs +249 -0
- package/npm-shrinkwrap.json +1544 -0
- package/output-styles/destijl.md +67 -0
- package/package.json +22 -4
- package/pipelines/PWS_grading/01-deep-grade.md +48 -0
- package/pipelines/PWS_grading/02-mullins.md +50 -0
- package/pipelines/PWS_grading/03-build-thesis.md +60 -0
- package/pipelines/PWS_grading/04-structure-argument.md +77 -0
- package/pipelines/PWS_grading/CHAIN.md +79 -0
- package/pipelines/analogy/01-decompose.md +80 -0
- package/pipelines/analogy/02-abstract.md +87 -0
- package/pipelines/analogy/03-search.md +135 -0
- package/pipelines/analogy/04-transfer.md +101 -0
- package/pipelines/analogy/05-validate.md +106 -0
- package/pipelines/analogy/CHAIN.md +73 -0
- package/pipelines/discovery/01-explore-domains.md +44 -0
- package/pipelines/discovery/02-think-hats.md +50 -0
- package/pipelines/discovery/03-analyze-needs.md +54 -0
- package/pipelines/discovery/CHAIN.md +48 -0
- package/pipelines/thesis/01-structure-argument.md +45 -0
- package/pipelines/thesis/02-challenge-assumptions.md +48 -0
- package/pipelines/thesis/03-build-thesis.md +54 -0
- package/pipelines/thesis/CHAIN.md +48 -0
- package/references/brain/causal-directives.md +91 -0
- package/references/brain/causal-enrichment.cypher +165 -0
- package/references/brain/command-triggers-schema.md +15 -0
- package/references/brain/graph-architecture.md +317 -0
- package/references/brain/query-patterns.md +504 -0
- package/references/brain/room-hierarchy-schema.md +218 -0
- package/references/brain/schema.md +86 -0
- package/references/capability-radar/capabilities-index.md +269 -0
- package/references/capability-radar/changelog-cache.md +95 -0
- package/references/causal/causal-schema.md +103 -0
- package/references/design/email-template-standard.md +169 -0
- package/references/design/graph-visualization-standard.md +178 -0
- package/references/design/newsletter-email-template.html +307 -0
- package/references/document-generation.md +179 -0
- package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
- package/references/import-config.md +141 -0
- package/references/integrations/detection-patterns.md +101 -0
- package/references/meeting/artifact-template.md +377 -0
- package/references/meeting/cross-meeting-intelligence.md +216 -0
- package/references/meeting/cross-relationship-patterns.md +202 -0
- package/references/meeting/extraction-perspectives.md +225 -0
- package/references/meeting/filing-protocol.md +222 -0
- package/references/meeting/knowledge-typing.md +242 -0
- package/references/meeting/live-join-interface.md +244 -0
- package/references/meeting/section-mapping.md +192 -0
- package/references/meeting/segment-classification.md +258 -0
- package/references/meeting/speaker-profile-template.md +219 -0
- package/references/meeting/summary-template.md +348 -0
- package/references/meeting/transcript-patterns.md +226 -0
- package/references/methodology/analyze-needs.md +135 -0
- package/references/methodology/analyze-systems.md +121 -0
- package/references/methodology/analyze-timing.md +149 -0
- package/references/methodology/beautiful-question.md +109 -0
- package/references/methodology/build-knowledge.md +161 -0
- package/references/methodology/build-thesis-scored.md +60 -0
- package/references/methodology/build-thesis.md +237 -0
- package/references/methodology/causal-loop-diagrams.md +65 -0
- package/references/methodology/challenge-assumptions.md +127 -0
- package/references/methodology/diagnose.md +169 -0
- package/references/methodology/dominant-designs.md +212 -0
- package/references/methodology/explore-domains.md +147 -0
- package/references/methodology/explore-futures.md +163 -0
- package/references/methodology/explore-trends.md +129 -0
- package/references/methodology/find-bottlenecks.md +131 -0
- package/references/methodology/grade.md +211 -0
- package/references/methodology/huji-stage-a-intake.md +246 -0
- package/references/methodology/index.md +34 -0
- package/references/methodology/leadership.md +200 -0
- package/references/methodology/lean-canvas.md +116 -0
- package/references/methodology/leverage-scan-signatures.md +83 -0
- package/references/methodology/macro-trends.md +192 -0
- package/references/methodology/map-unknowns.md +137 -0
- package/references/methodology/mullins-7-domains.md +104 -0
- package/references/methodology/problem-types.md +65 -0
- package/references/methodology/root-cause.md +178 -0
- package/references/methodology/rubric-huji.md +160 -0
- package/references/methodology/sapphire-encoding.md +355 -0
- package/references/methodology/scenario-plan.md +190 -0
- package/references/methodology/score-innovation.md +154 -0
- package/references/methodology/skillopt-judge-rubric.md +48 -0
- package/references/methodology/skillopt-queries-rubric.md +60 -0
- package/references/methodology/skillopt-refute-rubric.md +59 -0
- package/references/methodology/skillopt-review-rubric.md +72 -0
- package/references/methodology/skillopt-revise-rubric.md +48 -0
- package/references/methodology/structure-argument.md +158 -0
- package/references/methodology/systems-thinking.md +231 -0
- package/references/methodology/think-hats.md +147 -0
- package/references/methodology/triz-matrix.json +751 -0
- package/references/methodology/triz-principles.md +501 -0
- package/references/methodology/user-needs.md +199 -0
- package/references/methodology/validate.md +163 -0
- package/references/methodology/value-proposition.md +244 -0
- package/references/opportunities/funding-lifecycle.md +103 -0
- package/references/opportunities/grant-api-patterns.md +99 -0
- package/references/opportunities/opportunity-template.md +84 -0
- package/references/opportunities/tnufa-rubric.md +81 -0
- package/references/personality/assessment-philosophy.md +72 -0
- package/references/personality/lexicon.md +100 -0
- package/references/personality/persona-chains.md +56 -0
- package/references/personality/pws-lexicon-full.md +499 -0
- package/references/personality/voice-dna.md +156 -0
- package/references/personas/hat-perspectives.md +76 -0
- package/references/personas/persona-template.md +63 -0
- package/references/pipeline/act-output-contract.md +88 -0
- package/references/pipeline/chains-index.md +39 -0
- package/references/pws-profile-generation.md +79 -0
- package/references/reasoning/reasoning-schema.md +143 -0
- package/references/reasoning/reasoning-template.md +68 -0
- package/references/reasoning/run-template.md +38 -0
- package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
- package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
- package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
- package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
- package/references/security/agentshield-baseline.json +8 -0
- package/references/security/cve-db.json +220 -0
- package/references/taxonomy/TAXONOMY.md +192 -0
- package/references/templates/MINTO.md +36 -0
- package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
- package/references/vault-kit/README.md +35 -0
- package/references/vault-kit/app.json +12 -0
- package/references/vault-kit/appearance.json +12 -0
- package/references/vault-kit/graph.json +35 -0
- package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
- package/references/vault-kit/templates/new-artifact.md +37 -0
- package/references/vault-kit/templates/new-meeting-note.md +35 -0
- package/references/vault-kit/templates/new-team-profile.md +29 -0
- package/references/vault-kit/templates/new-xref.md +35 -0
- package/references/visual/ROOM.md +55 -0
- package/references/visual/palette.json +54 -0
- package/references/visual/symbol-system.md +151 -0
- package/scripts/189-plurai-gate-check.cjs +88 -0
- package/scripts/198-plurai-gate-check.cjs +118 -0
- package/scripts/198-rollback-rehearsal.cjs +233 -0
- package/scripts/83-scope-injection.test.cjs +648 -0
- package/scripts/PYTHON_GATES.md +78 -0
- package/scripts/__pycache__/compute-hsi.cpython-312.pyc +0 -0
- package/scripts/__pycache__/detect-reverse-salients.cpython-312.pyc +0 -0
- package/scripts/__pycache__/rs-engine.cpython-312.pyc +0 -0
- package/scripts/act-command.cjs +518 -0
- package/scripts/admin-brain-write.cjs +442 -0
- package/scripts/admin-command-gate.cjs +178 -0
- package/scripts/agentshield-scan-cli.cjs +226 -0
- package/scripts/agentshield-sessionstart-scan.cjs +282 -0
- package/scripts/analogy-fitness-report.cjs +266 -0
- package/scripts/analyze-room +1079 -0
- package/scripts/apply-teaching-content.cjs +298 -0
- package/scripts/async-artifact-auto-commit.cjs +485 -0
- package/scripts/audit-active-phase-resolver.cjs +160 -0
- package/scripts/audit-body-shape-coverage.cjs +213 -0
- package/scripts/auto-explore-drain.cjs +274 -0
- package/scripts/auto-explore-fingerprint.cjs +354 -0
- package/scripts/auto-explore-fire.cjs +359 -0
- package/scripts/backfill-correlation-id.cjs +425 -0
- package/scripts/backfill-hitl-shape.cjs +276 -0
- package/scripts/backfill-hitl-shape.test.cjs +307 -0
- package/scripts/backup-modifications +68 -0
- package/scripts/banner +182 -0
- package/scripts/brain-derivation-drain.cjs +291 -0
- package/scripts/brain-derive-command.cjs +846 -0
- package/scripts/brain-normalize-books.cypher +505 -0
- package/scripts/brain-normalize-final.cypher +1355 -0
- package/scripts/brain-normalize-problemtype.cypher +475 -0
- package/scripts/brain-normalize-supplement.cypher +449 -0
- package/scripts/brain-normalize-targeted.cypher +344 -0
- package/scripts/brain-normalize-v194.cypher +61 -0
- package/scripts/brain-normalize.cypher +530 -0
- package/scripts/brain-response-sanitize-hook.cjs +165 -0
- package/scripts/build-brain-census.cjs +957 -0
- package/scripts/build-brain-packet-schema.cjs +225 -0
- package/scripts/build-command-registry.cjs +587 -0
- package/scripts/build-connector-registry.cjs +1331 -0
- package/scripts/build-corpus-stats.cjs +408 -0
- package/scripts/build-dist-bundles.cjs +495 -0
- package/scripts/build-ecosystem-graph.cjs +490 -0
- package/scripts/build-graph +1462 -0
- package/scripts/build-graph-from-sqlite.cjs +97 -0
- package/scripts/build-harness-manifest.cjs +1090 -0
- package/scripts/build-jtbd-nudges +205 -0
- package/scripts/build-new-surface.cjs +805 -0
- package/scripts/build-orchestration-projection.cjs +1221 -0
- package/scripts/build-render-coverage.cjs +637 -0
- package/scripts/build-skill-mirrors.cjs +681 -0
- package/scripts/cache-hitrate-report.cjs +216 -0
- package/scripts/causal-to-graph.cjs +163 -0
- package/scripts/check-abstraction-fixture-neutral.cjs +160 -0
- package/scripts/check-admin-identity.cjs +212 -0
- package/scripts/check-brain-tool-liveness.cjs +502 -0
- package/scripts/check-card-fire.cjs +1809 -0
- package/scripts/check-cirs-declaration.cjs +279 -0
- package/scripts/check-deck-design.cjs +104 -0
- package/scripts/check-dual-graph-health.cjs +645 -0
- package/scripts/check-first-touch-drift.cjs +220 -0
- package/scripts/check-flagship-floor.cjs +391 -0
- package/scripts/check-framework-vocabulary-drift.cjs +563 -0
- package/scripts/check-gate-seam.cjs +178 -0
- package/scripts/check-graph-derive-health.cjs +208 -0
- package/scripts/check-graph-export-typemap.cjs +118 -0
- package/scripts/check-help-coverage.cjs +290 -0
- package/scripts/check-hitl-stages.cjs +219 -0
- package/scripts/check-hook-schema-compatibility.cjs +231 -0
- package/scripts/check-hsi-deps +42 -0
- package/scripts/check-kuzu-reintroduction.cjs +251 -0
- package/scripts/check-onboard +43 -0
- package/scripts/check-onboard-statusline.cjs +363 -0
- package/scripts/check-palette-consistency.cjs +106 -0
- package/scripts/check-pending-ambiguous.cjs +244 -0
- package/scripts/check-pending-breakthrough.cjs +189 -0
- package/scripts/check-pending-breakthrough.test.cjs +166 -0
- package/scripts/check-pending-naming-decision.cjs +97 -0
- package/scripts/check-plugin-path-anchoring.cjs +830 -0
- package/scripts/check-publish-needs.cjs +126 -0
- package/scripts/check-registry-drift.cjs +206 -0
- package/scripts/check-release-payload-ceiling.cjs +269 -0
- package/scripts/check-render-coverage.cjs +558 -0
- package/scripts/check-research-isomorphism.cjs +446 -0
- package/scripts/check-reward-before-investment.cjs +261 -0
- package/scripts/check-room-blueprints.cjs +213 -0
- package/scripts/check-schema-aliases.cjs +532 -0
- package/scripts/check-shape-declaration.cjs +958 -0
- package/scripts/check-shape-declaration.test.cjs +185 -0
- package/scripts/check-skill-spec.cjs +312 -0
- package/scripts/check-skill-vs-code-drift.cjs +178 -0
- package/scripts/check-substrate.cjs +513 -0
- package/scripts/check-tool-honesty.cjs +1601 -0
- package/scripts/check-update +60 -0
- package/scripts/check-version-and-sha.cjs +366 -0
- package/scripts/check-voice-style.cjs +184 -0
- package/scripts/check-worktree-hygiene.cjs +530 -0
- package/scripts/classify-insight +59 -0
- package/scripts/coherence-smoke-test.cjs +489 -0
- package/scripts/compute-bayesian-surprise.py +175 -0
- package/scripts/compute-blindspot-mass.py +209 -0
- package/scripts/compute-disruption-index.py +211 -0
- package/scripts/compute-element-novelty.py +254 -0
- package/scripts/compute-external-whitespace.py +563 -0
- package/scripts/compute-hsi.py +947 -0
- package/scripts/compute-meetings-intelligence +716 -0
- package/scripts/compute-opportunity-state +17 -0
- package/scripts/compute-state +393 -0
- package/scripts/compute-team +1112 -0
- package/scripts/compute-whitespace-embeddings.py +464 -0
- package/scripts/compute-whitespace-gaps.py +562 -0
- package/scripts/compute_topic_forest.py +684 -0
- package/scripts/consolidate-pinecone.py +265 -0
- package/scripts/context-monitor +943 -0
- package/scripts/create-speaker-profile +311 -0
- package/scripts/cross-room-detect.cjs +195 -0
- package/scripts/csv-to-idea-graph.cjs +755 -0
- package/scripts/ctxl-eval.cjs +846 -0
- package/scripts/curation-132-03-dedup-held-rename.cjs +395 -0
- package/scripts/curation-132-05-pseudonymize.cjs +189 -0
- package/scripts/derive-verb-reach-affinity.cjs +507 -0
- package/scripts/detect-reverse-salients.py +242 -0
- package/scripts/diagnostics-command.cjs +345 -0
- package/scripts/dial-memory-refresh-command.cjs +147 -0
- package/scripts/discover-analogy-whitespace.py +433 -0
- package/scripts/discover-hsi-whitespace.py +352 -0
- package/scripts/discover-rs-whitespace.py +327 -0
- package/scripts/discovery-cycle.cjs +625 -0
- package/scripts/disposition-render-v2.cjs +229 -0
- package/scripts/doctor-preflight-format.cjs +101 -0
- package/scripts/doctor.cjs +4327 -0
- package/scripts/dogfood-bridge/append-to-queue.sh +59 -0
- package/scripts/dogfood-bridge/path-filter.cjs +86 -0
- package/scripts/dogfood-derive.cjs +158 -0
- package/scripts/dogfood-emit.cjs +126 -0
- package/scripts/empathy-observation-emit.cjs +108 -0
- package/scripts/enrichment-queue-append.cjs +157 -0
- package/scripts/ensure-brain-baseline.cjs +117 -0
- package/scripts/entity-extract.cjs +1135 -0
- package/scripts/eureka-command.cjs +687 -0
- package/scripts/eureka-critic-run.cjs +662 -0
- package/scripts/eureka-portfolio-report.cjs +2038 -0
- package/scripts/eureka-room-report.cjs +419 -0
- package/scripts/explain-decision-command.cjs +499 -0
- package/scripts/export-pinecone-embeddings.cjs +516 -0
- package/scripts/extract-room-intelligence.cjs +714 -0
- package/scripts/fetch-brain-baseline.cjs +190 -0
- package/scripts/fetch-brain-baseline.py +360 -0
- package/scripts/feynman-minto-guardian.cjs +672 -0
- package/scripts/feynman-timeline-refresh-command.cjs +270 -0
- package/scripts/file-asset +253 -0
- package/scripts/fire-sensor-event.cjs +83 -0
- package/scripts/first-install-router.cjs +968 -0
- package/scripts/frontmatter-schema-validator.cjs +339 -0
- package/scripts/fts-index-drain.cjs +221 -0
- package/scripts/generate-chat-embed.cjs +179 -0
- package/scripts/generate-deck.cjs +322 -0
- package/scripts/generate-export.cjs +474 -0
- package/scripts/generate-hub.cjs +2553 -0
- package/scripts/generate-lobby.cjs +718 -0
- package/scripts/generate-presentation.cjs +988 -0
- package/scripts/generate-presentation.test.cjs +405 -0
- package/scripts/generate-section-intelligence.cjs +330 -0
- package/scripts/generate-snapshot.cjs +1410 -0
- package/scripts/generate-standalone +159 -0
- package/scripts/git-ops +211 -0
- package/scripts/graph-heal-pipeline.cjs +321 -0
- package/scripts/gsd-artifact-graph-hook.cjs +219 -0
- package/scripts/gsd-graph-derive-drain.cjs +571 -0
- package/scripts/gsd-graph-derive-sweep.cjs +195 -0
- package/scripts/heal-command.cjs +1365 -0
- package/scripts/hedge-refit-pipeline.cjs +207 -0
- package/scripts/help-renderer.cjs +303 -0
- package/scripts/hmi-compliance-poll.cjs +451 -0
- package/scripts/hmi-status-command.cjs +510 -0
- package/scripts/hooked-rescore-117.cjs +224 -0
- package/scripts/hooks/pre-commit +533 -0
- package/scripts/hooks/pre-commit-room-minto-guard.cmd +43 -0
- package/scripts/hooks/pre-commit-room-minto-guard.sh +533 -0
- package/scripts/hsi-to-graph.cjs +194 -0
- package/scripts/huji-batch.cjs +821 -0
- package/scripts/huji-eval.cjs +1158 -0
- package/scripts/huji-intake.cjs +563 -0
- package/scripts/huji-pin-smoketest.cjs +454 -0
- package/scripts/huji-run-one-async.cjs +305 -0
- package/scripts/huji-run-one.cjs +671 -0
- package/scripts/install-pre-commit.sh +113 -0
- package/scripts/intent-classifier +106 -0
- package/scripts/intent-classifier.cjs +3620 -0
- package/scripts/interpret-whitespace.cjs +691 -0
- package/scripts/jtbd-command.cjs +810 -0
- package/scripts/jtbd-update.cjs +299 -0
- package/scripts/label-topic-forest.cjs +667 -0
- package/scripts/learn-from-usage +168 -0
- package/scripts/lib/__pycache__/ensure_ml_deps.cpython-312.pyc +0 -0
- package/scripts/lib/ensure_ml_deps.py +97 -0
- package/scripts/load-embeddings-into-neo4j.cjs +569 -0
- package/scripts/memory-artifact-graph-hook.cjs +191 -0
- package/scripts/memory-command.cjs +687 -0
- package/scripts/memory-completion-detector.cjs +217 -0
- package/scripts/memory-lifecycle.cjs +620 -0
- package/scripts/memory-resume-nudge.cjs +233 -0
- package/scripts/methodology-ingest-local.sh +36 -0
- package/scripts/migrate-brain-mcp-from-http-to-stdio.cjs +190 -0
- package/scripts/migrate-brain-mcp-from-http-to-stdio.test.cjs +242 -0
- package/scripts/migrate-hats-to-roomdb.cjs +217 -0
- package/scripts/migrate-lazygraph.cjs +250 -0
- package/scripts/migrate-minto-schema-v88.cjs +296 -0
- package/scripts/migrate-plugin-root-refs.cjs +168 -0
- package/scripts/migrate-room-sections-v275.cjs +556 -0
- package/scripts/migrate-rooms +417 -0
- package/scripts/migrate-skill-frontmatter.cjs +472 -0
- package/scripts/migrate-stale-user-settings.cjs +296 -0
- package/scripts/migrate-telemetry-v1.cjs +384 -0
- package/scripts/migrate-telemetry-v1.test.cjs +356 -0
- package/scripts/minto-debouncer.cjs +428 -0
- package/scripts/mos-status.cjs +622 -0
- package/scripts/mva-detect.cjs +229 -0
- package/scripts/mva-run.cjs +36 -0
- package/scripts/on-agent-complete +163 -0
- package/scripts/on-cwd-changed +149 -0
- package/scripts/on-file-changed +68 -0
- package/scripts/on-stop +617 -0
- package/scripts/on-task-complete +194 -0
- package/scripts/operator-command.cjs +858 -0
- package/scripts/operator-update.cjs +308 -0
- package/scripts/part8-egress-guard-hook.cjs +229 -0
- package/scripts/pipeline-command.cjs +271 -0
- package/scripts/post-compact +383 -0
- package/scripts/post-update-activation.cjs +339 -0
- package/scripts/post-write +283 -0
- package/scripts/power-demo-prompt.md +621 -0
- package/scripts/pre-compact +273 -0
- package/scripts/preflight-auto-explore.cjs +314 -0
- package/scripts/preflight-doctor.cjs +225 -0
- package/scripts/preflight-release-drift.cjs +168 -0
- package/scripts/preflight-tension-surface.cjs +414 -0
- package/scripts/probe-brain-contract.cjs +502 -0
- package/scripts/publish-ops +346 -0
- package/scripts/query-efficiency-telemetry.cjs +364 -0
- package/scripts/query-semantic-scholar.cjs +545 -0
- package/scripts/reapply-modifications +49 -0
- package/scripts/reassign-primary.cjs +125 -0
- package/scripts/recompile-room-references.cjs +601 -0
- package/scripts/release-beta-preflight.sh +69 -0
- package/scripts/render-pdf +588 -0
- package/scripts/render-viz +190 -0
- package/scripts/research-speaker +239 -0
- package/scripts/resolve-room +204 -0
- package/scripts/restore-post-compact-context.cjs +320 -0
- package/scripts/room-auto-create-nudge.cjs +136 -0
- package/scripts/room-naming-selector.cjs +201 -0
- package/scripts/room-registry +767 -0
- package/scripts/rs-discovery-engine.cjs +725 -0
- package/scripts/rs-engine.py +2073 -0
- package/scripts/rs-experts-command.cjs +300 -0
- package/scripts/rs-explain-command.cjs +323 -0
- package/scripts/rs-fetch-command.cjs +170 -0
- package/scripts/rs-thesis-command.cjs +203 -0
- package/scripts/rs-vector-bridge.cjs +410 -0
- package/scripts/run-harness.cjs +890 -0
- package/scripts/scout-cadence-guard.cjs +323 -0
- package/scripts/scout-cadence-runner.cjs +509 -0
- package/scripts/scout-telemetry-aggregator.cjs +268 -0
- package/scripts/sealed-walker.py +96 -0
- package/scripts/seed-brain-commands.cjs +324 -0
- package/scripts/self-update +105 -0
- package/scripts/sentinel-deadline-monitor +241 -0
- package/scripts/sentinel-health-check +198 -0
- package/scripts/sentinel-snapshot +42 -0
- package/scripts/serve-dashboard +103 -0
- package/scripts/serve-dashboard-live +932 -0
- package/scripts/serve-presentation +97 -0
- package/scripts/serve-wiki +90 -0
- package/scripts/session-end-presence.cjs +79 -0
- package/scripts/session-start +1942 -0
- package/scripts/sessionstart-coordinator.cjs +249 -0
- package/scripts/sessionstart-npm-reconcile.cjs +111 -0
- package/scripts/sessionstart-post-update-preflight.cjs +303 -0
- package/scripts/sessionstart-reference-now-seed.cjs +137 -0
- package/scripts/setup-hooks.sh +112 -0
- package/scripts/skillopt-codereview.cjs +830 -0
- package/scripts/skillopt-eval.cjs +519 -0
- package/scripts/skillopt-funnel.cjs +1076 -0
- package/scripts/skillopt-genqueries.cjs +571 -0
- package/scripts/skillopt-inventory.cjs +319 -0
- package/scripts/skillopt-merge.cjs +656 -0
- package/scripts/skillopt-triggerloop.cjs +1126 -0
- package/scripts/soft-alias-runner.cjs +116 -0
- package/scripts/stamp-artifact-write.cjs +276 -0
- package/scripts/stamp-firing-block.cjs +325 -0
- package/scripts/state-write.cjs +97 -0
- package/scripts/statusline-fallback-echo.cjs +297 -0
- package/scripts/statusline-mos +116 -0
- package/scripts/statusline-mos-dispatch +127 -0
- package/scripts/suggest-next-command.cjs +416 -0
- package/scripts/sweep-skill-descriptions.cjs +263 -0
- package/scripts/sync-rooms-brain +438 -0
- package/scripts/sync-rooms-graph +264 -0
- package/scripts/telemetry-command-invocation.cjs +120 -0
- package/scripts/test-doctor-recovery.cjs +242 -0
- package/scripts/test-fresh-install.md +140 -0
- package/scripts/track-analytics +156 -0
- package/scripts/transcribe-audio +148 -0
- package/scripts/transcript-ingest.cjs +225 -0
- package/scripts/update-icm-index +153 -0
- package/scripts/v182-brain-optimize.cypher +1191 -0
- package/scripts/validate-model-profiles +93 -0
- package/scripts/vault-content-reformatter.cjs +581 -0
- package/scripts/vault-export-orchestrator.cjs +400 -0
- package/scripts/vault-footer-injector.cjs +315 -0
- package/scripts/vault-import.cjs +839 -0
- package/scripts/vault-integration-test.cjs +318 -0
- package/scripts/vault-regenerate-all.cjs +216 -0
- package/scripts/vault-regenerate-all.test.cjs +138 -0
- package/scripts/vault-rules-generator.cjs +220 -0
- package/scripts/vault-section-minto-generator.cjs +1642 -0
- package/scripts/vault-section-minto-generator.integration.test.cjs +576 -0
- package/scripts/vault-section-minto-generator.test.cjs +291 -0
- package/scripts/vault-section-state-generator.cjs +385 -0
- package/scripts/vault-welcome-generator.cjs +403 -0
- package/scripts/vault-wikilink-injector.cjs +449 -0
- package/scripts/verify-phase-132.cjs +338 -0
- package/scripts/verify-release +744 -0
- package/scripts/whitespace-command.cjs +831 -0
- package/scripts/whitespace-to-brain.cjs +383 -0
- package/scripts/whitespace-to-graph.cjs +220 -0
- package/scripts/wikilink-batch.cjs +102 -0
- package/scripts/wikilink-file.cjs +139 -0
- package/scripts/write-bridge-artifacts.cjs +140 -0
- package/scripts/write-scope-check +12 -0
- package/scripts/write-scope-check.cjs +461 -0
- package/scripts/write-whitespace-sections.cjs +483 -0
- package/settings.json +7 -0
- package/skills/act/SKILL.md +523 -0
- package/skills/admin/SKILL.md +437 -0
- package/skills/agentshield/SKILL.md +50 -0
- package/skills/analyze-needs/SKILL.md +74 -0
- package/skills/analyze-systems/SKILL.md +69 -0
- package/skills/analyze-timing/SKILL.md +94 -0
- package/skills/auto-explore/SKILL.md +84 -0
- package/skills/beautiful-question/SKILL.md +72 -0
- package/skills/bono/SKILL.md +146 -0
- package/skills/brain-connector/SKILL.md +206 -0
- package/skills/brain-derive/SKILL.md +140 -0
- package/skills/build-knowledge/SKILL.md +118 -0
- package/skills/build-thesis/SKILL.md +90 -0
- package/skills/causal/SKILL.md +265 -0
- package/skills/challenge-assumptions/SKILL.md +74 -0
- package/skills/client-discovery-interview/SKILL.md +94 -0
- package/skills/client-discovery-interview/discovery-scaffold.json +41 -0
- package/skills/client-discovery-interview/question-bank.md +149 -0
- package/skills/compare-ventures/SKILL.md +113 -0
- package/skills/context-engine/SKILL.md +169 -0
- package/skills/conversation-mode/SKILL.md +189 -0
- package/skills/correct-reference-now/SKILL.md +86 -0
- package/skills/dashboard/SKILL.md +142 -0
- package/skills/deck/SKILL.md +113 -0
- package/skills/deep-grade/SKILL.md +210 -0
- package/skills/diagnose/SKILL.md +128 -0
- package/skills/diagnostics/SKILL.md +193 -0
- package/skills/dial-memory-refresh/SKILL.md +116 -0
- package/skills/diffusion/SKILL.md +125 -0
- package/skills/discover/SKILL.md +216 -0
- package/skills/doctor/SKILL.md +269 -0
- package/skills/dogfood-flush/SKILL.md +133 -0
- package/skills/dominant-designs/SKILL.md +72 -0
- package/skills/eureka/SKILL.md +313 -0
- package/skills/explain-decision/SKILL.md +138 -0
- package/skills/explore-domains/SKILL.md +74 -0
- package/skills/explore-futures/SKILL.md +64 -0
- package/skills/explore-opportunity/SKILL.md +168 -0
- package/skills/explore-trends/SKILL.md +74 -0
- package/skills/export/SKILL.md +126 -0
- package/skills/feynman-timeline-refresh/SKILL.md +105 -0
- package/skills/file-meeting/SKILL.md +1190 -0
- package/skills/find-analogies/SKILL.md +360 -0
- package/skills/find-bottlenecks/SKILL.md +104 -0
- package/skills/find-connections/SKILL.md +108 -0
- package/skills/funding/SKILL.md +110 -0
- package/skills/futures/SKILL.md +124 -0
- package/skills/grade/SKILL.md +252 -0
- package/skills/grade-grant/SKILL.md +293 -0
- package/skills/graph/SKILL.md +216 -0
- package/skills/hat-briefing/SKILL.md +160 -0
- package/skills/heal/SKILL.md +69 -0
- package/skills/help/SKILL.md +172 -0
- package/skills/hmi-status/SKILL.md +73 -0
- package/skills/ignite/SKILL.md +225 -0
- package/skills/ingest-methodology/SKILL.md +125 -0
- package/skills/intel-pipeline/SKILL.md +128 -0
- package/skills/intelligence-orchestrator/SKILL.md +262 -0
- package/skills/jtbd/SKILL.md +261 -0
- package/skills/larry-personality/SKILL.md +570 -0
- package/skills/larry-personality/framework-chains.md +92 -0
- package/skills/larry-personality/mode-engine.md +185 -0
- package/skills/leadership/SKILL.md +109 -0
- package/skills/lean-canvas/SKILL.md +70 -0
- package/skills/macro-trends/SKILL.md +84 -0
- package/skills/map-unknowns/SKILL.md +72 -0
- package/skills/memory/SKILL.md +212 -0
- package/skills/memory-cortex-reach/SKILL.md +80 -0
- package/skills/models/SKILL.md +197 -0
- package/skills/mos/SKILL.md +160 -0
- package/skills/mos-deck-engine/SKILL.md +329 -0
- package/skills/mos-reason/SKILL.md +484 -0
- package/skills/mullins/SKILL.md +151 -0
- package/skills/mullins-scaffold/SKILL.md +132 -0
- package/skills/mullins-scaffold/scaffold.json +223 -0
- package/skills/mva-brief/SKILL.md +87 -0
- package/skills/mva-option/SKILL.md +120 -0
- package/skills/mva-pipeline/SKILL.md +158 -0
- package/skills/mva-report/SKILL.md +79 -0
- package/skills/new-project/SKILL.md +629 -0
- package/skills/new-surface/SKILL.md +112 -0
- package/skills/onboard/SKILL.md +485 -0
- package/skills/operator/SKILL.md +167 -0
- package/skills/opportunities/SKILL.md +171 -0
- package/skills/organize/SKILL.md +69 -0
- package/skills/persona/SKILL.md +272 -0
- package/skills/pipeline/SKILL.md +164 -0
- package/skills/present/SKILL.md +123 -0
- package/skills/publish/SKILL.md +224 -0
- package/skills/pws-brain/SKILL.md +152 -0
- package/skills/pws-methodology/SKILL.md +92 -0
- package/skills/qualify-opportunity/SKILL.md +111 -0
- package/skills/query/SKILL.md +68 -0
- package/skills/radar/SKILL.md +106 -0
- package/skills/reanalyze/SKILL.md +122 -0
- package/skills/research/SKILL.md +462 -0
- package/skills/room/SKILL.md +381 -0
- package/skills/room-passive/SKILL.md +174 -0
- package/skills/room-proactive/SKILL.md +344 -0
- package/skills/rooms/SKILL.md +678 -0
- package/skills/root-cause/SKILL.md +73 -0
- package/skills/rs-experts/SKILL.md +151 -0
- package/skills/rs-explain/SKILL.md +130 -0
- package/skills/rs-fetch/SKILL.md +115 -0
- package/skills/rs-thesis/SKILL.md +129 -0
- package/skills/scenario-plan/SKILL.md +76 -0
- package/skills/scheduled-tasks/SKILL.md +301 -0
- package/skills/score-innovation/SKILL.md +74 -0
- package/skills/scout/SKILL.md +370 -0
- package/skills/setup/SKILL.md +654 -0
- package/skills/show/SKILL.md +68 -0
- package/skills/skill/SKILL.md +104 -0
- package/skills/snapshot/SKILL.md +178 -0
- package/skills/speakers/SKILL.md +116 -0
- package/skills/splash/SKILL.md +54 -0
- package/skills/stance/SKILL.md +89 -0
- package/skills/status/SKILL.md +107 -0
- package/skills/structure-argument/SKILL.md +78 -0
- package/skills/suggest-next/SKILL.md +137 -0
- package/skills/systems-thinking/SKILL.md +220 -0
- package/skills/think-hats/SKILL.md +82 -0
- package/skills/trending-to-absurd/SKILL.md +100 -0
- package/skills/ui-system/SKILL.md +474 -0
- package/skills/ui-system/design-system/M-OS-DESIGN-SYSTEM.md +273 -0
- package/skills/ui-system/design-system/SPEC.md +72 -0
- package/skills/ui-system/design-system/image-prompt-style.md +29 -0
- package/skills/ui-system/design-system/mos-design-system.css +240 -0
- package/skills/ui-system/rules/design-system.md +60 -0
- package/skills/ui-system/rules/dual-palette.md +156 -0
- package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
- package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
- package/skills/update/SKILL.md +254 -0
- package/skills/user-needs/SKILL.md +73 -0
- package/skills/validate/SKILL.md +73 -0
- package/skills/value-proposition/SKILL.md +141 -0
- package/skills/vault/SKILL.md +326 -0
- package/skills/visualize/SKILL.md +69 -0
- package/skills/whitespace/SKILL.md +539 -0
- package/skills/wiki/SKILL.md +94 -0
- package/templates/_base.html +20 -0
- package/templates/chat-panel.html +205 -0
- package/templates/constellation-chat-hooks.js +89 -0
- package/templates/constellation.html +1272 -0
- package/templates/destijl-base.css +268 -0
- package/templates/discovery/discovery-brief-template.md +97 -0
- package/templates/due-diligence.html +49 -0
- package/templates/executive-summary.html +40 -0
- package/templates/icm/CLAUDE.md +31 -0
- package/templates/icm/GROUP-CLAUDE.md +33 -0
- package/templates/icm/INDEX.md +26 -0
- package/templates/import/stage-contracts/01-ingest.md +34 -0
- package/templates/import/stage-contracts/02-classify.md +48 -0
- package/templates/import/stage-contracts/03-route.md +42 -0
- package/templates/import/stage-contracts/04-enrich.md +40 -0
- package/templates/investment-thesis.html +25 -0
- package/templates/meeting-report.html +361 -0
- package/templates/presentation/dashboard.html +1150 -0
- package/templates/presentation/deck.html +540 -0
- package/templates/presentation/diagrams.html +437 -0
- package/templates/presentation/graph.html +391 -0
- package/templates/presentation/insights.html +548 -0
- package/templates/presentation/wiki.html +533 -0
- package/templates/profile.html +136 -0
- package/templates/room-context/last-session.md +56 -0
- package/templates/room-context/methodology-history.md +28 -0
- package/templates/room-context/rejection-log.md +25 -0
- package/templates/room-context/weekly-digest.md +54 -0
- package/templates/room-skeleton/MINTO.md.tmpl +19 -0
- package/templates/room-skeleton/ROOM.md.identity.tmpl +13 -0
- package/templates/room-skeleton/ROOM.md.section.tmpl +33 -0
- package/templates/room-skeleton/STATE.md.tmpl +33 -0
- package/templates/room-skeleton/USER.md.tmpl +23 -0
- package/templates/room-skeleton/references/.gitkeep +0 -0
- package/templates/room-skeleton/references/SECTION-SCHEMA.md +213 -0
- package/templates/room-skeleton/references/SUB-SCHEMAS.md +91 -0
- package/templates/room-skeleton/section-contracts/.gitkeep +0 -0
- package/templates/room-skeleton/section-contracts/business-model.md +32 -0
- package/templates/room-skeleton/section-contracts/competitive-analysis.md +33 -0
- package/templates/room-skeleton/section-contracts/financial-model.md +30 -0
- package/templates/room-skeleton/section-contracts/funding.md +41 -0
- package/templates/room-skeleton/section-contracts/legal-ip.md +30 -0
- package/templates/room-skeleton/section-contracts/market-analysis.md +32 -0
- package/templates/room-skeleton/section-contracts/opportunity-bank.md +39 -0
- package/templates/room-skeleton/section-contracts/problem-definition.md +32 -0
- package/templates/room-skeleton/section-contracts/solution-design.md +36 -0
- package/templates/room-skeleton/section-contracts/strategy.md +35 -0
- package/templates/room-skeleton/section-contracts/team-execution.md +36 -0
- package/templates/shared.css +427 -0
- package/templates/showcase/blueprint.html +432 -0
- package/templates/showcase/library.html +571 -0
- package/templates/showcase/narrative.html +361 -0
- package/templates/showcase/overview.html +468 -0
- package/templates/showcase/synthesis.html +473 -0
- 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
|
+
}
|