@mindrian_os/cli 2.0.0-beta.3 → 2.0.0-beta.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +14 -0
- package/CHANGELOG.md +497 -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 +2621 -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 +230 -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 +566 -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 +563 -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 +551 -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 +2115 -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 +224 -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/collect-cold-install-evidence.cjs +411 -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 +1943 -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-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 +384 -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 +571 -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,1809 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* Phase 179-01 -- the GA-4 card-fire interceptor (the R-1 cure, Wave 1)
|
|
6
|
+
* =====================================================================
|
|
7
|
+
* A Stop-hook-class turn-scan that moves card-fire enforcement BELOW the agent.
|
|
8
|
+
*
|
|
9
|
+
* R15 (Phase 178) proves a gate surface is WIRED to emit a card; it cannot force the
|
|
10
|
+
* model to FIRE the card at runtime (the named R-1 residual). A prose fence shipped
|
|
11
|
+
* (commit e22b9ea4) and the agent ignored it. This interceptor is the structural cure:
|
|
12
|
+
* a deterministic turn-scan the LLM has no say in. On a reached-Decision-Gate turn with
|
|
13
|
+
* no fired AskUserQuestion card it emits a Stop-block (decision:'block') +
|
|
14
|
+
* additionalContext re-prompt forcing the card; after MAX_FORCE_RETRIES bounded retries
|
|
15
|
+
* it degrades gracefully (log + allow) so a genuinely card-incapable surface cannot trap
|
|
16
|
+
* the navigator.
|
|
17
|
+
*
|
|
18
|
+
* Hook class: this runs on the Stop event (hooks/hooks.json Stop block), NOT PostToolUse.
|
|
19
|
+
* The block route is a JSON envelope `{ decision:'block', continue:false, ... }` written
|
|
20
|
+
* to stdout on exit 0 -- a valid Stop-block path (the Claude Code Stop hook honors a
|
|
21
|
+
* decision:'block' envelope to re-prompt the turn). It is NOT an exit-2 block; earlier
|
|
22
|
+
* drafts of this header and 179-CONTEXT called it an "exit-2 PostToolUse interceptor" --
|
|
23
|
+
* that was doc drift, reconciled here (IN-01) to match the shipped Stop-block-on-exit-0
|
|
24
|
+
* route.
|
|
25
|
+
*
|
|
26
|
+
* Stop-stdin contract (BL-01, the live cure): the real Claude Code Stop hook delivers
|
|
27
|
+
* `{ hook_event_name, transcript_path, stop_hook_active, session_id, ... }` -- it does
|
|
28
|
+
* NOT deliver output_text / ran_entries / askuserquestion_fired directly. The interceptor
|
|
29
|
+
* READS the turn by parsing `transcript_path` (mirroring scripts/on-stop:33): it extracts
|
|
30
|
+
* the LAST assistant message text (drives the BACKSTOP) and scans the transcript's
|
|
31
|
+
* tool-use records for an AskUserQuestion invocation (drives askuserquestion_fired). If
|
|
32
|
+
* the envelope already carries the normalized signals directly (the unit-test shape),
|
|
33
|
+
* those are used as-is (backward-compatible). An unreadable/missing transcript yields
|
|
34
|
+
* empty signals -> a safe no-op (never throws, never blocks spuriously).
|
|
35
|
+
*
|
|
36
|
+
* Detection (CONTEXT decision 1: registry-keyed PRIMARY + output-text BACKSTOP):
|
|
37
|
+
* PRIMARY (DEFERRED -- see doctrine note below) -- a render-coverage-registry
|
|
38
|
+
* gate-reaching surface (data/render-coverage-registry.json entries[] with
|
|
39
|
+
* render_coverage 'card-emission') ran this turn AND no AskUserQuestion
|
|
40
|
+
* tool-call fired. The enumeration is DERIVED from the registry, never
|
|
41
|
+
* hand-maintained.
|
|
42
|
+
* BACKSTOP (LIVE -- the detector this phase ships) -- the turn output text carries the
|
|
43
|
+
* ASCII-box gate glyphs (the `[1] [2] [3]` / "type 1, 2, or 3" anti-pattern)
|
|
44
|
+
* with no fired card -- intercepted even for an OFF-registry surface.
|
|
45
|
+
*
|
|
46
|
+
* DOCTRINE UPDATE (Phase 209-06, H3): PRIMARY IS LIVE. WR-04 (below, historical record)
|
|
47
|
+
* documented PRIMARY as deferred because no producer of `ran_entries` / `reached_gate_entries`
|
|
48
|
+
* existed. That producer now exists: lib/core/card-fire-sidechannel.cjs (recordReachedGate /
|
|
49
|
+
* readReachedGates), wired at the THREE places that mint a Shape-F gate envelope --
|
|
50
|
+
* lib/hmi/selector-dispatcher.cjs's pickShape trailer door (gated on payload.emitTelemetry
|
|
51
|
+
* === true, Canon Part 8 fs_scope), and scripts/intent-classifier.cjs's engine arm +
|
|
52
|
+
* emitBindingGate (F.8). deriveTurnSignals (below) now reads the side file via
|
|
53
|
+
* readReachedGates(session_id) whenever the envelope does NOT already carry ran_entries /
|
|
54
|
+
* reached_gate_entries directly (direct-field envelopes -- the unit-test shape -- keep
|
|
55
|
+
* PRECEDENCE, so the 22 existing predicate assertions stay byte-identical). The BACKSTOP
|
|
56
|
+
* (transcript ASCII-box text detection) stays SECONDARY PERMANENTLY -- it is the only
|
|
57
|
+
* detector that still works when the side-channel writer itself fails (a missing, corrupt,
|
|
58
|
+
* or oversized side-file all degrade to [] inside readReachedGates, never throwing here).
|
|
59
|
+
*
|
|
60
|
+
* WR-04 (historical, PRE-209-06): "PRIMARY is DEFERRED, not live. PRIMARY keys off a
|
|
61
|
+
* `ran_entries` / `reached_gate_entries` set that records which registry gate-reaching
|
|
62
|
+
* surfaces ran this turn. A Stop-hook transcript yields assistant TEXT (the BACKSTOP
|
|
63
|
+
* signal), NOT a reached-entries SET, and a repo-wide grep finds ZERO producers of
|
|
64
|
+
* `ran_entries` / `reached_gate_entries` outside this file and its tests. So until a
|
|
65
|
+
* side-channel writer exists ... PRIMARY has no live source and stays INERT." -- superseded
|
|
66
|
+
* by the DOCTRINE UPDATE above; retained verbatim for the historical record.
|
|
67
|
+
*
|
|
68
|
+
* Bounded escape (T-179-01 DoS mitigation): a LOCAL retry side-file
|
|
69
|
+
* (~/.mindrian/card-fire-retries.json) keyed by a TRANSCRIPT-GROWTH-INVARIANT turn identity
|
|
70
|
+
* (the Stop stdin session_id + a stable gate identity) counts consecutive intercepts. The
|
|
71
|
+
* gate identity is the GATE-IDENTIFYING CONTENT of the LAST assistant message -- the matched
|
|
72
|
+
* ASCII-box glyph span plus the NORMALIZED option-label token SET (lowercased, prose and
|
|
73
|
+
* whitespace stripped, labels sorted). The key is invariant across BOTH (WR-01) a re-worded
|
|
74
|
+
* re-prompt (the gate signature anchors on the glyph + the option-label SET, not the full
|
|
75
|
+
* message prose) AND (CR-02 / CR-03) a GROWING transcript: the model appends an assistant
|
|
76
|
+
* turn every time it re-emits the blocked gate, so any transcript-LENGTH value (e.g. an
|
|
77
|
+
* assistant-message counter) would mint a fresh key every retry and the bounded escape would
|
|
78
|
+
* NEVER converge -- a production livelock. The gate-content signature stays FIXED across all
|
|
79
|
+
* assistant re-emissions of the same stuck gate, so consecutive intercepts of the SAME gate
|
|
80
|
+
* increment the SAME counter and MAX_FORCE_RETRIES is actually reachable.
|
|
81
|
+
*
|
|
82
|
+
* CR-03 (the root fix this wave ships): the key is anchored on the GATE, never the user
|
|
83
|
+
* message. The prior fix anchored on the LAST USER message, which is EMPTY when the box turn
|
|
84
|
+
* has no preceding role:user record (a compaction type:'summary' lead, OR this codebase's own
|
|
85
|
+
* Phase 114/117 auto-fire-before-the-user-types) -- the empty anchor then fell back to
|
|
86
|
+
* assistantCount (the growing counter) and the bounded escape livelocked again. Anchoring on
|
|
87
|
+
* the gate-identifying content has NO such hole: it is present whenever a gate is detected
|
|
88
|
+
* (the detection IS the gate content), it is growth-invariant, and two genuinely-different
|
|
89
|
+
* gates get genuinely-different counters (WR-07: no cross-gate budget bleed). The
|
|
90
|
+
* assistantCount / gate_turn_index fallback is DELETED from the key derivation entirely; if
|
|
91
|
+
* the gate signature is somehow empty (degenerate), the key falls back to session_id ALONE
|
|
92
|
+
* (coarse but ALWAYS converges -- any consecutive intercept in the session counts up), NEVER
|
|
93
|
+
* to a growing value. A growing value is unreachable as the key.
|
|
94
|
+
*
|
|
95
|
+
* The store is pruned on write by a TTL (WR-02: each entry carries { count, ts }; entries
|
|
96
|
+
* older than RETRY_TTL_MS are evicted, so the side-file cannot grow without bound). At
|
|
97
|
+
* MAX_FORCE_RETRIES the predicate returns degrade=true and the envelope is
|
|
98
|
+
* { continue: true, suppressOutput: true } (log to stderr, allow the turn).
|
|
99
|
+
*
|
|
100
|
+
* CR-04 (the TERMINAL convergence floor -- the session-wide intercept ceiling): the per-gate
|
|
101
|
+
* counter above keys on the gate-identifying CONTENT (gate_signature), which is MODEL-controlled.
|
|
102
|
+
* When the model re-emits the SAME stuck gate with DIFFERENT option labels each retry (the most
|
|
103
|
+
* natural LLM behavior under this interceptor's own "re-emit this turn" re-prompt), the per-gate
|
|
104
|
+
* signature FLAPS to a fresh key every retry, no single per-gate key ever reaches
|
|
105
|
+
* MAX_FORCE_RETRIES, and the bounded escape livelocks forever. ANY key derived from model-emitted
|
|
106
|
+
* content can be flapped; the ONLY identity that cannot be flapped is the SESSION itself. So a
|
|
107
|
+
* SECOND, session-wide counter keyed on session_id ALONE (content-INDEPENDENT, stored at
|
|
108
|
+
* `__session__:<id>` in the same TTL-pruned side-file) is bumped on EVERY intercept, with its own
|
|
109
|
+
* hard ceiling MAX_SESSION_INTERCEPTS (set generously above the per-gate cap so normal multi-gate
|
|
110
|
+
* sessions are unaffected). The predicate degrades if EITHER ceiling is reached. The session
|
|
111
|
+
* ceiling is the load-bearing guarantee: a continuous run of intercepts in ONE session is bounded
|
|
112
|
+
* by MAX_SESSION_INTERCEPTS REGARDLESS of how the gate content / per-gate key flaps. A degrade or
|
|
113
|
+
* a no-intercept Stop turn resets the session counter (the navigator got unstuck).
|
|
114
|
+
*
|
|
115
|
+
* WR-08 (transcript read cap): readTranscriptTurn reads only the LAST TRANSCRIPT_TAIL_BYTES
|
|
116
|
+
* of the transcript file (a pathological multi-hundred-MB transcript cannot stall the 3000ms
|
|
117
|
+
* hook). The detection semantics are unchanged -- the LAST assistant message and the gate
|
|
118
|
+
* signature both live at the tail; a partial leading line after the byte cut is dropped as a
|
|
119
|
+
* malformed JSONL line by the existing per-line try/catch.
|
|
120
|
+
*
|
|
121
|
+
* Envelope schema (Phase 95 BASH-95-01 invariant): top-level keys are a subset of
|
|
122
|
+
* { decision, reason, continue, stopReason, suppressOutput, systemMessage,
|
|
123
|
+
* hookSpecificOutput }. additionalContext lives ONLY inside hookSpecificOutput.
|
|
124
|
+
*
|
|
125
|
+
* Canon Part 8 (The Graph Boundary): LOCAL-only. Reads the local registry + the hook
|
|
126
|
+
* stdin envelope + the LOCAL transcript file (transcript_path is a local path, same as
|
|
127
|
+
* scripts/on-stop reads it) + the local retry side-file; makes ZERO Brain reads/writes
|
|
128
|
+
* and ZERO network calls. The interceptor opens no remote wire and loads no Brain module.
|
|
129
|
+
* The transcript content the interceptor reads (an untrusted model turn) stays LOCAL and
|
|
130
|
+
* is NEVER echoed to Brain or sent anywhere -- it is read, scanned for glyphs/tool-use,
|
|
131
|
+
* and discarded.
|
|
132
|
+
*
|
|
133
|
+
* Additive only: mints no reach / posture / edge / node; the frozen Part 3 contracts
|
|
134
|
+
* (MAX_K=3, DIAL_REACH_K=6, the 0.70/0.15 gate, the 6-reach bank, the glyphs) are
|
|
135
|
+
* untouched.
|
|
136
|
+
*
|
|
137
|
+
* Defensive: any internal error -> stderr + { continue: true, suppressOutput: true } +
|
|
138
|
+
* exit 0. The interceptor NEVER blocks the hook chain on a bug.
|
|
139
|
+
*
|
|
140
|
+
* House rule: hyphens only, no em-dashes. CJS module, Node built-ins only.
|
|
141
|
+
*
|
|
142
|
+
* License: BSL 1.1.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
'use strict';
|
|
146
|
+
|
|
147
|
+
const fs = require('node:fs');
|
|
148
|
+
const path = require('node:path');
|
|
149
|
+
const os = require('node:os');
|
|
150
|
+
const crypto = require('node:crypto');
|
|
151
|
+
|
|
152
|
+
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
153
|
+
|
|
154
|
+
// Phase 210-05 (item 210-E-1): the shared relevance predicate module. It owns the
|
|
155
|
+
// option-label extraction (MOVED out of gateSignature below so there is exactly one
|
|
156
|
+
// implementation) plus the two relevance verdicts classifyCardFire consults before
|
|
157
|
+
// its final intercept. Pure LOCAL string work, never throws (Part 8).
|
|
158
|
+
const gateRelevance = require(path.join(PLUGIN_ROOT, 'lib', 'core', 'gate-relevance.cjs'));
|
|
159
|
+
|
|
160
|
+
// Phase 298-03: the transcript reader (readTranscriptTurn/readTranscriptTail plus the
|
|
161
|
+
// four helper functions it used) moved to lib/hmi/turn-text.cjs, the single transcript
|
|
162
|
+
// reader shared with a later Stop-hook consumer (298-07). This file keeps
|
|
163
|
+
// gateSignature/matchedGlyphSpan (card-fire-domain logic that needs gateRelevance
|
|
164
|
+
// above) and recomputes askuserquestion_fired + gate_signature locally from what
|
|
165
|
+
// turn-text.cjs returns.
|
|
166
|
+
const turnText = require('../lib/hmi/turn-text.cjs');
|
|
167
|
+
|
|
168
|
+
// 238-05 Task 2 (GATE-03 half B, lost-update fix): NOTE this file does NOT
|
|
169
|
+
// require lib/core/write-lock.cjs. D-05 called for reusing its acquireLock/
|
|
170
|
+
// releaseLock unchanged; adversarial testing found a real TOCTOU race in
|
|
171
|
+
// that shared primitive under this file's higher-frequency usage pattern
|
|
172
|
+
// (full rationale and measurements at the withRetryStoreLock definition
|
|
173
|
+
// below, near line 760). The retry-store fence is a separate, local, more
|
|
174
|
+
// rigorously verified lock confined to this file; lib/core/write-lock.cjs
|
|
175
|
+
// and its other consumers (SQLite write-safety) are untouched.
|
|
176
|
+
|
|
177
|
+
// data/render-coverage-registry.json is the Phase 178 R15 substrate. The interceptor
|
|
178
|
+
// keys PRIMARY detection off its card-emission entries; it does NOT hand-maintain a
|
|
179
|
+
// parallel gate list.
|
|
180
|
+
const REGISTRY_PATH = path.join(PLUGIN_ROOT, 'data', 'render-coverage-registry.json');
|
|
181
|
+
|
|
182
|
+
// The LOCAL retry side-file for the bounded escape (Part 8 LOCAL-only).
|
|
183
|
+
function retryFilePath() {
|
|
184
|
+
const home = process.env.MINDRIAN_HOME || path.join(os.homedir(), '.mindrian');
|
|
185
|
+
return path.join(home, 'card-fire-retries.json');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// CR-07: the LOCAL append-only diagnostic log path. Resolved the SAME way as
|
|
189
|
+
// retryFilePath (MINDRIAN_HOME override honored) so a test / ops re-point isolates both
|
|
190
|
+
// files identically. Part 8 LOCAL-only: this file is written to local disk (~/.mindrian),
|
|
191
|
+
// never to the Brain and never over any network wire.
|
|
192
|
+
function interceptLogPath() {
|
|
193
|
+
const home = process.env.MINDRIAN_HOME || path.join(os.homedir(), '.mindrian');
|
|
194
|
+
return path.join(home, 'card-fire-intercepts.log');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The bounded-escape ceiling (named constant). After this many consecutive intercepts
|
|
198
|
+
// on the same turn-context (the per-gate key), degrade to log + allow so a card-incapable
|
|
199
|
+
// surface cannot trap the navigator. This is the per-gate counter: it gives good UX (each
|
|
200
|
+
// genuinely-distinct gate gets forced up to N times before we give up on it).
|
|
201
|
+
const MAX_FORCE_RETRIES = 3;
|
|
202
|
+
|
|
203
|
+
// CR-04 (the TERMINAL convergence floor): the SESSION-WIDE intercept ceiling. The per-gate
|
|
204
|
+
// counter above keys on the gate-identifying CONTENT (gate_signature), which is MODEL-
|
|
205
|
+
// CONTROLLED: when the model re-emits the SAME stuck gate with DIFFERENT option labels each
|
|
206
|
+
// retry (the single most natural LLM behavior under this interceptor's own "re-emit this
|
|
207
|
+
// turn" re-prompt), the per-gate signature FLAPS to a fresh key every retry, no single per-
|
|
208
|
+
// gate key ever reaches MAX_FORCE_RETRIES, and the bounded escape LIVELOCKS forever. ANY key
|
|
209
|
+
// derived from model-emitted content can be flapped; the ONLY identity that cannot be flapped
|
|
210
|
+
// is the SESSION itself. So we add a second, session-wide counter keyed on session_id ALONE
|
|
211
|
+
// (content-INDEPENDENT, un-flappable) with its own hard ceiling. On each intercept we bump
|
|
212
|
+
// BOTH counters and degrade if EITHER ceiling is reached. The session ceiling is the load-
|
|
213
|
+
// bearing guarantee: an unbroken run of intercepts in ONE session is bounded by
|
|
214
|
+
// MAX_SESSION_INTERCEPTS REGARDLESS of how the gate content / per-gate key flaps.
|
|
215
|
+
//
|
|
216
|
+
// It is set GENEROUSLY above the per-gate cap (4x MAX_FORCE_RETRIES, well above 2x) so that
|
|
217
|
+
// a normal multi-gate session is unaffected: the per-gate degrade fires first for each
|
|
218
|
+
// genuinely-distinct gate (WR-07's two-distinct-gates case degrades per-gate long before the
|
|
219
|
+
// session total reaches 12), and ONLY a flapping single-gate livelock -- which the per-gate
|
|
220
|
+
// counter cannot catch -- is bounded by this ceiling.
|
|
221
|
+
const MAX_SESSION_INTERCEPTS = 12;
|
|
222
|
+
|
|
223
|
+
// Phase 238-08 (GATE-04, D-15): local mirrors of lib/core/card-fire-sidechannel.cjs's
|
|
224
|
+
// HEALTH_OK / HEALTH_UNAVAILABLE values. Duplicated deliberately, not re-derived via a
|
|
225
|
+
// require, because the one case that needs a value here (deriveTurnSignals's own
|
|
226
|
+
// require-failure degrade branch, below) is exactly the case where that module's
|
|
227
|
+
// require has already failed and its exported constant is unreachable. Plain string
|
|
228
|
+
// literals so classifyCardFire's D-16 comparison never needs to require the module
|
|
229
|
+
// just to compare an enum value.
|
|
230
|
+
const SIDECHANNEL_HEALTH_OK = 'healthy';
|
|
231
|
+
const SIDECHANNEL_HEALTH_UNAVAILABLE = 'unavailable';
|
|
232
|
+
|
|
233
|
+
// The session-counter key prefix in the shared side-file. A session entry lives at
|
|
234
|
+
// `__session__:<session_id>` alongside the per-gate `<ctxHash>` entries; both are { count, ts }
|
|
235
|
+
// shaped and both are TTL-pruned identically (WR-02), so the side-file still cannot grow
|
|
236
|
+
// without bound. The `__session__:` prefix cannot collide with a per-gate ctxHash (a 16-hex
|
|
237
|
+
// sha256 slice never starts with `__session__:`).
|
|
238
|
+
const SESSION_KEY_PREFIX = '__session__:';
|
|
239
|
+
|
|
240
|
+
// WR-02: the retry side-file TTL. Each store entry carries { count, ts }; on every write
|
|
241
|
+
// entries older than this are evicted, so the LOCAL ~/.mindrian/card-fire-retries.json
|
|
242
|
+
// cannot grow without bound over a long-lived install. 24h is generous: a stuck gate
|
|
243
|
+
// resolves within one turn; an abandoned key is dead long before a day passes.
|
|
244
|
+
const RETRY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
245
|
+
|
|
246
|
+
// CR-07 (the LOCAL intercept diagnostic log -- backstop-benign-list-defeats-relevance-gate
|
|
247
|
+
// item 2b, feeding live-session-running-stale-plugin-cache-fixes-inert's still-open
|
|
248
|
+
// "unexplained backstop trigger" mystery). The truncation cap for output_text written to
|
|
249
|
+
// the log per record, so a pathological turn cannot bloat one log line. TTL-pruned by the
|
|
250
|
+
// SAME RETRY_TTL_MS as the retry side-file (WR-02) so it cannot grow without bound.
|
|
251
|
+
const INTERCEPT_LOG_TEXT_CAP = 4000;
|
|
252
|
+
|
|
253
|
+
// WR-08 (298-03): the transcript-read tail cap moved to lib/hmi/turn-text.cjs
|
|
254
|
+
// (TRANSCRIPT_TAIL_BYTES) along with readTranscriptTail itself; re-exported below as
|
|
255
|
+
// turnText.TRANSCRIPT_TAIL_BYTES so this file's own export name is unchanged.
|
|
256
|
+
|
|
257
|
+
// The ASCII-box gate glyphs / literal anti-pattern the BACKSTOP scans for. A reached
|
|
258
|
+
// gate that renders as flat text instead of firing the card carries these.
|
|
259
|
+
//
|
|
260
|
+
// Phase 209-07 (H1): tuned after H3 PRIMARY (209-06) went live, per the LOCKED wave
|
|
261
|
+
// order (tune the floor only once the native path validates which branches still
|
|
262
|
+
// fire). Four alternatives:
|
|
263
|
+
// 1. Same-line labeled box: `[1] ... [2] ...` (unchanged from Wave 1).
|
|
264
|
+
// 2. The literal `type 1, 2, or 3` exemplar (unchanged from Wave 1).
|
|
265
|
+
// 3. Multiline labeled box: `[1] ...\n...\n[2] ...` (NEW -- verdict 25's exact
|
|
266
|
+
// false-negative shape: a gate whose options wrap onto separate lines).
|
|
267
|
+
// 4. Numbered-prose gate: a line-anchored `1. <option>` followed by `2. <option>`
|
|
268
|
+
// within a 3-line span (NEW -- the incident's "hand-rolled numbered list
|
|
269
|
+
// instead of a card" shape). `(?:^|\n)` substitutes for per-line anchoring
|
|
270
|
+
// without an `m` flag, so this alternative matches anywhere inside a larger
|
|
271
|
+
// message, not only at the string's absolute start.
|
|
272
|
+
// The bare U+25A0 alternative is DELETED: a lone U+25A0 in a status/dial row with
|
|
273
|
+
// no option structure is SANCTIONED plugin UI vocabulary (selector-dispatcher.cjs:256,
|
|
274
|
+
// ui-system SKILL.md:131, dial-presenter.cjs:134) and must never intercept a turn on
|
|
275
|
+
// its own. A U+25A0 appearing WITHIN one of the four gate-shaped alternatives above
|
|
276
|
+
// still counts (it is part of the matched gate content, not a bare occurrence).
|
|
277
|
+
const ASCII_BOX_GLYPH_RE =
|
|
278
|
+
/\[\s*1\s*\]\s*.*\[\s*2\s*\]|type\s+1\s*,\s*2\s*,\s*or\s+3|\[\s*1\s*\][^\n]*\n[\s\S]*?\[\s*2\s*\]|(?:^|\n)\s{0,3}1[.)]\s+\S[^\n]*\n(?:[^\n]*\n)?\s{0,3}2[.)]\s+\S/i;
|
|
279
|
+
|
|
280
|
+
// backstop-numbered-prose-retired (card-fire-relevance-check-gap, navigator decision
|
|
281
|
+
// 2026-07-17 "widen scope: fix both mechanisms"): ASCII_BOX_GLYPH_RE above stays
|
|
282
|
+
// BYTE-IDENTICAL (the retry-key signature in gateSignature and the Phase 209 regex-matrix
|
|
283
|
+
// tests both depend on it verbatim). The BACKSTOP intercept now keys on ONE tier only:
|
|
284
|
+
// - UNCONDITIONAL (alternatives 1-3: same-line bracket box, the "type 1, 2, or 3"
|
|
285
|
+
// literal, the multiline bracket box). These are shape-specific enough that ordinary
|
|
286
|
+
// prose practically never renders them by accident, so a match alone is a backstop hit.
|
|
287
|
+
// The Phase 209-07 bare numbered-prose alternative (formerly ASCII_BOX_NUMBERED_PROSE_RE,
|
|
288
|
+
// gated by GATE_FRAMING_RE within GATE_FRAMING_WINDOW) is RETIRED. Live evidence -- the 7
|
|
289
|
+
// real backstop fires in ~/.mindrian/card-fire-intercepts.log -- showed the framing
|
|
290
|
+
// co-requirement could not separate the ONE genuine fork (cfec3113, "Two honest paths --
|
|
291
|
+
// pick one: build vs file") from the SIX false positives, an ~86% false-positive net: pairs
|
|
292
|
+
// of clarifying questions, informational step lists, and even a disclaimer that literally
|
|
293
|
+
// said "NOT options to pick between" all tripped the bare `?`/verb cue, and two fires
|
|
294
|
+
// carried the cue INSIDE unrelated content (a Neo4j tool description reading "silently pick
|
|
295
|
+
// the single best match"). A `1. ... 2. ...` list is the exact shape of a benign Action
|
|
296
|
+
// Footer, a step-by-step explanation, or a set of caveats, and a shape-plus-nearby-cue proxy
|
|
297
|
+
// provably cannot tell those from a genuine fork. Catching a genuine numbered-prose fork is
|
|
298
|
+
// now the MODEL's own Phase-210 / SEED-021 judgment responsibility (its system prompt
|
|
299
|
+
// already mandates firing AskUserQuestion for a real decision fork); the flat-text safety net
|
|
300
|
+
// is deliberately removed because it did more harm than good. A genuine gate rendered as a
|
|
301
|
+
// real `[1]...[2]` bracket box is still caught by arms 1-3.
|
|
302
|
+
const ASCII_BOX_UNCONDITIONAL_RE =
|
|
303
|
+
/\[\s*1\s*\]\s*.*\[\s*2\s*\]|type\s+1\s*,\s*2\s*,\s*or\s+3|\[\s*1\s*\][^\n]*\n[\s\S]*?\[\s*2\s*\]/i;
|
|
304
|
+
|
|
305
|
+
// ----- envelope schema allowlist (Phase 95 BASH-95-01) -----
|
|
306
|
+
//
|
|
307
|
+
// stop-hook-invalid-hookspecificoutput-schema (2026-07-23): 'hookSpecificOutput'
|
|
308
|
+
// REMOVED from this allowlist. Claude Code's Stop-hook output schema does not
|
|
309
|
+
// define a Stop variant of hookSpecificOutput at all (the union covers only
|
|
310
|
+
// PreToolUse, UserPromptSubmit, and PostToolUse) -- including the key at all
|
|
311
|
+
// makes the validator reject the ENTIRE envelope ("Hook JSON output validation
|
|
312
|
+
// failed: - : Invalid input"), which replaces the calm systemMessage/reason
|
|
313
|
+
// text below with a raw schema-error dump on every turn this branch fires.
|
|
314
|
+
// Same root cause as scripts/on-stop's v1.10.9->v1.10.10 fix (2026-04-15,
|
|
315
|
+
// see the comment at that file's legacy success-output branch); this
|
|
316
|
+
// allowlist previously legitimized the mistake for this file's own call site.
|
|
317
|
+
// Keeping the key OUT of the allowlist means a future call site that tries to
|
|
318
|
+
// add hookSpecificOutput back gets silently key-filtered rather than shipping
|
|
319
|
+
// broken JSON again.
|
|
320
|
+
const ALLOWED_ENVELOPE_KEYS = new Set([
|
|
321
|
+
'decision',
|
|
322
|
+
'reason',
|
|
323
|
+
'continue',
|
|
324
|
+
'stopReason',
|
|
325
|
+
'suppressOutput',
|
|
326
|
+
'systemMessage',
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// loadRegistry() -- read the render-coverage registry. Pure local read; never throws
|
|
331
|
+
// (returns { entries: [] } on any error so the interceptor degrades to the BACKSTOP
|
|
332
|
+
// signal alone rather than blocking the hook chain).
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
function loadRegistry() {
|
|
335
|
+
try {
|
|
336
|
+
const raw = fs.readFileSync(REGISTRY_PATH, 'utf8');
|
|
337
|
+
const reg = JSON.parse(raw);
|
|
338
|
+
return reg && typeof reg === 'object' ? reg : { entries: [] };
|
|
339
|
+
} catch (_e) {
|
|
340
|
+
return { entries: [] };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
// gateReachingEntries(registry) -- DERIVE the gate-reaching enumeration from the
|
|
346
|
+
// registry's card-emission entries (Phase 178 R15). render-only-excluded entries are
|
|
347
|
+
// NOT gate-reaching. Returns an array of entry paths. Never throws.
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
function gateReachingEntries(registry) {
|
|
350
|
+
const reg = registry && typeof registry === 'object' ? registry : {};
|
|
351
|
+
const entries = Array.isArray(reg.entries) ? reg.entries : [];
|
|
352
|
+
const out = [];
|
|
353
|
+
for (const e of entries) {
|
|
354
|
+
if (e && e.render_coverage === 'card-emission' && typeof e.entry === 'string') {
|
|
355
|
+
out.push(e.entry);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// gateSignature(outputText) -- a STABLE structural signature of the GATE-IDENTIFYING content
|
|
363
|
+
// of an assistant message: the matched ASCII-box glyph span plus the NORMALIZED option-label
|
|
364
|
+
// token SET. This is the CR-03 / WR-07 root anchor: it is present whenever a gate is detected
|
|
365
|
+
// (the detection IS this content), it is INVARIANT across transcript growth and surrounding
|
|
366
|
+
// prose re-wording, and two genuinely-different gates produce genuinely-different signatures.
|
|
367
|
+
//
|
|
368
|
+
// Normalization (WR-01 robustness): re-wording the PROSE between retries must not change the
|
|
369
|
+
// signature, so we anchor ONLY on (a) the matched glyph span (the literal anti-pattern the
|
|
370
|
+
// BACKSTOP fires on) and (b) the option labels as a lowercased, prose/whitespace-stripped,
|
|
371
|
+
// SORTED token set. The labels are the `[1] foo`, `[2] bar` option bodies; we take the label
|
|
372
|
+
// TEXT after each `[n]` marker, normalize it, and sort the resulting set so option re-ordering
|
|
373
|
+
// does not flap the key.
|
|
374
|
+
//
|
|
375
|
+
// CR-04 (corrected -- this comment previously claimed the empty-only session-alone floor bounds
|
|
376
|
+
// the flap; it does NOT, and that false claim is DELETED). If the model MATERIALLY changes the
|
|
377
|
+
// option labels each retry, this per-gate signature DOES flap to a fresh key every retry, and
|
|
378
|
+
// the EMPTY-only degenerate floor in turnContextHash (which fires only when BOTH glyph span and
|
|
379
|
+
// labels are empty) provably NEVER catches a flapping NON-empty signature. The flap is bounded
|
|
380
|
+
// instead by the SESSION-WIDE intercept ceiling (MAX_SESSION_INTERCEPTS, applied in main): the
|
|
381
|
+
// session counter is keyed on session_id ALONE and is content-INDEPENDENT, so it climbs to its
|
|
382
|
+
// ceiling regardless of how this per-gate signature flaps, and degrades the run. The per-gate
|
|
383
|
+
// signature stays the GOOD-UX granularity for the common stable case (each genuinely-distinct
|
|
384
|
+
// gate gets its own per-gate budget); the session ceiling is the un-flappable convergence floor
|
|
385
|
+
// behind it.
|
|
386
|
+
//
|
|
387
|
+
// Returns '' (degenerate) when no gate content is recoverable; turnContextHash then falls back
|
|
388
|
+
// to session_id ALONE -- NEVER to a growing value. Part 8: pure local string work; the result
|
|
389
|
+
// feeds a local sha256 and never egresses.
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
function gateSignature(outputText) {
|
|
392
|
+
const text = typeof outputText === 'string' ? outputText : '';
|
|
393
|
+
if (!text) return '';
|
|
394
|
+
// (a) the matched glyph span -- the literal anti-pattern the BACKSTOP fired on. We normalize
|
|
395
|
+
// the matched substring (lowercased, whitespace collapsed) so trivial spacing churn does
|
|
396
|
+
// not move the key. A non-glyph caller (no match) contributes ''.
|
|
397
|
+
const glyphMatch = text.match(ASCII_BOX_GLYPH_RE);
|
|
398
|
+
const glyphSpan = glyphMatch
|
|
399
|
+
? glyphMatch[0].toLowerCase().replace(/\s+/g, ' ').trim()
|
|
400
|
+
: '';
|
|
401
|
+
// (b) the option-label token SET. Pull the label TEXT after each `[n]` (or `n)` / `n.`)
|
|
402
|
+
// option marker, normalize each (lowercased, non-alphanumeric stripped), drop empties,
|
|
403
|
+
// dedupe, and SORT so option re-ordering does not flap the signature.
|
|
404
|
+
// Phase 210-05: the extraction itself (the OPTION_LABEL_RE loop, including its
|
|
405
|
+
// load-bearing whitespace-separator requirement) MOVED verbatim to
|
|
406
|
+
// lib/core/gate-relevance.cjs::extractOptionLabels so the retry-key signature and
|
|
407
|
+
// the relevance predicate share exactly ONE implementation. Same regex, same
|
|
408
|
+
// normalization, same encounter-order dedupe -- the signature output is
|
|
409
|
+
// byte-equivalent across the move, so retry keys do not change.
|
|
410
|
+
const labelSet = gateRelevance.extractOptionLabels(text);
|
|
411
|
+
labelSet.sort();
|
|
412
|
+
const sig = 'glyph:' + glyphSpan + '|labels:' + labelSet.join(',');
|
|
413
|
+
// Degenerate: no glyph span AND no labels recovered -> '' so the key floors to session alone.
|
|
414
|
+
if (!glyphSpan && labelSet.length === 0) return '';
|
|
415
|
+
return crypto.createHash('sha256').update(sig).digest('hex').slice(0, 24);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// turnContextHash(turn) -- a STABLE LOCAL key for the bounded-escape retry counter.
|
|
420
|
+
//
|
|
421
|
+
// WR-01 + CR-02 + CR-03: the key MUST be stable across re-PROMPTS on the SAME stuck gate, or
|
|
422
|
+
// MAX_FORCE_RETRIES is never reachable. There are ways the key can drift, and each is fatal to
|
|
423
|
+
// convergence:
|
|
424
|
+
// - WR-01 (re-worded prose): the model re-emits the same gate with different PROSE, so an
|
|
425
|
+
// output_text-derived key would mint a fresh key every retry.
|
|
426
|
+
// - CR-02 / CR-03 (transcript GROWTH): on the LIVE BACKSTOP path the model APPENDS one
|
|
427
|
+
// assistant turn every time the interceptor blocks and it re-emits, so ANY
|
|
428
|
+
// transcript-LENGTH-derived identity (an assistant-message counter, OR a last-USER-message
|
|
429
|
+
// anchor that is EMPTY on a no-role:user transcript and falls back to that counter) mints a
|
|
430
|
+
// fresh key every retry -- a production livelock.
|
|
431
|
+
// ROOT FIX: anchor the gate identity on the GATE-IDENTIFYING CONTENT itself (gate_signature):
|
|
432
|
+
// - session_id : the Stop stdin session_id (the same conversation across retries).
|
|
433
|
+
// - gate identity (in precedence order, each transcript-growth-INVARIANT):
|
|
434
|
+
// 1. the reached-gate entry SET when present (PRIMARY signal -- deferred, no producer).
|
|
435
|
+
// 2. the GATE SIGNATURE (gate_signature): the matched glyph span + normalized option-label
|
|
436
|
+
// SET of the LAST assistant message. Present whenever a gate is detected, invariant
|
|
437
|
+
// across transcript growth and prose re-wording, per-gate (two different gates ->
|
|
438
|
+
// different signatures -> different counters; WR-07).
|
|
439
|
+
// DEGENERATE FLOOR: if BOTH are empty, the key is session_id ALONE -- coarse but ALWAYS
|
|
440
|
+
// converges. NEVER the output text (WR-01), NEVER assistantCount / gate_turn_index / any
|
|
441
|
+
// transcript-length value (CR-02 / CR-03). A growing value is UNREACHABLE as the key.
|
|
442
|
+
// Part 8: a local sha256 over local stable identity scalars; never egresses.
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
function turnContextHash(turn) {
|
|
445
|
+
const t = turn && typeof turn === 'object' ? turn : {};
|
|
446
|
+
const session = typeof t.session_id === 'string' ? t.session_id : '';
|
|
447
|
+
// Gate identity, in precedence order. Each candidate is invariant across BOTH a re-worded
|
|
448
|
+
// re-prompt (WR-01) AND a growing transcript (CR-02 / CR-03).
|
|
449
|
+
const ran = Array.isArray(t.ran_entries) && t.ran_entries.length > 0
|
|
450
|
+
? t.ran_entries.slice().sort().join('|')
|
|
451
|
+
: '';
|
|
452
|
+
// gate_signature: the transcript-growth-INVARIANT, per-gate identity for the LIVE BACKSTOP
|
|
453
|
+
// path (CR-03 / WR-07 root anchor). It is derived from the GATE CONTENT, never the user
|
|
454
|
+
// message and never a transcript-length counter, so the no-role:user path has no livelock
|
|
455
|
+
// hole and distinct gates never share a counter.
|
|
456
|
+
const gateSig = typeof t.gate_signature === 'string' ? t.gate_signature : '';
|
|
457
|
+
const gateIdentity = ran || gateSig;
|
|
458
|
+
// DEGENERATE FLOOR (gateIdentity empty): key on session_id ALONE. This always converges (any
|
|
459
|
+
// consecutive intercept in the session increments the same counter). It is NEVER a growing
|
|
460
|
+
// value -- the assistantCount / gate_turn_index fallback is DELETED.
|
|
461
|
+
return crypto
|
|
462
|
+
.createHash('sha256')
|
|
463
|
+
.update('s:' + session + '|g:' + gateIdentity)
|
|
464
|
+
.digest('hex')
|
|
465
|
+
.slice(0, 16);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---------------------------------------------------------------------------
|
|
469
|
+
// computeBackstopHit(outputText) -- the BACKSTOP signal. A hit ONLY on the shape-specific
|
|
470
|
+
// alternatives 1-3 (ASCII_BOX_UNCONDITIONAL_RE: same-line bracket box, the "type 1, 2, or 3"
|
|
471
|
+
// literal, the multiline bracket box). The Phase 209-07 bare numbered-prose alternative is
|
|
472
|
+
// RETIRED (see the retirement note above ASCII_BOX_UNCONDITIONAL_RE): it was an ~86%
|
|
473
|
+
// false-positive net in the live log, and a shape-plus-nearby-cue proxy cannot separate a
|
|
474
|
+
// benign numbered list from a genuine fork. A real numbered-prose fork is now the model's own
|
|
475
|
+
// Phase-210 / SEED-021 judgment. Pure LOCAL string work; never throws.
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
function computeBackstopHit(outputText) {
|
|
478
|
+
const text = typeof outputText === 'string' ? outputText : '';
|
|
479
|
+
if (!text) return false;
|
|
480
|
+
// Arms 1-3 only: a literal `[1] ... [2]` bracket box or the "type 1, 2, or 3" literal.
|
|
481
|
+
// Numbered-prose shape alone no longer counts (retired -- model judgment owns the fork).
|
|
482
|
+
return ASCII_BOX_UNCONDITIONAL_RE.test(text);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
// matchedGlyphSpan(outputText) -- CR-07 diagnostic helper: return the ACTUAL text of the
|
|
487
|
+
// ASCII_BOX_GLYPH_RE match (not just a boolean), so the LOCAL intercept log records exactly
|
|
488
|
+
// which glyph span tripped the scan. '' when nothing matched. Never throws. Part 8: pure
|
|
489
|
+
// local string work; the span is written only to the local diagnostic log, never egressed.
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
function matchedGlyphSpan(outputText) {
|
|
492
|
+
const text = typeof outputText === 'string' ? outputText : '';
|
|
493
|
+
if (!text) return '';
|
|
494
|
+
const mm = text.match(ASCII_BOX_GLYPH_RE);
|
|
495
|
+
return mm ? mm[0] : '';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
// classifyCardFire(turn, registry) -- THE deterministic predicate.
|
|
500
|
+
//
|
|
501
|
+
// Returns { intercept, reason, degrade }:
|
|
502
|
+
// - intercept=true : a reached gate fired no card; force it (decision:block-on-exit-0 re-prompt).
|
|
503
|
+
// - intercept=false : ordinary turn, card already fired, or degraded.
|
|
504
|
+
// - degrade=true : the bounded escape released (retry_count >= MAX_FORCE_RETRIES);
|
|
505
|
+
// stop forcing, log + allow.
|
|
506
|
+
//
|
|
507
|
+
// turn shape (the normalized turn signals a Stop hook derives):
|
|
508
|
+
// { ran_entries: string[], askuserquestion_fired: bool, output_text: string,
|
|
509
|
+
// retry_count?: number, session_count?: number }
|
|
510
|
+
//
|
|
511
|
+
// Bounded escape (CR-04): degrade when EITHER ceiling is reached --
|
|
512
|
+
// - retry_count >= MAX_FORCE_RETRIES (the per-gate counter; good UX, per-gate granularity)
|
|
513
|
+
// - session_count >= MAX_SESSION_INTERCEPTS (the SESSION-wide ceiling; the content-INDEPENDENT,
|
|
514
|
+
// un-flappable convergence floor that catches a
|
|
515
|
+
// flapping per-gate key the per-gate counter cannot).
|
|
516
|
+
// The session ceiling is the load-bearing guarantee: an unbroken run of intercepts in ONE
|
|
517
|
+
// session cannot exceed MAX_SESSION_INTERCEPTS regardless of how the gate content / per-gate key
|
|
518
|
+
// flaps. Both ceilings are checked; the session ceiling is reported distinctly so the degrade
|
|
519
|
+
// reason names WHICH floor fired.
|
|
520
|
+
//
|
|
521
|
+
// Pure code, deterministic, no network, no clock, no random. Never throws.
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
function classifyCardFire(turn, registry) {
|
|
524
|
+
try {
|
|
525
|
+
const t = turn && typeof turn === 'object' ? turn : {};
|
|
526
|
+
const ran = Array.isArray(t.ran_entries) ? t.ran_entries : [];
|
|
527
|
+
const cardFired = t.askuserquestion_fired === true;
|
|
528
|
+
const outputText = typeof t.output_text === 'string' ? t.output_text : '';
|
|
529
|
+
|
|
530
|
+
// If the card already fired, there is nothing to force.
|
|
531
|
+
if (cardFired) {
|
|
532
|
+
return { intercept: false, reason: 'card-fired', degrade: false };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// PRIMARY signal: did a render-coverage-registry gate-reaching surface run?
|
|
536
|
+
const reaching = new Set(gateReachingEntries(registry));
|
|
537
|
+
const primaryHit = ran.some(function (e) { return reaching.has(e); });
|
|
538
|
+
|
|
539
|
+
// BACKSTOP signal: does the turn output carry the ASCII-box gate glyphs? Catches
|
|
540
|
+
// the literal anti-pattern even for an off-registry surface. CR-05: a bare
|
|
541
|
+
// numbered-prose list (alternative 4) is a hit ONLY when it carries a choice-framing
|
|
542
|
+
// cue nearby, so a benign Action Footer / step-by-step list no longer force-fires.
|
|
543
|
+
const backstopHit = computeBackstopHit(outputText);
|
|
544
|
+
|
|
545
|
+
if (!primaryHit && !backstopHit) {
|
|
546
|
+
// No gate-reaching signal on this turn -> zero forced cards (ordinary turn).
|
|
547
|
+
return { intercept: false, reason: 'no-gate-signal', degrade: false };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Phase 238-08 (GATE-04, D-15): the backstop-corroboration gate. `238-RESEARCH.md`
|
|
551
|
+
// measured that pure regex provably cannot separate a footnote reference list from
|
|
552
|
+
// a bracket-box gate -- `[1] Simon 1962.` / `[2] Rittel and Webber 1973.` and
|
|
553
|
+
// `[1] Build the plan` / `[2] File the evidence` are the SAME string shape. Both
|
|
554
|
+
// candidate replacement patterns still fired on the footnote list, and
|
|
555
|
+
// line-anchoring additionally lost the same-line bracket-box true positive (both
|
|
556
|
+
// measured, not assumed). Evidence: 5 of 6 authored false-positive fixtures fire on
|
|
557
|
+
// the shipped ASCII_BOX_UNCONDITIONAL_RE pattern today; 0 of 38 live
|
|
558
|
+
// ~/.mindrian/card-fire-intercepts.log records are backstop fires (37 of 38 are
|
|
559
|
+
// PRIMARY-arm, 1 of 38 is neither arm); the backstop's live false-positive rate on
|
|
560
|
+
// ordinary prose is real, not hypothetical. A regex cannot close this: a footnote
|
|
561
|
+
// reference list and a bracket-box gate are the same string shape, and a
|
|
562
|
+
// shape-plus-nearby-cue proxy was already tried and retired once at roughly an 86
|
|
563
|
+
// percent false-positive rate (see the retirement note above
|
|
564
|
+
// ASCII_BOX_UNCONDITIONAL_RE) -- this branch does NOT re-introduce that proxy under
|
|
565
|
+
// a new name. The remedy is architectural instead: a structural signal a citation
|
|
566
|
+
// cannot have, a corroborating side-channel reach record for THIS session. Decision
|
|
567
|
+
// ruled on 2026-07-29 (D-15, 238-08-PLAN.md).
|
|
568
|
+
//
|
|
569
|
+
// Condition: the PRIMARY arm did not hit (this branch is BACKSTOP-only), the
|
|
570
|
+
// BACKSTOP arm did hit, sidechannel_health is EXPLICITLY the healthy value (D-16:
|
|
571
|
+
// a turn carrying NEITHER new field -- the direct-field unit-test envelope shape --
|
|
572
|
+
// does not satisfy this and falls through unchanged, keeping every existing
|
|
573
|
+
// predicate assertion byte-stable), and reach_corroborated is not true. When all
|
|
574
|
+
// four hold, the backstop hit is UNCORROBORATED on a HEALTHY side channel: suppress
|
|
575
|
+
// the intercept and report degrade:false (this was never a real gate, so it must
|
|
576
|
+
// not consume retry budget -- placing this branch BEFORE the ceiling checks below
|
|
577
|
+
// is deliberate for exactly that reason).
|
|
578
|
+
//
|
|
579
|
+
// ACCEPTED RESIDUAL, stated plainly, not hidden (D-15): a genuine unrendered
|
|
580
|
+
// bracket-box gate from an OFF-REGISTRY surface, with a healthy side channel and NO
|
|
581
|
+
// reach record, no longer intercepts here. That is a real reduction in coverage,
|
|
582
|
+
// accepted because the PRIMARY arm handles the overwhelming majority of real fires
|
|
583
|
+
// (37 of 38 in the live log) and the alternative is a classifier that fires on
|
|
584
|
+
// ordinary technical prose in a dev repo. When the side channel is UNAVAILABLE
|
|
585
|
+
// (sidechannel_health is not explicitly healthy), this branch does not fire and the
|
|
586
|
+
// backstop keeps its full pre-238-08 independent authority -- the "detector of last
|
|
587
|
+
// resort" its own doctrine header claims, preserved rather than deleted.
|
|
588
|
+
if (
|
|
589
|
+
!primaryHit &&
|
|
590
|
+
backstopHit &&
|
|
591
|
+
t.sidechannel_health === SIDECHANNEL_HEALTH_OK &&
|
|
592
|
+
t.reach_corroborated !== true
|
|
593
|
+
) {
|
|
594
|
+
return { intercept: false, degrade: false, reason: 'backstop-uncorroborated-by-side-channel' };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// A reached gate with no fired card is a candidate intercept. Apply the bounded escape:
|
|
598
|
+
// degrade if EITHER the per-gate counter OR the SESSION-wide counter has hit its ceiling.
|
|
599
|
+
const sessionCount = Number.isFinite(t.session_count) ? t.session_count : 0;
|
|
600
|
+
if (sessionCount >= MAX_SESSION_INTERCEPTS) {
|
|
601
|
+
// The CR-04 convergence floor: the session ceiling catches a flapping per-gate key that
|
|
602
|
+
// the per-gate counter never reaches. Content-INDEPENDENT, so it ALWAYS converges.
|
|
603
|
+
return {
|
|
604
|
+
intercept: false,
|
|
605
|
+
degrade: true,
|
|
606
|
+
reason: 'session-intercept-ceiling-reached-after-' + MAX_SESSION_INTERCEPTS + '-intercepts',
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const retryCount = Number.isFinite(t.retry_count) ? t.retry_count : 0;
|
|
611
|
+
if (retryCount >= MAX_FORCE_RETRIES) {
|
|
612
|
+
return {
|
|
613
|
+
intercept: false,
|
|
614
|
+
degrade: true,
|
|
615
|
+
reason: 'bounded-escape-released-after-' + MAX_FORCE_RETRIES + '-retries',
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Phase 210-05 (item 210-E-1): the RELEVANCE GATE, inserted between the bounded
|
|
620
|
+
// escape above and the final intercept below. Two NARROW pass reasons only:
|
|
621
|
+
// - 'gate-already-answered' : the preceding user turn already plainly answered
|
|
622
|
+
// the gate (the 2026-07-02 live incident: a plain-text "yes" to the question
|
|
623
|
+
// the gate would re-ask).
|
|
624
|
+
// - 'gate-irrelevant-to-turn': the gate's subject matter has ZERO token overlap
|
|
625
|
+
// with the current conversation (the stale-artifact pattern).
|
|
626
|
+
// Empty/uncertain user text takes NEITHER pass (the conservative verdict inside
|
|
627
|
+
// gate-relevance.cjs) and falls through to the existing intercept -- the Phase 209
|
|
628
|
+
// guarantee (a genuine, relevant, unanswered fork still force-fires) is preserved.
|
|
629
|
+
// CR-03: preceding_user_text feeds ONLY this verdict, never the retry key.
|
|
630
|
+
//
|
|
631
|
+
// 2026-07-05 relevance-gate PRIMARY-path fix (kept): gateAlreadyAnswered's gateLabels
|
|
632
|
+
// (derived from outputText) is UNTOUCHED below -- only the relevance comparison's second
|
|
633
|
+
// argument (the gate subject) is path-specific. WHY: on the PRIMARY path, outputText is
|
|
634
|
+
// the CURRENT TURN's assistant reply, and the model naturally echoes the user's current
|
|
635
|
+
// topic back when answering it -- so comparing precedingUserText against outputText almost
|
|
636
|
+
// always looked "relevant" no matter what the stale reach was actually about (it compared
|
|
637
|
+
// the user's turn against the assistant's OWN reply, never against the reach's real
|
|
638
|
+
// subject).
|
|
639
|
+
//
|
|
640
|
+
// card-fire-relevance-check-gap (2026-07-17) PRIMARY-path gate-existence guard: a PRIMARY
|
|
641
|
+
// hit means a registry gate-reaching surface (lib/hmi/selector-dispatcher.cjs) appears in
|
|
642
|
+
// ran_entries -- but the side-channel's NO_SESSION_KEY union + ~10-min TTL bleeds ONE real
|
|
643
|
+
// gate-mint into EVERY turn's ran_entries for the next ten minutes across all sessions (the
|
|
644
|
+
// live log shows 10 consecutive false fires on status / "building" / "boot the real one"
|
|
645
|
+
// turns spanning ~21 min and 4 session_ids). So ran_entries alone is NOT proof a gate was
|
|
646
|
+
// reached THIS turn. Require CONFIRMED gate-existence: a non-empty reach-recorded subject
|
|
647
|
+
// (t.gate_subject_text -- the gate's own recorded content from the side-channel). With no
|
|
648
|
+
// subject there is no gate this turn -> 'primary-gate-existence-unconfirmed', no intercept.
|
|
649
|
+
// Relevance on the primary path compares the preceding user turn against the reach's OWN
|
|
650
|
+
// recorded subject ONLY, never the outputText fallback (which compares the user turn against
|
|
651
|
+
// the assistant's own reply and almost always reads "relevant" -- the exact defect this
|
|
652
|
+
// closes). This guard is PRIMARY-only; the BACKSTOP path carries its gate content in
|
|
653
|
+
// outputText and keeps the outputText fallback below.
|
|
654
|
+
const precedingUserText = typeof t.preceding_user_text === 'string'
|
|
655
|
+
? t.preceding_user_text
|
|
656
|
+
: '';
|
|
657
|
+
if (primaryHit && !backstopHit) {
|
|
658
|
+
const primarySubject = (typeof t.gate_subject_text === 'string' && t.gate_subject_text)
|
|
659
|
+
? t.gate_subject_text
|
|
660
|
+
: '';
|
|
661
|
+
if (!primarySubject) {
|
|
662
|
+
return { intercept: false, reason: 'primary-gate-existence-unconfirmed', degrade: false };
|
|
663
|
+
}
|
|
664
|
+
// room-bind-gate-fires-on-notification-only-turns (2026-07-23): refines
|
|
665
|
+
// card-fire-over-enforcement (2026-07-20) fix (B) above. That fix distinguished a
|
|
666
|
+
// FRESH gate from a STALE one; it did NOT distinguish WHY precedingUserText is empty.
|
|
667
|
+
// gateTopicallyRelevant's low-signal branch conflates two very different '' causes:
|
|
668
|
+
// (1) a genuinely short/terse HUMAN turn ("ok", "go on") -- KEEP forcing here, a
|
|
669
|
+
// human really was there and might be dismissively engaging with the gate
|
|
670
|
+
// (the WR-06/CR-06 floor, unchanged).
|
|
671
|
+
// (2) the preceding transcript record is a SYNTHETIC tool_result / task-notification
|
|
672
|
+
// envelope with NO human-authored text field at all (a background subagent
|
|
673
|
+
// finishing, an automated task notification) -- there was no human turn to be
|
|
674
|
+
// relevant or irrelevant to, so forcing a Decision Gate card here is never
|
|
675
|
+
// correct. Confirmed live: the F.8 room-binding gate force-fired three
|
|
676
|
+
// consecutive times on notification-only turns in one session (see
|
|
677
|
+
// .planning/debug/resolved/room-bind-gate-fires-on-notification-only-turns.md).
|
|
678
|
+
// classifyPrecedingUserContentSource (readTranscriptTurn) is the ONLY producer of
|
|
679
|
+
// 'tool_result' -- an unexplained/absent preceding record still resolves to 'none' and
|
|
680
|
+
// stays on the pre-fix conservative floor below. This check is PRIMARY-path ONLY (per
|
|
681
|
+
// the confirmed mechanism); the BACKSTOP path already forces unconditionally on a terse
|
|
682
|
+
// turn (gateStale:false, unchanged) and is out of scope for this fix.
|
|
683
|
+
if (t.preceding_user_text_source === 'tool_result') {
|
|
684
|
+
return { intercept: false, reason: 'preceding-turn-synthetic-no-user-engagement', degrade: false };
|
|
685
|
+
}
|
|
686
|
+
// card-fire-over-enforcement (2026-07-20) fix (B): a STALE side-channel
|
|
687
|
+
// gate (bled forward by the file TTL) plus a low-signal terse turn is the
|
|
688
|
+
// dominant over-enforcement class. Pass the gate's staleness so the
|
|
689
|
+
// low-signal relevance branch stops defaulting to force. A FRESH gate
|
|
690
|
+
// (gate_is_fresh !== false) keeps the conservative force floor unchanged.
|
|
691
|
+
if (!gateRelevance.gateTopicallyRelevant(precedingUserText, primarySubject,
|
|
692
|
+
{ gateStale: t.gate_is_fresh === false })) {
|
|
693
|
+
return { intercept: false, reason: 'gate-irrelevant-to-turn', degrade: false };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const gateLabels = gateRelevance.extractOptionLabels(outputText);
|
|
697
|
+
if (gateRelevance.gateAlreadyAnswered(precedingUserText, gateLabels)) {
|
|
698
|
+
return { intercept: false, reason: 'gate-already-answered', degrade: false };
|
|
699
|
+
}
|
|
700
|
+
if (!(primaryHit && !backstopHit)) {
|
|
701
|
+
// BACKSTOP path: outputText legitimately IS the gate content, so the fallback to
|
|
702
|
+
// outputText is preserved here (and ONLY here).
|
|
703
|
+
const gateSubjectText = (typeof t.gate_subject_text === 'string' && t.gate_subject_text)
|
|
704
|
+
? t.gate_subject_text
|
|
705
|
+
: outputText;
|
|
706
|
+
// BACKSTOP path: the gate content is THIS turn's own output_text, so the
|
|
707
|
+
// gate is always fresh here -- gateStale:false preserves the WR-06 floor
|
|
708
|
+
// (a terse turn against a freshly rendered box still force-fires).
|
|
709
|
+
if (!gateRelevance.gateTopicallyRelevant(precedingUserText, gateSubjectText,
|
|
710
|
+
{ gateStale: false })) {
|
|
711
|
+
return { intercept: false, reason: 'gate-irrelevant-to-turn', degrade: false };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Navigator decision (2026-07-05): a plain 2-option yes/no closer is a SIMPLE
|
|
716
|
+
// BINARY, not a genuine multi-option Decision Gate, and is EXEMPT from the
|
|
717
|
+
// card-fire requirement -- only genuine 3+-option forks force-fire. This pass-
|
|
718
|
+
// reason sits AFTER the two relevance checks on purpose: placing it before
|
|
719
|
+
// gate-already-answered would relabel an already-answered 2-option gate (the
|
|
720
|
+
// relevance test's RELEASE_GATE is also 2-option) as gate-is-simple-binary and
|
|
721
|
+
// change that pass-reason's precedence. Reuse the gateLabels already extracted
|
|
722
|
+
// above (per the RCA: no new option-extraction path).
|
|
723
|
+
//
|
|
724
|
+
// intern-w1-card-discipline-decay (2026-07-11): the original condition was a
|
|
725
|
+
// BARE `gateLabels.length === 2` cardinality check, which cannot distinguish a
|
|
726
|
+
// trivial yes/no confirmation closer ("Want those?") from a genuine two-way
|
|
727
|
+
// FORCED-CHOICE strategic fork ("run research vs build the plan" / "build the
|
|
728
|
+
// plan now vs file evidence first"). All 3 of an intern's missed forks in one
|
|
729
|
+
// QA session carried exactly 2 option labels and were swallowed by this
|
|
730
|
+
// exemption regardless of content. Fixed by requiring the labels to be
|
|
731
|
+
// YES/NO-SHAPED (gateRelevance.isYesNoShapedGate -- the SAME semantic test
|
|
732
|
+
// gateAlreadyAnswered already uses for its own 2-option yes/no answer-matching,
|
|
733
|
+
// reused here per Part 7 rather than re-implemented), not merely 2-in-number.
|
|
734
|
+
// A genuine 2-option fork whose labels are NOT yes/no-shaped now falls through
|
|
735
|
+
// to the final intercept below and force-fires, exactly like a 3+-way fork; a
|
|
736
|
+
// real yes/no closer stays exempt.
|
|
737
|
+
if (gateRelevance.isYesNoShapedGate(gateLabels)) {
|
|
738
|
+
return { intercept: false, reason: 'gate-is-simple-binary', degrade: false };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const reason = primaryHit
|
|
742
|
+
? 'reached-registry-gate-no-card'
|
|
743
|
+
: 'ascii-box-backstop-no-card';
|
|
744
|
+
return { intercept: true, reason: reason, degrade: false };
|
|
745
|
+
} catch (_e) {
|
|
746
|
+
// Defensive: never throw out of the predicate.
|
|
747
|
+
return { intercept: false, reason: 'predicate-error', degrade: false };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ---------------------------------------------------------------------------
|
|
752
|
+
// buildEnforcementEnvelope(verdict) -- shape the Stop-hook output envelope from a
|
|
753
|
+
// classifyCardFire verdict.
|
|
754
|
+
// - degrade -> { continue: true, suppressOutput: true } (log + allow; no loop).
|
|
755
|
+
// - intercept-> the decision:block-on-exit-0 block: { decision:'block', reason,
|
|
756
|
+
// systemMessage, continue:false }.
|
|
757
|
+
// - neither -> { continue: true, suppressOutput: true } (nothing to do).
|
|
758
|
+
// Returns a key-filtered envelope. Never throws.
|
|
759
|
+
//
|
|
760
|
+
// CR-06 (2026-07-11, backstop-benign-list-defeats-relevance-gate reopen of
|
|
761
|
+
// card-fire-block-surface Finding 1): the ORIGINAL fix added a systemMessage on the
|
|
762
|
+
// premise that Claude Code surfaces `reason` as "Stop hook error: <reason>" ONLY when no
|
|
763
|
+
// systemMessage override is present. Live observation (v1.15.3-beta.12) proved that premise
|
|
764
|
+
// FALSE -- Claude Code renders "Stop hook error: <reason>" REGARDLESS of systemMessage, so
|
|
765
|
+
// the internal slug (e.g. ascii-box-backstop-no-card) still reached the user looking like a
|
|
766
|
+
// crash. The only lever left is the `reason` CONTENT itself: on BOTH the intercept branch
|
|
767
|
+
// and the degrade branch it is now a calm, human-safe phrase, never the internal slug. The
|
|
768
|
+
// slug is PRESERVED for telemetry in the LOCAL intercept log (appendInterceptLog), not
|
|
769
|
+
// deleted. systemMessage stays on the intercept branch. The else branch keeps
|
|
770
|
+
// suppressOutput: true and carries no systemMessage.
|
|
771
|
+
//
|
|
772
|
+
// stop-hook-invalid-hookspecificoutput-schema (2026-07-23, 4th occurrence of this
|
|
773
|
+
// defect class): the intercept branch previously ALSO carried a hookSpecificOutput
|
|
774
|
+
// envelope (event name Stop, plus an additionalContext re-prompt string). Stop is
|
|
775
|
+
// not one of the three event types Claude Code's schema defines a
|
|
776
|
+
// hookSpecificOutput variant for (PreToolUse / UserPromptSubmit / PostToolUse only),
|
|
777
|
+
// so that key made the validator reject the WHOLE envelope -- the user saw a raw
|
|
778
|
+
// "Hook JSON output validation failed: Invalid input" dump instead of the calm
|
|
779
|
+
// `reason`/`systemMessage` text above, on every turn this branch fired. REMOVED
|
|
780
|
+
// outright. The `additionalContext` re-prompt text it carried ("You MUST fire the
|
|
781
|
+
// AskUserQuestion card NOW...") has no valid Stop-hook delivery channel in the
|
|
782
|
+
// current schema (additionalContext is UserPromptSubmit/PostToolUse-only) and per
|
|
783
|
+
// CR-06 must not be smuggled into `reason`/`systemMessage` either (those are
|
|
784
|
+
// human-facing and must stay calm, never carry model-directive/internal-mechanism
|
|
785
|
+
// text) -- so it is dropped, not relocated. See
|
|
786
|
+
// .planning/debug/resolved/stop-hook-invalid-hookspecificoutput-schema.md.
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
function buildEnforcementEnvelope(verdict) {
|
|
789
|
+
const v = verdict && typeof verdict === 'object' ? verdict : {};
|
|
790
|
+
|
|
791
|
+
let raw;
|
|
792
|
+
if (v.degrade === true) {
|
|
793
|
+
raw = {
|
|
794
|
+
continue: true,
|
|
795
|
+
suppressOutput: true,
|
|
796
|
+
// CR-06: calm, human-safe `reason` (never the internal slug). Claude Code surfaces
|
|
797
|
+
// `reason` as "Stop hook error: <reason>" regardless of systemMessage, so the slug
|
|
798
|
+
// must not live here. It is preserved for telemetry in the LOCAL intercept log.
|
|
799
|
+
reason: 'continuing this turn',
|
|
800
|
+
};
|
|
801
|
+
} else if (v.intercept === true) {
|
|
802
|
+
raw = {
|
|
803
|
+
decision: 'block',
|
|
804
|
+
// CR-06: calm, human-safe `reason` (never the internal slug -- see the branch note
|
|
805
|
+
// above). The slug is preserved for telemetry in the LOCAL intercept log instead.
|
|
806
|
+
reason: 'rendering your choices as a selectable card',
|
|
807
|
+
// FIXED human-facing line (never interpolated from transcript content or the reason
|
|
808
|
+
// slug; T-m9g-01). Retained from the original Finding 1 fix as the second calm surface.
|
|
809
|
+
systemMessage: 'Re-rendering your choices as a selectable card...',
|
|
810
|
+
continue: false,
|
|
811
|
+
};
|
|
812
|
+
} else {
|
|
813
|
+
raw = { continue: true, suppressOutput: true };
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const filtered = {};
|
|
817
|
+
for (const k of Object.keys(raw)) {
|
|
818
|
+
if (ALLOWED_ENVELOPE_KEYS.has(k)) filtered[k] = raw[k];
|
|
819
|
+
}
|
|
820
|
+
if (filtered.continue === undefined && filtered.decision === undefined) {
|
|
821
|
+
filtered.continue = true;
|
|
822
|
+
}
|
|
823
|
+
return filtered;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
// 238-05 Task 2 (GATE-03 half B, the LOST-UPDATE fix): withRetryStoreLock, the
|
|
828
|
+
// bounded-wait fence around the retry-store read-modify-write.
|
|
829
|
+
//
|
|
830
|
+
// Measured pre-fix (238-RESEARCH.md Finding 4): 20 forked processes each doing
|
|
831
|
+
// 10 bumpRetryCount calls against an isolated MINDRIAN_HOME landed a final
|
|
832
|
+
// count of 3, not 200 -- 197 lost updates. Task 1's tmp-and-rename write fixes
|
|
833
|
+
// torn READS; it does NOT fix this. The defect is a read-modify-write with no
|
|
834
|
+
// fence: every concurrent writer reads the same pre-state and the last writer
|
|
835
|
+
// wins, discarding every increment that landed between its own read and write.
|
|
836
|
+
//
|
|
837
|
+
// DEVIATION FROM D-05, recorded here and in 238-05-SUMMARY.md: D-05 called
|
|
838
|
+
// for reusing lib/core/write-lock.cjs's acquireLock/releaseLock UNCHANGED.
|
|
839
|
+
// Adversarial testing during this task (20 forked processes doing 200 total
|
|
840
|
+
// acquire/release cycles, not the single simultaneous-storm shape Phase
|
|
841
|
+
// 87-02's own precedent test exercises) found that acquireLock has a REAL
|
|
842
|
+
// TOCTOU race under this higher-frequency usage: its create step is
|
|
843
|
+
// open-then-SEPARATELY-write (a reader can observe an empty file mid-create
|
|
844
|
+
// and misclassify it as corrupt), and its corrupt/stale/dead-PID cleanup
|
|
845
|
+
// branches unconditionally unlink whatever is CURRENTLY at the lock path
|
|
846
|
+
// without verifying it is still the same file they read moments earlier --
|
|
847
|
+
// so a slow contender can destroy a DIFFERENT, already-valid lock a faster
|
|
848
|
+
// contender just created, breaking mutual exclusion. Measured: repeated
|
|
849
|
+
// 20x10 runs against the unmodified primitive landed 148-197 of 200, never a
|
|
850
|
+
// reliable 200, with zero fence-exhaustion events (so the loss is a true
|
|
851
|
+
// double-hold, not a wait-budget problem). Root-caused with a custom
|
|
852
|
+
// nanosecond-precision trace (hrtime.bigint on ACQ/REL boundaries across all
|
|
853
|
+
// 20 children) that caught two live overlapping "holds" of the same lock
|
|
854
|
+
// path in the act.
|
|
855
|
+
//
|
|
856
|
+
// scripts/check-card-fire.cjs is the ONLY file this plan may modify
|
|
857
|
+
// (files_modified). lib/core/write-lock.cjs is shared by other subsystems
|
|
858
|
+
// (SQLite write-safety) and is NOT touched here -- fixing its cleanup
|
|
859
|
+
// protocol correctly is real distributed-systems work (verified by writing
|
|
860
|
+
// and discarding two intermediate fix attempts that each introduced their
|
|
861
|
+
// own new race or performance regression before landing on the design
|
|
862
|
+
// below) and belongs to a dedicated follow-up, not a plan whose declared
|
|
863
|
+
// scope is this file alone. Instead, the retry-store fence below is a
|
|
864
|
+
// SEPARATE, purpose-built lock local to this file, using two idioms already
|
|
865
|
+
// proven correct in this session:
|
|
866
|
+
// 1. Atomic CREATE: write full content to a unique temp path, then
|
|
867
|
+
// fs.linkSync(tmpPath, lockPath). link() only ever makes an ALREADY-
|
|
868
|
+
// FULLY-WRITTEN inode visible under the final name -- there is no
|
|
869
|
+
// window where a reader can see empty/partial content, unlike
|
|
870
|
+
// open('wx') followed by a separate write.
|
|
871
|
+
// 2. Inode-VERIFIED cleanup: before removing a lock judged corrupt/stale/
|
|
872
|
+
// dead-PID, open it, fstat it, and only unlink if the inode still
|
|
873
|
+
// matches what was read. A concurrent contender who has already
|
|
874
|
+
// replaced the file with a fresh, valid lock can never have that lock
|
|
875
|
+
// destroyed by a decision based on a now-stale snapshot.
|
|
876
|
+
// Empirically verified: 20+ consecutive 20-process x 10-bump runs against
|
|
877
|
+
// this design landed exactly 200 every time (see 238-05-SUMMARY.md for the
|
|
878
|
+
// full trial log). The external behavior this plan's callers need is
|
|
879
|
+
// unchanged: bump/clear still best-effort, still swallow write faults, still
|
|
880
|
+
// degrade to an unfenced write on fence exhaustion (D-12).
|
|
881
|
+
// ---------------------------------------------------------------------------
|
|
882
|
+
|
|
883
|
+
// retryStoreLockPath() -- a dedicated lock file sitting NEXT TO the retry
|
|
884
|
+
// store itself (a sibling path, not nested under a room-shaped '.mindrian'
|
|
885
|
+
// directory the way the shared write-lock.cjs primitive would have used).
|
|
886
|
+
// Since this lock is purpose-built for exactly one file, it needs no
|
|
887
|
+
// borrowed room-directory convention.
|
|
888
|
+
function retryStoreLockPath() {
|
|
889
|
+
return retryFilePath() + '.lock';
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// The bounded-wait sizing. Named constants, not magic numbers, so the total
|
|
893
|
+
// worst-case wait is legible at a glance: 300 attempts * 20ms = ~6000ms.
|
|
894
|
+
//
|
|
895
|
+
// Justification for affording the wait at all: Phase 241 verified against
|
|
896
|
+
// real Claude Code documentation that the platform's default Stop-hook
|
|
897
|
+
// timeout is 600 SECONDS per hook command, not the ~3000ms this file's own
|
|
898
|
+
// older comments assumed and never checked. A worst-case wait of about six
|
|
899
|
+
// seconds sits comfortably (~100x) inside the real budget, so fencing here
|
|
900
|
+
// is never a latency risk in production.
|
|
901
|
+
//
|
|
902
|
+
// Tuning note: an initial 50-attempt (~1s) budget, sized before this file's
|
|
903
|
+
// own concurrency was measured, produced occasional (D-12) fence-exhaustion
|
|
904
|
+
// degrades under 20-process x 10-bump real contention (2-3 exhaustions per
|
|
905
|
+
// run, each landing one increment via the unfenced path instead of the
|
|
906
|
+
// fenced one -- safe per D-12, but not exact). Widening to 300 attempts
|
|
907
|
+
// (~6s) was verified against 24+ consecutive 20x10 runs with zero
|
|
908
|
+
// exhaustions and an exact count every time (238-05-SUMMARY.md has the
|
|
909
|
+
// trial log). 6s worst case is still far inside the 600s hook budget.
|
|
910
|
+
const RETRY_LOCK_MAX_ATTEMPTS = 300;
|
|
911
|
+
const RETRY_LOCK_SLEEP_MS = 20;
|
|
912
|
+
|
|
913
|
+
// RETRY_LOCK_STALE_THRESHOLD_MS -- mirrors lib/core/write-lock.cjs's own
|
|
914
|
+
// STALE_THRESHOLD_MS (5s) for consistency of expectations across the
|
|
915
|
+
// codebase's lock files: a lock older than this is presumed abandoned by a
|
|
916
|
+
// crashed holder and is safe to reclaim (after inode verification).
|
|
917
|
+
const RETRY_LOCK_STALE_THRESHOLD_MS = 5000;
|
|
918
|
+
|
|
919
|
+
// safeVerifiedDelete(lockPath, expectedIno, expectedDev) -- remove lockPath
|
|
920
|
+
// ONLY if it is still the exact inode we already inspected. Opens the file
|
|
921
|
+
// (not just statSync on the path) so the inode check and the unlink discuss
|
|
922
|
+
// the SAME file handle's identity, closing the gap a path-based re-check
|
|
923
|
+
// would still leave.
|
|
924
|
+
function safeVerifiedDelete(lockPath, expectedIno, expectedDev) {
|
|
925
|
+
let fd;
|
|
926
|
+
try {
|
|
927
|
+
fd = fs.openSync(lockPath, 'r');
|
|
928
|
+
} catch (_e) {
|
|
929
|
+
return; // already gone; nothing to clean up
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
const st = fs.fstatSync(fd);
|
|
933
|
+
if (st.ino !== expectedIno || st.dev !== expectedDev) {
|
|
934
|
+
// A different, presumably newer file now lives at this path. Not ours
|
|
935
|
+
// to delete -- deleting it would destroy someone else's valid lock.
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
fs.unlinkSync(lockPath);
|
|
939
|
+
} catch (_e) {
|
|
940
|
+
/* best-effort cleanup; never throw out of the fence */
|
|
941
|
+
} finally {
|
|
942
|
+
try { fs.closeSync(fd); } catch (_e) { /* best-effort */ }
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// tryAtomicCreate(lockPath, payload) -- write-then-link. Throws EEXIST (same
|
|
947
|
+
// error code openSync('wx') would throw) if the name is already taken, so
|
|
948
|
+
// callers can treat it identically to a failed exclusive-create.
|
|
949
|
+
function tryAtomicCreate(lockPath, payload) {
|
|
950
|
+
const tmpPath = lockPath + '.tmp-' + process.pid + '-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
951
|
+
fs.writeFileSync(tmpPath, payload);
|
|
952
|
+
try {
|
|
953
|
+
fs.linkSync(tmpPath, lockPath);
|
|
954
|
+
} finally {
|
|
955
|
+
try { fs.unlinkSync(tmpPath); } catch (_e) { /* best-effort */ }
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// acquireRetryLock() -- one attempt to claim the retry-store lock. Throws on
|
|
960
|
+
// failure (live holder or a pathological double-failure after cleanup);
|
|
961
|
+
// withRetryStoreLock below is what turns this into a bounded WAIT.
|
|
962
|
+
function acquireRetryLock() {
|
|
963
|
+
const lockPath = retryStoreLockPath();
|
|
964
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
965
|
+
const payload = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
966
|
+
|
|
967
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
968
|
+
// Cheap pre-check: only pay for the tmp-write-then-link dance when the
|
|
969
|
+
// path looks free. Under contention the lock is usually held, so this
|
|
970
|
+
// check saves a wasted disk write on nearly every failed attempt; if it
|
|
971
|
+
// races (looks free but is not by the time we act), linkSync's own
|
|
972
|
+
// EEXIST still catches it correctly -- this is a performance hint only,
|
|
973
|
+
// never relied on for correctness.
|
|
974
|
+
if (!fs.existsSync(lockPath)) {
|
|
975
|
+
try {
|
|
976
|
+
tryAtomicCreate(lockPath, payload);
|
|
977
|
+
return;
|
|
978
|
+
} catch (createErr) {
|
|
979
|
+
if (createErr.code !== 'EEXIST') throw createErr;
|
|
980
|
+
// fall through to inspection below
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
let fd;
|
|
985
|
+
try {
|
|
986
|
+
fd = fs.openSync(lockPath, 'r');
|
|
987
|
+
} catch (_openErr) {
|
|
988
|
+
// Vanished between the existsSync check and open -- someone else is
|
|
989
|
+
// racing; retry from the top of this attempt.
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
let st, raw;
|
|
993
|
+
try {
|
|
994
|
+
st = fs.fstatSync(fd);
|
|
995
|
+
raw = fs.readFileSync(fd, 'utf-8');
|
|
996
|
+
} finally {
|
|
997
|
+
try { fs.closeSync(fd); } catch (_e) { /* best-effort */ }
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
let data;
|
|
1001
|
+
try {
|
|
1002
|
+
data = JSON.parse(raw);
|
|
1003
|
+
} catch (_parseErr) {
|
|
1004
|
+
safeVerifiedDelete(lockPath, st.ino, st.dev);
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const age = Date.now() - data.timestamp;
|
|
1008
|
+
if (age > RETRY_LOCK_STALE_THRESHOLD_MS) {
|
|
1009
|
+
safeVerifiedDelete(lockPath, st.ino, st.dev);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (data.pid === process.pid) {
|
|
1013
|
+
// Same-process re-acquire: no cross-process race is possible here
|
|
1014
|
+
// (single-threaded, same PID owns it by definition).
|
|
1015
|
+
fs.writeFileSync(lockPath, payload);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
process.kill(data.pid, 0);
|
|
1020
|
+
throw new Error('retry-store lock held by PID ' + data.pid);
|
|
1021
|
+
} catch (killErr) {
|
|
1022
|
+
if (killErr.message && killErr.message.indexOf('retry-store lock held') === 0) throw killErr;
|
|
1023
|
+
// Dead PID -- clean up (inode-verified) and retry.
|
|
1024
|
+
safeVerifiedDelete(lockPath, st.ino, st.dev);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
throw new Error('retry-store lock could not be acquired after retry');
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function releaseRetryLock() {
|
|
1032
|
+
try {
|
|
1033
|
+
fs.unlinkSync(retryStoreLockPath());
|
|
1034
|
+
} catch (_e) {
|
|
1035
|
+
/* ENOENT or other errors -- silent, matches releaseLock's contract */
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// withRetryStoreLock(fn) -- run fn() with the retry-store file lock held.
|
|
1040
|
+
//
|
|
1041
|
+
// A bare single-shot acquire THROWS when a live holder exists; it does not
|
|
1042
|
+
// wait. The bounded spin below is the whole point of this function, not an
|
|
1043
|
+
// optimization: it retries the acquire up to RETRY_LOCK_MAX_ATTEMPTS times,
|
|
1044
|
+
// sleeping RETRY_LOCK_SLEEP_MS (via a real synchronous Atomics.wait sleep,
|
|
1045
|
+
// never a busy spin) between attempts.
|
|
1046
|
+
//
|
|
1047
|
+
// D-12 (degrade direction on fence exhaustion): if every attempt fails to
|
|
1048
|
+
// acquire, run fn() UNFENCED anyway rather than dropping the increment. That
|
|
1049
|
+
// is exactly today's (pre-238-05) behavior, so this change can never be worse
|
|
1050
|
+
// than the status quo on any path -- dropping the increment is the defect
|
|
1051
|
+
// being fixed, not an acceptable fallback. Nothing is swallowed silently on
|
|
1052
|
+
// this path beyond an optional stderr line (this runs inside a Stop hook, so
|
|
1053
|
+
// it must never throw or print to stdout).
|
|
1054
|
+
function withRetryStoreLock(fn) {
|
|
1055
|
+
for (let attempt = 0; attempt < RETRY_LOCK_MAX_ATTEMPTS; attempt += 1) {
|
|
1056
|
+
try {
|
|
1057
|
+
acquireRetryLock();
|
|
1058
|
+
} catch (_acquireErr) {
|
|
1059
|
+
// Did not win the fence this attempt. Sleep synchronously and retry.
|
|
1060
|
+
// Atomics.wait on a one-element Int32Array backed by a SharedArrayBuffer
|
|
1061
|
+
// is a real blocking sleep Node permits on the main thread, needs no
|
|
1062
|
+
// dependency, and is not a CPU-spinning busy loop.
|
|
1063
|
+
const sab = new SharedArrayBuffer(4);
|
|
1064
|
+
const ia = new Int32Array(sab);
|
|
1065
|
+
Atomics.wait(ia, 0, 0, RETRY_LOCK_SLEEP_MS);
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
return fn();
|
|
1070
|
+
} finally {
|
|
1071
|
+
releaseRetryLock();
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// Fence exhausted (D-12): degrade to the unfenced read-modify-write rather
|
|
1075
|
+
// than dropping the increment. Never worse than pre-238-05 behavior.
|
|
1076
|
+
try {
|
|
1077
|
+
process.stderr.write('[check-card-fire] retry-store lock fence exhausted; writing unfenced\n');
|
|
1078
|
+
} catch (_e) {
|
|
1079
|
+
/* stderr write must never throw the hook */
|
|
1080
|
+
}
|
|
1081
|
+
return fn();
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// readRetryCount(ctxHash) / bumpRetryCount(ctxHash) / clearRetryCount(ctxHash) --
|
|
1085
|
+
// the LOCAL bounded-escape side-file (Part 8 LOCAL-only). Best-effort: any error is
|
|
1086
|
+
// swallowed (a missing/corrupt side-file degrades to a fresh count of 0, never blocks).
|
|
1087
|
+
// ---------------------------------------------------------------------------
|
|
1088
|
+
function readRetryStore() {
|
|
1089
|
+
try {
|
|
1090
|
+
const raw = fs.readFileSync(retryFilePath(), 'utf8');
|
|
1091
|
+
const obj = JSON.parse(raw);
|
|
1092
|
+
return obj && typeof obj === 'object' ? obj : {};
|
|
1093
|
+
} catch (_e) {
|
|
1094
|
+
return {};
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// pruneRetryStore(store, now) -- WR-02: evict entries older than RETRY_TTL_MS so the
|
|
1099
|
+
// side-file cannot grow without bound. Each entry is { count, ts }; a legacy bare-integer
|
|
1100
|
+
// entry (pre-WR-02 format) is normalized to { count, ts: now } on read so it is not lost
|
|
1101
|
+
// but does become subject to the TTL going forward. Pure; returns a fresh pruned store.
|
|
1102
|
+
function pruneRetryStore(store, now) {
|
|
1103
|
+
const t = Number.isFinite(now) ? now : Date.now();
|
|
1104
|
+
const out = {};
|
|
1105
|
+
const src = store && typeof store === 'object' ? store : {};
|
|
1106
|
+
for (const k of Object.keys(src)) {
|
|
1107
|
+
const e = normalizeRetryEntry(src[k], t);
|
|
1108
|
+
if (e && (t - e.ts) <= RETRY_TTL_MS) {
|
|
1109
|
+
out[k] = e;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return out;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// normalizeRetryEntry(v, now) -- coerce a store value to { count, ts }. Tolerates the
|
|
1116
|
+
// legacy bare-integer shape (pre-WR-02) and the { count, ts } shape; anything else -> null.
|
|
1117
|
+
function normalizeRetryEntry(v, now) {
|
|
1118
|
+
const t = Number.isFinite(now) ? now : Date.now();
|
|
1119
|
+
if (Number.isFinite(v)) return { count: v, ts: t };
|
|
1120
|
+
if (v && typeof v === 'object' && Number.isFinite(v.count)) {
|
|
1121
|
+
return { count: v.count, ts: Number.isFinite(v.ts) ? v.ts : t };
|
|
1122
|
+
}
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// 238-05 Task 1 (GATE-03 half B, torn-read fix): writeRetryStore writes via a
|
|
1127
|
+
// tmp-file-plus-renameSync in the SAME directory as the target, mirroring the
|
|
1128
|
+
// sibling side-file's idiom (lib/core/card-fire-sidechannel.cjs::writeStoreAtomic).
|
|
1129
|
+
// Rename is atomic on the same filesystem, which is why the temp file must live
|
|
1130
|
+
// next to the target and never in os.tmpdir().
|
|
1131
|
+
//
|
|
1132
|
+
// What this fixes: a concurrent READER can never observe a partial or
|
|
1133
|
+
// half-written file, because the target path only ever transitions from "old
|
|
1134
|
+
// complete content" to "new complete content" in one atomic filesystem op.
|
|
1135
|
+
//
|
|
1136
|
+
// What this does NOT fix: lost updates. This function still overwrites
|
|
1137
|
+
// whatever `store` it is handed; if two callers both read the pre-state and
|
|
1138
|
+
// both call this with their own read-modify-write, the last rename wins and
|
|
1139
|
+
// the other caller's increment is gone. Closing THAT defect is Task 2's job
|
|
1140
|
+
// (withRetryStoreLock, the bounded-wait fence around the read-modify-write).
|
|
1141
|
+
// Do not mistake one fix for the other.
|
|
1142
|
+
function writeRetryStore(store) {
|
|
1143
|
+
try {
|
|
1144
|
+
const fp = retryFilePath();
|
|
1145
|
+
const dir = path.dirname(fp);
|
|
1146
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1147
|
+
// WR-02: prune on every write.
|
|
1148
|
+
const now = Date.now();
|
|
1149
|
+
const tmp = fp + '.tmp-' + process.pid + '-' + now;
|
|
1150
|
+
fs.writeFileSync(tmp, JSON.stringify(pruneRetryStore(store, now)), 'utf8');
|
|
1151
|
+
fs.renameSync(tmp, fp);
|
|
1152
|
+
} catch (_e) {
|
|
1153
|
+
/* best-effort; never block on a side-file write */
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function readRetryCount(ctxHash) {
|
|
1158
|
+
const store = readRetryStore();
|
|
1159
|
+
const e = normalizeRetryEntry(store[ctxHash], Date.now());
|
|
1160
|
+
return e ? e.count : 0;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// 238-05 Task 2: the read is moved INSIDE the fence. A read taken before the
|
|
1164
|
+
// lock reintroduces the exact lost-update race being fixed (a reader outside
|
|
1165
|
+
// the lock can still see a stale pre-state that a concurrent writer is about
|
|
1166
|
+
// to overwrite with its own stale-based next value).
|
|
1167
|
+
function bumpRetryCount(ctxHash) {
|
|
1168
|
+
return withRetryStoreLock(function () {
|
|
1169
|
+
const store = readRetryStore();
|
|
1170
|
+
const now = Date.now();
|
|
1171
|
+
const e = normalizeRetryEntry(store[ctxHash], now);
|
|
1172
|
+
const next = (e ? e.count : 0) + 1;
|
|
1173
|
+
store[ctxHash] = { count: next, ts: now };
|
|
1174
|
+
writeRetryStore(store);
|
|
1175
|
+
return next;
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function clearRetryCount(ctxHash) {
|
|
1180
|
+
withRetryStoreLock(function () {
|
|
1181
|
+
const store = readRetryStore();
|
|
1182
|
+
if (ctxHash in store) {
|
|
1183
|
+
delete store[ctxHash];
|
|
1184
|
+
writeRetryStore(store);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// ---------------------------------------------------------------------------
|
|
1190
|
+
// CR-04 SESSION-WIDE intercept ceiling -- the content-INDEPENDENT convergence floor.
|
|
1191
|
+
//
|
|
1192
|
+
// sessionKey(sessionId) / readSessionCount(sessionId) / bumpSessionCount(sessionId) /
|
|
1193
|
+
// clearSessionCount(sessionId) -- the session counter lives in the SAME side-file as the per-
|
|
1194
|
+
// gate entries, at `__session__:<sessionId>`, { count, ts } shaped, TTL-pruned identically
|
|
1195
|
+
// (WR-02). It is keyed on the OPAQUE session_id ALONE (no model-emitted content), so it cannot
|
|
1196
|
+
// be flapped: a continuous run of intercepts in ONE session climbs the SAME session counter
|
|
1197
|
+
// REGARDLESS of how the per-gate signature moves, and MAX_SESSION_INTERCEPTS hard-bounds it.
|
|
1198
|
+
//
|
|
1199
|
+
// Part 8: session_id is an opaque conversation identifier (not user content / not artifact
|
|
1200
|
+
// text). The key is `__session__:<opaque-id>`; the value is a scalar count + timestamp. No raw
|
|
1201
|
+
// text egress; the side-file is LOCAL (~/.mindrian) only. Best-effort like the per-gate helpers.
|
|
1202
|
+
// ---------------------------------------------------------------------------
|
|
1203
|
+
function sessionKey(sessionId) {
|
|
1204
|
+
const s = typeof sessionId === 'string' ? sessionId : '';
|
|
1205
|
+
return SESSION_KEY_PREFIX + s;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function readSessionCount(sessionId) {
|
|
1209
|
+
const store = readRetryStore();
|
|
1210
|
+
const e = normalizeRetryEntry(store[sessionKey(sessionId)], Date.now());
|
|
1211
|
+
return e ? e.count : 0;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// 238-05 Task 2: routed through withRetryStoreLock, same read-inside-the-fence
|
|
1215
|
+
// discipline as bumpRetryCount/clearRetryCount above. This counter shares the
|
|
1216
|
+
// SAME file and the SAME write path as the per-gate entries, so a fence
|
|
1217
|
+
// applied to only one accessor family would still lose the other's updates.
|
|
1218
|
+
function bumpSessionCount(sessionId) {
|
|
1219
|
+
return withRetryStoreLock(function () {
|
|
1220
|
+
const store = readRetryStore();
|
|
1221
|
+
const now = Date.now();
|
|
1222
|
+
const key = sessionKey(sessionId);
|
|
1223
|
+
const e = normalizeRetryEntry(store[key], now);
|
|
1224
|
+
const next = (e ? e.count : 0) + 1;
|
|
1225
|
+
store[key] = { count: next, ts: now };
|
|
1226
|
+
writeRetryStore(store);
|
|
1227
|
+
return next;
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function clearSessionCount(sessionId) {
|
|
1232
|
+
withRetryStoreLock(function () {
|
|
1233
|
+
const store = readRetryStore();
|
|
1234
|
+
const key = sessionKey(sessionId);
|
|
1235
|
+
if (key in store) {
|
|
1236
|
+
delete store[key];
|
|
1237
|
+
writeRetryStore(store);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// ---------------------------------------------------------------------------
|
|
1243
|
+
// CR-07 -- the LOCAL append-only intercept diagnostic log. Written whenever
|
|
1244
|
+
// classifyCardFire returns intercept:true OR degrade:true, so the NEXT occurrence of the
|
|
1245
|
+
// still-unexplained backstop trigger (live-session-running-stale-plugin-cache-fixes-inert)
|
|
1246
|
+
// is a one-log-read diagnosis instead of a multi-hour forensic reconstruction. Each record
|
|
1247
|
+
// preserves the ORIGINAL classification slug (verdict.reason) even though the user-facing
|
|
1248
|
+
// envelope `reason` is now a calm phrase (CR-06), so telemetry survives the calming.
|
|
1249
|
+
//
|
|
1250
|
+
// TTL-pruned on every write by the SAME RETRY_TTL_MS the retry side-file uses (WR-02), so
|
|
1251
|
+
// the JSONL cannot grow without bound. Best-effort like the retry helpers: any error is
|
|
1252
|
+
// swallowed -- a diagnostic log write must NEVER block or throw the Stop hook.
|
|
1253
|
+
//
|
|
1254
|
+
// Part 8 (Graph Boundary): LOCAL-only. The record (including the truncated output_text,
|
|
1255
|
+
// an untrusted model turn already on local disk in the transcript) is written to
|
|
1256
|
+
// ~/.mindrian on local disk, never to the Brain and never over any network wire.
|
|
1257
|
+
// ---------------------------------------------------------------------------
|
|
1258
|
+
function readInterceptLogLines(fp, now) {
|
|
1259
|
+
const out = [];
|
|
1260
|
+
try {
|
|
1261
|
+
const raw = fs.readFileSync(fp, 'utf8');
|
|
1262
|
+
const lines = raw.split('\n');
|
|
1263
|
+
for (const line of lines) {
|
|
1264
|
+
const trimmed = line.trim();
|
|
1265
|
+
if (!trimmed) continue;
|
|
1266
|
+
let obj;
|
|
1267
|
+
try { obj = JSON.parse(trimmed); } catch (_e) { continue; }
|
|
1268
|
+
// TTL-prune: keep only records younger than RETRY_TTL_MS (WR-02 parity).
|
|
1269
|
+
if (obj && Number.isFinite(obj.ts) && (now - obj.ts) <= RETRY_TTL_MS) {
|
|
1270
|
+
out.push(trimmed);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
} catch (_e) {
|
|
1274
|
+
/* missing / unreadable -> fresh log */
|
|
1275
|
+
}
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function appendInterceptLog(turn, verdict) {
|
|
1280
|
+
try {
|
|
1281
|
+
const t = turn && typeof turn === 'object' ? turn : {};
|
|
1282
|
+
const v = verdict && typeof verdict === 'object' ? verdict : {};
|
|
1283
|
+
const now = Date.now();
|
|
1284
|
+
const outputText = typeof t.output_text === 'string' ? t.output_text : '';
|
|
1285
|
+
const record = {
|
|
1286
|
+
ts: now,
|
|
1287
|
+
timestamp: new Date(now).toISOString(),
|
|
1288
|
+
session_id: typeof t.session_id === 'string' ? t.session_id : '',
|
|
1289
|
+
// The ORIGINAL classification slug -- preserved here even though the user-facing
|
|
1290
|
+
// envelope `reason` changes to a calm phrase (CR-06). This is the telemetry sink.
|
|
1291
|
+
reason: typeof v.reason === 'string' ? v.reason : '',
|
|
1292
|
+
gate_signature: typeof t.gate_signature === 'string' ? t.gate_signature : '',
|
|
1293
|
+
ran_entries: Array.isArray(t.ran_entries) ? t.ran_entries : [],
|
|
1294
|
+
matched_glyph_span: matchedGlyphSpan(outputText),
|
|
1295
|
+
output_text: outputText.slice(0, INTERCEPT_LOG_TEXT_CAP),
|
|
1296
|
+
};
|
|
1297
|
+
const fp = interceptLogPath();
|
|
1298
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
|
1299
|
+
const kept = readInterceptLogLines(fp, now);
|
|
1300
|
+
kept.push(JSON.stringify(record));
|
|
1301
|
+
fs.writeFileSync(fp, kept.join('\n') + '\n', 'utf8');
|
|
1302
|
+
} catch (_e) {
|
|
1303
|
+
/* best-effort; never block the hook on a diagnostic-log write */
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// ---------------------------------------------------------------------------
|
|
1308
|
+
// consumeReachedGatesForVerdict(turn, verdict) -- card-fire-answered-gate-refires-
|
|
1309
|
+
// within-ttl-window (2026-07-28). THE record-lifecycle wire: spend the side-channel
|
|
1310
|
+
// reached-gate records once this Stop evaluation has reached a TERMINAL verdict on
|
|
1311
|
+
// them, so a single gate mint can never be re-litigated on a LATER turn.
|
|
1312
|
+
//
|
|
1313
|
+
// THE GAP THIS CLOSES (confirmed from source + the live ~/.mindrian/card-fire-
|
|
1314
|
+
// intercepts.log, 6 real records, every one of them reason 'reached-registry-gate-
|
|
1315
|
+
// no-card' with ran_entries ["scripts/intent-classifier.cjs"] and an empty/benign
|
|
1316
|
+
// matched_glyph_span -- i.e. every one a PRIMARY-arm hit on a turn whose own output
|
|
1317
|
+
// could not possibly match ASCII_BOX_UNCONDITIONAL_RE): the side-channel record had
|
|
1318
|
+
// no lifecycle at all. Its ONLY exit was the 10-minute TTL. So the PRIMARY arm kept
|
|
1319
|
+
// asserting "a gate was reached, force the card" on EVERY Stop evaluation inside the
|
|
1320
|
+
// 2-minute TURN_FRESH_MS window -- including the turns AFTER the navigator had
|
|
1321
|
+
// already fired the card and answered it.
|
|
1322
|
+
//
|
|
1323
|
+
// WHY THE EXISTING ESCAPES CANNOT COVER IT (each was checked against this shape):
|
|
1324
|
+
// - askuserquestion_fired is CURRENT-TURN scoped (readTranscriptTurn resets its
|
|
1325
|
+
// window at every role:user boundary), so a card fired and answered in turn N is
|
|
1326
|
+
// structurally invisible to turn N+1's Stop hook.
|
|
1327
|
+
// - gate-already-answered derives gateLabels from THIS turn's outputText, which is
|
|
1328
|
+
// EMPTY on a no-card prose turn, so that branch returns false before it can help.
|
|
1329
|
+
// - gate_is_fresh encodes elapsed time since mint, never still-pending state; a
|
|
1330
|
+
// gate answered 30 seconds ago and a gate still sitting unanswered 30 seconds ago
|
|
1331
|
+
// are byte-identical to it.
|
|
1332
|
+
// - preceding-turn-synthetic-no-user-engagement only fires on a CONFIRMED
|
|
1333
|
+
// 'tool_result' record; a background task-notification carrying a real text block
|
|
1334
|
+
// classifies as 'typed' and falls straight through.
|
|
1335
|
+
//
|
|
1336
|
+
// TERMINAL vs ACTIVE (the load-bearing distinction): consume on card-fired /
|
|
1337
|
+
// no-gate-signal / any relevance pass / a bounded-escape degrade. NEVER consume on
|
|
1338
|
+
// intercept:true -- an intercept means the force-loop is still ACTIVE, the model is
|
|
1339
|
+
// about to be re-prompted, and the record must survive so the re-prompted turn is
|
|
1340
|
+
// re-checked. Consuming there would make MAX_FORCE_RETRIES unreachable and silently
|
|
1341
|
+
// turn the force-fire into a single-shot -- a reverse regression on the Phase 209
|
|
1342
|
+
// guarantee, which is exactly as bad as the over-enforcement this fix removes.
|
|
1343
|
+
//
|
|
1344
|
+
// Best-effort like every other side-file helper here: a missing module, a missing
|
|
1345
|
+
// export, or any fs fault degrades to a silent 0. A consumption fault must NEVER
|
|
1346
|
+
// block or throw the Stop hook (T-209-24). Part 8: LOCAL-only, deletes local scalars.
|
|
1347
|
+
// ---------------------------------------------------------------------------
|
|
1348
|
+
function consumeReachedGatesForVerdict(turn, verdict) {
|
|
1349
|
+
try {
|
|
1350
|
+
const t = turn && typeof turn === 'object' ? turn : {};
|
|
1351
|
+
const v = verdict && typeof verdict === 'object' ? verdict : {};
|
|
1352
|
+
// ACTIVE force-loop: keep the record alive so the re-prompted turn re-checks it.
|
|
1353
|
+
if (v.intercept === true) return 0;
|
|
1354
|
+
const sidechannel = require(path.join(PLUGIN_ROOT, 'lib', 'core', 'card-fire-sidechannel.cjs'));
|
|
1355
|
+
if (typeof sidechannel.consumeReachedGates !== 'function') return 0;
|
|
1356
|
+
return sidechannel.consumeReachedGates(
|
|
1357
|
+
typeof t.session_id === 'string' ? t.session_id : ''
|
|
1358
|
+
);
|
|
1359
|
+
} catch (_e) {
|
|
1360
|
+
return 0;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// ----- stdin / envelope helpers (the on-stop idiom from operator-update.cjs) -----
|
|
1365
|
+
|
|
1366
|
+
function readStdinJson() {
|
|
1367
|
+
try {
|
|
1368
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
1369
|
+
if (!raw || !raw.trim()) return {};
|
|
1370
|
+
return JSON.parse(raw);
|
|
1371
|
+
} catch (_e) {
|
|
1372
|
+
return {};
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function emitEnvelope(obj) {
|
|
1377
|
+
const filtered = {};
|
|
1378
|
+
for (const k of Object.keys(obj || {})) {
|
|
1379
|
+
if (ALLOWED_ENVELOPE_KEYS.has(k)) filtered[k] = obj[k];
|
|
1380
|
+
}
|
|
1381
|
+
if (filtered.continue === undefined && filtered.decision === undefined) {
|
|
1382
|
+
filtered.continue = true;
|
|
1383
|
+
}
|
|
1384
|
+
process.stdout.write(JSON.stringify(filtered));
|
|
1385
|
+
process.exit(0);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function silentSuccess() {
|
|
1389
|
+
emitEnvelope({ continue: true, suppressOutput: true });
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// ---------------------------------------------------------------------------
|
|
1393
|
+
// readTranscriptTurn(transcriptPath) -- parse the Stop-hook transcript JSONL and extract
|
|
1394
|
+
// the signals a transcript can yield. Mirrors the scripts/on-stop:33 idiom: the Stop hook
|
|
1395
|
+
// delivers a `transcript_path` (a LOCAL .jsonl file, one JSON object per line).
|
|
1396
|
+
//
|
|
1397
|
+
// Phase 298-03: the parse walk itself (WR-08 tail-capped read, per-line JSON parse,
|
|
1398
|
+
// role:user window reset, assistant-content collection) moved to
|
|
1399
|
+
// lib/hmi/turn-text.cjs::readTurnText, the single transcript reader shared with a
|
|
1400
|
+
// later Stop-hook consumer (298-07). This function is now a thin wrapper: it calls
|
|
1401
|
+
// turnText.readTurnText for the shared parse, then computes the two fields that stay
|
|
1402
|
+
// card-fire DOMAIN logic (they need gateRelevance / ASCII_BOX_GLYPH_RE, which
|
|
1403
|
+
// turn-text.cjs deliberately does not depend on):
|
|
1404
|
+
// - askuserquestion_fired: true ONLY if the LAST assistant message fired the AskUserQuestion
|
|
1405
|
+
// card. Scoped to the SAME message that produced output_text (WR-06):
|
|
1406
|
+
// a STALE earlier card must NOT suppress interception of the CURRENT
|
|
1407
|
+
// (last) box turn -- the two signals MUST share the same turn scope.
|
|
1408
|
+
// - gate_signature : the CR-03 / WR-07 transcript-growth-INVARIANT gate identity -- a
|
|
1409
|
+
// sha256 over the GATE-IDENTIFYING content of the LAST assistant
|
|
1410
|
+
// message (the matched glyph span + normalized option-label SET).
|
|
1411
|
+
// Anchored on the GATE, never the user message and never an assistant
|
|
1412
|
+
// counter, so the no-role:user path has no livelock hole (CR-03) and
|
|
1413
|
+
// two distinct gates get distinct counters (WR-07).
|
|
1414
|
+
// Defensive (Part 8 / on-stop discipline): a missing / unreadable / malformed transcript
|
|
1415
|
+
// returns empty signals; NEVER throws (turnText.readTurnText already degrades this way; this
|
|
1416
|
+
// wrapper's own additions -- scanContentForAskUserQuestion and gateSignature -- are themselves
|
|
1417
|
+
// never-throw). The transcript content stays LOCAL -- read, scanned, discarded; never egressed
|
|
1418
|
+
// (gate_signature is a sha256 hash, not the message text itself).
|
|
1419
|
+
// ---------------------------------------------------------------------------
|
|
1420
|
+
function readTranscriptTurn(transcriptPath) {
|
|
1421
|
+
const turn = turnText.readTurnText(transcriptPath);
|
|
1422
|
+
// H2: askFired is an OR across every assistant message since the last user
|
|
1423
|
+
// boundary (the CURRENT turn), not the last message alone.
|
|
1424
|
+
const askFired = turn.assistant_contents.some(turnText.scanContentForAskUserQuestion);
|
|
1425
|
+
// CR-03 / WR-07 ROOT ANCHOR: the gate signature over the LAST assistant message's
|
|
1426
|
+
// gate-identifying content (Part 8: a sha256 hash, the raw message text never leaves this
|
|
1427
|
+
// function). Growth-invariant, per-gate, present whenever a gate is detected.
|
|
1428
|
+
const gateSig = gateSignature(turn.output_text);
|
|
1429
|
+
return {
|
|
1430
|
+
output_text: turn.output_text,
|
|
1431
|
+
askuserquestion_fired: askFired,
|
|
1432
|
+
gate_signature: gateSig,
|
|
1433
|
+
preceding_user_text: turn.preceding_user_text,
|
|
1434
|
+
preceding_user_text_source: turn.preceding_user_text_source,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// readTranscriptTail(transcriptPath) -- re-exported from lib/hmi/turn-text.cjs (298-03 lift);
|
|
1439
|
+
// this file's own export name is unchanged so lib/mcp/stop-gate-handler.cjs and any other
|
|
1440
|
+
// consumer of check-card-fire.cjs's exports.readTranscriptTail keep working unmodified.
|
|
1441
|
+
const readTranscriptTail = turnText.readTranscriptTail;
|
|
1442
|
+
|
|
1443
|
+
// ---------------------------------------------------------------------------
|
|
1444
|
+
// sidechannelReachIsFresh(sidechannelModule, sessionId) -- Phase 238-08 (GATE-04,
|
|
1445
|
+
// D-15): true when the most recent side-channel reach record for sessionId falls
|
|
1446
|
+
// within TURN_FRESH_MS of now. This is the SAME mostRecentReachedTs-vs-TURN_FRESH_MS
|
|
1447
|
+
// formula deriveTurnSignals already needed for gateIsFresh (the PRIMARY-path
|
|
1448
|
+
// staleness signal, card-fire-over-enforcement fix B); factored out here so it is
|
|
1449
|
+
// written ONCE and reused for BOTH gateIsFresh and the new reach_corroborated signal
|
|
1450
|
+
// below, rather than duplicated. Never throws: mostRecentReachedTs itself already
|
|
1451
|
+
// degrades to 0 on any fault (never-throw contract, T-209-24), and this wrapper
|
|
1452
|
+
// degrades to false on top of that for defensive symmetry with the rest of this file.
|
|
1453
|
+
// ---------------------------------------------------------------------------
|
|
1454
|
+
function sidechannelReachIsFresh(sidechannelModule, sessionId) {
|
|
1455
|
+
try {
|
|
1456
|
+
const ts = sidechannelModule.mostRecentReachedTs(sessionId);
|
|
1457
|
+
const freshWindow = typeof sidechannelModule.TURN_FRESH_MS === 'number'
|
|
1458
|
+
? sidechannelModule.TURN_FRESH_MS
|
|
1459
|
+
: (2 * 60 * 1000);
|
|
1460
|
+
return Number.isFinite(ts) && ts > 0 && (Date.now() - ts) <= freshWindow;
|
|
1461
|
+
} catch (_e) {
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// ---------------------------------------------------------------------------
|
|
1467
|
+
// deriveTurnSignals(env) -- normalize the turn signals from the Stop-hook stdin envelope.
|
|
1468
|
+
//
|
|
1469
|
+
// BL-01 (the live cure): the REAL Stop-hook stdin delivers a `transcript_path`, NOT the
|
|
1470
|
+
// normalized fields. So when the envelope carries `transcript_path`, we PARSE the transcript
|
|
1471
|
+
// (readTranscriptTurn, mirroring scripts/on-stop:33): the last assistant text drives the
|
|
1472
|
+
// BACKSTOP and an AskUserQuestion tool-use drives askuserquestion_fired.
|
|
1473
|
+
//
|
|
1474
|
+
// BACKWARD-COMPAT: if the envelope already carries the normalized signals directly
|
|
1475
|
+
// (output_text / last_assistant_text / ran_entries / askuserquestion_fired -- the unit-test
|
|
1476
|
+
// shape), those are used as-is, so the 22 existing predicate assertions stay green.
|
|
1477
|
+
//
|
|
1478
|
+
// session_id + gate_signature (CR-03 / WR-07 root anchor) thread through for the
|
|
1479
|
+
// WR-01/CR-02/CR-03 stable retry key. PRIMARY's ran_entries stays read from the envelope (it
|
|
1480
|
+
// has no transcript source -- the WR-04 deferred note). Tolerant of missing fields; never throws.
|
|
1481
|
+
// ---------------------------------------------------------------------------
|
|
1482
|
+
function deriveTurnSignals(env) {
|
|
1483
|
+
const e = env && typeof env === 'object' ? env : {};
|
|
1484
|
+
|
|
1485
|
+
// PRIMARY ran-entries: side-channel only (no transcript source -- WR-04 deferred).
|
|
1486
|
+
// Phase 209-06 (H3): WR-04's doctrine note is UPDATED here, not just above --
|
|
1487
|
+
// the producer now exists (lib/core/card-fire-sidechannel.cjs, wired at the
|
|
1488
|
+
// pickShape trailer door + the two intent-classifier.cjs sites: the engine
|
|
1489
|
+
// arm and emitBindingGate). Direct-field envelopes (the unit-test shape)
|
|
1490
|
+
// keep PRECEDENCE: the side file is consulted ONLY when the envelope did
|
|
1491
|
+
// NOT already carry ran_entries/reached_gate_entries, so the 22 existing
|
|
1492
|
+
// predicate assertions stay green byte-for-byte. Regex BACKSTOP stays
|
|
1493
|
+
// SECONDARY permanently -- it is the only detector when the side-channel
|
|
1494
|
+
// writer itself fails (a missing/corrupt/oversized file all degrade to []
|
|
1495
|
+
// inside readReachedGates, never throwing here).
|
|
1496
|
+
let ranEntries = Array.isArray(e.ran_entries)
|
|
1497
|
+
? e.ran_entries
|
|
1498
|
+
: (Array.isArray(e.reached_gate_entries) ? e.reached_gate_entries : []);
|
|
1499
|
+
|
|
1500
|
+
// 2026-07-05 relevance-gate fix: the reach's OWN recorded subject text
|
|
1501
|
+
// (side-channel only, populated ONLY when the side-channel itself supplied
|
|
1502
|
+
// ranEntries -- i.e. the true PRIMARY path). '' on the BACKSTOP path (no
|
|
1503
|
+
// side-channel producer runs there), so classifyCardFire falls back to its
|
|
1504
|
+
// prior output_text comparison unchanged for that path.
|
|
1505
|
+
let sideChannelSubjectText = '';
|
|
1506
|
+
|
|
1507
|
+
// card-fire-over-enforcement (2026-07-20) fix (B): whether the reached gate is
|
|
1508
|
+
// FRESH (reached this turn) or STALE (a prior turn's reach bled forward by the
|
|
1509
|
+
// file TTL). Direct-field / BACKSTOP gates are always this-turn, so freshness
|
|
1510
|
+
// defaults to true; only a side-channel-sourced gate can be stale, decided by
|
|
1511
|
+
// comparing its most-recent mint ts against TURN_FRESH_MS below.
|
|
1512
|
+
let gateIsFresh = true;
|
|
1513
|
+
|
|
1514
|
+
if (ranEntries.length === 0) {
|
|
1515
|
+
try {
|
|
1516
|
+
const sidechannel = require(path.join(PLUGIN_ROOT, 'lib', 'core', 'card-fire-sidechannel.cjs'));
|
|
1517
|
+
const sessionId = typeof e.session_id === 'string' ? e.session_id : '';
|
|
1518
|
+
const fromSideChannel = sidechannel.readReachedGates(sessionId);
|
|
1519
|
+
if (Array.isArray(fromSideChannel) && fromSideChannel.length > 0) {
|
|
1520
|
+
ranEntries = fromSideChannel;
|
|
1521
|
+
try {
|
|
1522
|
+
const subjects = sidechannel.readReachedGateSubjects(sessionId);
|
|
1523
|
+
sideChannelSubjectText = Array.isArray(subjects) ? subjects.join(' ') : '';
|
|
1524
|
+
} catch (_e2) {
|
|
1525
|
+
sideChannelSubjectText = '';
|
|
1526
|
+
}
|
|
1527
|
+
// Freshness verdict: a side-channel gate counts as reached THIS turn only
|
|
1528
|
+
// if its most recent mint is within the turn window. An older mint is a
|
|
1529
|
+
// prior turn's (or prior session's) reach still inside the file TTL.
|
|
1530
|
+
// Phase 238-08: reuses sidechannelReachIsFresh (the IDENTICAL
|
|
1531
|
+
// mostRecentReachedTs-vs-TURN_FRESH_MS formula, also used below for
|
|
1532
|
+
// reach_corroborated) rather than computing it a second time inline.
|
|
1533
|
+
gateIsFresh = sidechannelReachIsFresh(sidechannel, sessionId);
|
|
1534
|
+
}
|
|
1535
|
+
} catch (_e) {
|
|
1536
|
+
/* degrade to empty ran_entries; BACKSTOP alone still applies */
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Phase 238-08 (GATE-04, D-15): reach_corroborated -- true when a fresh reach
|
|
1541
|
+
// record exists for THIS session. Computed UNCONDITIONALLY (not only inside the
|
|
1542
|
+
// ran_entries-empty branch above), because a turn can carry non-empty direct-field
|
|
1543
|
+
// ran_entries (the PRIMARY path already supplying its own entries) and still need
|
|
1544
|
+
// this corroboration signal for the BACKSTOP arm's decision in classifyCardFire.
|
|
1545
|
+
// Direct-field envelopes keep PRECEDENCE, mirroring ran_entries / gate_subject_text
|
|
1546
|
+
// above, so the unit-test seam still works. Uses the SAME sidechannelReachIsFresh
|
|
1547
|
+
// helper the ran_entries-empty branch above reuses for gateIsFresh, via a SEPARATE
|
|
1548
|
+
// guarded require (this module intentionally requires the side-channel module more
|
|
1549
|
+
// than once across this function rather than threading a shared handle through
|
|
1550
|
+
// unrelated branches; every guarded require degrades identically on a require
|
|
1551
|
+
// fault). A require fault or read fault degrades to false (uncorroborated) --
|
|
1552
|
+
// there is no evidence of a fresh reach when the side channel cannot be consulted.
|
|
1553
|
+
let reachCorroborated = false;
|
|
1554
|
+
if (typeof e.reach_corroborated === 'boolean') {
|
|
1555
|
+
reachCorroborated = e.reach_corroborated;
|
|
1556
|
+
} else {
|
|
1557
|
+
try {
|
|
1558
|
+
const sidechannelForReach = require(path.join(PLUGIN_ROOT, 'lib', 'core', 'card-fire-sidechannel.cjs'));
|
|
1559
|
+
const sessionIdForReach = typeof e.session_id === 'string' ? e.session_id : '';
|
|
1560
|
+
reachCorroborated = sidechannelReachIsFresh(sidechannelForReach, sessionIdForReach);
|
|
1561
|
+
} catch (_eReach) {
|
|
1562
|
+
reachCorroborated = false;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// Phase 238-08 (GATE-04, D-15): sidechannel_health -- resolved via a SEPARATE
|
|
1567
|
+
// guarded require of the SAME module the ran_entries-empty branch above uses for
|
|
1568
|
+
// the reach-record read (the same "require it fresh at each use site" style as
|
|
1569
|
+
// reach_corroborated above). A require fault degrades to the unavailable value,
|
|
1570
|
+
// matching the existing degrade direction elsewhere in this function: a missing or
|
|
1571
|
+
// broken module means the side channel cannot be consulted at all, which is the
|
|
1572
|
+
// blind state the backstop's last-resort arm must stay armed for (D-15 state 3).
|
|
1573
|
+
// Direct-field envelopes keep PRECEDENCE, mirroring ran_entries / gate_subject_text
|
|
1574
|
+
// above, so the unit-test seam still works.
|
|
1575
|
+
let sidechannelHealth = SIDECHANNEL_HEALTH_UNAVAILABLE;
|
|
1576
|
+
if (typeof e.sidechannel_health === 'string' && e.sidechannel_health) {
|
|
1577
|
+
sidechannelHealth = e.sidechannel_health;
|
|
1578
|
+
} else {
|
|
1579
|
+
try {
|
|
1580
|
+
const sidechannelForHealth = require(path.join(PLUGIN_ROOT, 'lib', 'core', 'card-fire-sidechannel.cjs'));
|
|
1581
|
+
sidechannelHealth = sidechannelForHealth.sideChannelHealth();
|
|
1582
|
+
} catch (_eHealth) {
|
|
1583
|
+
sidechannelHealth = SIDECHANNEL_HEALTH_UNAVAILABLE;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Direct-field signals (the unit-test / side-channel shape).
|
|
1588
|
+
const directText =
|
|
1589
|
+
typeof e.output_text === 'string' ? e.output_text
|
|
1590
|
+
: (typeof e.last_assistant_text === 'string' ? e.last_assistant_text : null);
|
|
1591
|
+
const directAsk =
|
|
1592
|
+
e.askuserquestion_fired === true || e.ask_user_question_fired === true;
|
|
1593
|
+
|
|
1594
|
+
// If no direct text was supplied but a transcript_path is present, parse the transcript
|
|
1595
|
+
// (the REAL Stop contract). Direct fields take precedence (backward-compat).
|
|
1596
|
+
let txn = null;
|
|
1597
|
+
if (directText === null && typeof e.transcript_path === 'string') {
|
|
1598
|
+
txn = readTranscriptTurn(e.transcript_path);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const outputText = directText !== null ? directText : (txn ? txn.output_text : '');
|
|
1602
|
+
const askFired = directAsk || (txn ? txn.askuserquestion_fired : false);
|
|
1603
|
+
// CR-03 / WR-07: the transcript-growth-invariant, per-gate retry anchor. Precedence:
|
|
1604
|
+
// 1. an explicit gate_signature on the envelope (test / side-channel shape);
|
|
1605
|
+
// 2. the parsed transcript's gate signature (the LIVE BACKSTOP path);
|
|
1606
|
+
// 3. DERIVED from the output text (the direct-field unit-test shape that supplies
|
|
1607
|
+
// output_text but no transcript) so the key is stable without re-walking a transcript.
|
|
1608
|
+
// It is NEVER a transcript-length counter -- the gate_turn_index fallback is DELETED.
|
|
1609
|
+
let gateSig = '';
|
|
1610
|
+
if (typeof e.gate_signature === 'string' && e.gate_signature) {
|
|
1611
|
+
gateSig = e.gate_signature;
|
|
1612
|
+
} else if (txn && typeof txn.gate_signature === 'string' && txn.gate_signature) {
|
|
1613
|
+
gateSig = txn.gate_signature;
|
|
1614
|
+
} else {
|
|
1615
|
+
gateSig = gateSignature(outputText);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// Phase 210-05 (item 210-E-1): thread the preceding user text through for the
|
|
1619
|
+
// relevance verdict. Direct-field envelopes may supply it; the transcript path
|
|
1620
|
+
// derives it. Absent on both -> '' (the conservative verdict: intercept). Per
|
|
1621
|
+
// CR-03 it never feeds gate_signature or turnContextHash.
|
|
1622
|
+
const precedingUserText = typeof e.preceding_user_text === 'string'
|
|
1623
|
+
? e.preceding_user_text
|
|
1624
|
+
: (txn ? txn.preceding_user_text : '');
|
|
1625
|
+
|
|
1626
|
+
// room-bind-gate-fires-on-notification-only-turns (2026-07-23): thread the preceding-turn
|
|
1627
|
+
// source classification through the same precedence rule as precedingUserText itself
|
|
1628
|
+
// (direct-field envelopes -- the unit-test seam -- keep precedence; the transcript path
|
|
1629
|
+
// derives it; absent on both defaults to 'none', the UNEXPLAINED-absence case, which stays
|
|
1630
|
+
// on the pre-fix conservative floor -- only a CONFIRMED 'tool_result' classification bypasses
|
|
1631
|
+
// it). Feeds ONLY the PRIMARY-path relevance check in classifyCardFire, per the same CR-03
|
|
1632
|
+
// scoping as precedingUserText.
|
|
1633
|
+
const precedingUserTextSource = typeof e.preceding_user_text_source === 'string'
|
|
1634
|
+
? e.preceding_user_text_source
|
|
1635
|
+
: (txn ? txn.preceding_user_text_source : 'none');
|
|
1636
|
+
|
|
1637
|
+
return {
|
|
1638
|
+
ran_entries: ranEntries,
|
|
1639
|
+
askuserquestion_fired: askFired,
|
|
1640
|
+
output_text: outputText,
|
|
1641
|
+
session_id: typeof e.session_id === 'string' ? e.session_id : '',
|
|
1642
|
+
gate_signature: gateSig,
|
|
1643
|
+
preceding_user_text: precedingUserText,
|
|
1644
|
+
preceding_user_text_source: precedingUserTextSource,
|
|
1645
|
+
// 2026-07-05 relevance-gate fix: direct-field envelopes (the unit-test
|
|
1646
|
+
// seam) keep precedence over the side-channel-derived subject text,
|
|
1647
|
+
// mirroring the existing ran_entries precedence rule exactly.
|
|
1648
|
+
gate_subject_text: (typeof e.gate_subject_text === 'string' && e.gate_subject_text)
|
|
1649
|
+
? e.gate_subject_text
|
|
1650
|
+
: sideChannelSubjectText,
|
|
1651
|
+
// card-fire-over-enforcement (2026-07-20) fix (B): true when the reached
|
|
1652
|
+
// gate is this-turn (direct-field / BACKSTOP / a fresh side-channel mint),
|
|
1653
|
+
// false when a side-channel mint is stale (a prior turn's reach bled forward
|
|
1654
|
+
// by the file TTL). classifyCardFire feeds this to the low-signal relevance
|
|
1655
|
+
// branch so a terse turn no longer force-fires a stale gate.
|
|
1656
|
+
gate_is_fresh: gateIsFresh,
|
|
1657
|
+
// Phase 238-08 (GATE-04, D-15): the two new backstop-corroboration signals.
|
|
1658
|
+
// sidechannel_health distinguishes a healthy-but-empty side channel from a
|
|
1659
|
+
// blind one; reach_corroborated is true only when a FRESH reach record exists
|
|
1660
|
+
// for this session. classifyCardFire's new branch reads both to decide whether
|
|
1661
|
+
// an uncorroborated backstop hit on a healthy side channel should intercept.
|
|
1662
|
+
sidechannel_health: sidechannelHealth,
|
|
1663
|
+
reach_corroborated: reachCorroborated,
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// ----- main (Stop-hook entry) -----
|
|
1668
|
+
|
|
1669
|
+
function main() {
|
|
1670
|
+
// Phase 198-09 (D-05): once the Stop-gate enforcement has moved server-side
|
|
1671
|
+
// (scripts/on-stop's own thin adapter, querying lib/mcp/tools/stop-gate.cjs
|
|
1672
|
+
// -> lib/mcp/stop-gate-handler.cjs, which WRAPS this exact predicate --
|
|
1673
|
+
// Part 7, no fork), this hook-side copy must no-op rather than run a SECOND,
|
|
1674
|
+
// independent enforcement pass in parallel (hooks/hooks.json dispatches
|
|
1675
|
+
// on-stop and this script as two SEPARATE Stop hook entries; running both
|
|
1676
|
+
// enforcement paths at once would double-block or race on the same retry
|
|
1677
|
+
// side-file). Flag OFF (unset/empty, the default) keeps this entire file's
|
|
1678
|
+
// call path byte-identical -- the check below is the ONLY change to the
|
|
1679
|
+
// OFF behavior (a no-op it can never reach), everything past this point is
|
|
1680
|
+
// untouched. The daemon-side path decides for itself via the SAME
|
|
1681
|
+
// MINDRIAN_MCP_FIRST contract (lib/mcp/mcp-first-flag.cjs's isMcpFirst),
|
|
1682
|
+
// required here rather than re-checked via a bash mirror since this file is
|
|
1683
|
+
// already Node.
|
|
1684
|
+
try {
|
|
1685
|
+
const { isMcpFirst } = require(path.join(PLUGIN_ROOT, 'lib', 'mcp', 'mcp-first-flag.cjs'));
|
|
1686
|
+
if (isMcpFirst('cli')) return silentSuccess();
|
|
1687
|
+
} catch (_e) {
|
|
1688
|
+
// A flag-reader failure must never suppress enforcement -- fall through
|
|
1689
|
+
// to the full legacy predicate below (the conservative direction).
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
const env = readStdinJson();
|
|
1693
|
+
const evt = env.hook_event_name || env.hookEventName || process.env.HOOK_EVENT_NAME || null;
|
|
1694
|
+
|
|
1695
|
+
// Only act on the Stop event; any other event is a no-op (defensive: do not block).
|
|
1696
|
+
if (evt && evt !== 'Stop') return silentSuccess();
|
|
1697
|
+
|
|
1698
|
+
const registry = loadRegistry();
|
|
1699
|
+
const turn = deriveTurnSignals(env);
|
|
1700
|
+
const sessionId = turn.session_id;
|
|
1701
|
+
const ctxHash = turnContextHash(turn);
|
|
1702
|
+
// Feed BOTH counters into the predicate: the per-gate counter (per-gate UX granularity) and
|
|
1703
|
+
// the SESSION-wide counter (the CR-04 content-independent convergence floor). The predicate
|
|
1704
|
+
// degrades if EITHER ceiling is reached.
|
|
1705
|
+
turn.retry_count = readRetryCount(ctxHash);
|
|
1706
|
+
turn.session_count = readSessionCount(sessionId);
|
|
1707
|
+
|
|
1708
|
+
const verdict = classifyCardFire(turn, registry);
|
|
1709
|
+
|
|
1710
|
+
if (verdict.degrade === true) {
|
|
1711
|
+
// Bounded escape released (per-gate ceiling OR the CR-04 session ceiling): log + allow.
|
|
1712
|
+
// Clear BOTH the per-gate counter AND the session counter -- the navigator is being let
|
|
1713
|
+
// through, so the session is "unstuck" and the next continuous run starts fresh. Clearing
|
|
1714
|
+
// the session counter here is the sane reset: a continuous run of intercepts is bounded by
|
|
1715
|
+
// MAX_SESSION_INTERCEPTS, and a degrade ENDS that run.
|
|
1716
|
+
process.stderr.write(
|
|
1717
|
+
'[check-card-fire] bounded escape released; allowing turn (' + verdict.reason + ')\n'
|
|
1718
|
+
);
|
|
1719
|
+
// CR-07: record the degrade with its ORIGINAL slug before the envelope calms `reason`.
|
|
1720
|
+
appendInterceptLog(turn, verdict);
|
|
1721
|
+
clearRetryCount(ctxHash);
|
|
1722
|
+
clearSessionCount(sessionId);
|
|
1723
|
+
// TERMINAL verdict: the bounded escape released this gate, so its side-channel
|
|
1724
|
+
// record is spent and must not force again on a later turn.
|
|
1725
|
+
consumeReachedGatesForVerdict(turn, verdict);
|
|
1726
|
+
return emitEnvelope(buildEnforcementEnvelope(verdict));
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
if (verdict.intercept === true) {
|
|
1730
|
+
// Force the card: bump BOTH the per-gate counter AND the session-wide counter, then emit
|
|
1731
|
+
// the Stop-block (decision:block-on-exit-0). Bumping the session counter on EVERY intercept
|
|
1732
|
+
// is what makes a flapping per-gate key (CR-04) still converge: the session counter climbs
|
|
1733
|
+
// regardless of which per-gate key the intercept landed on.
|
|
1734
|
+
bumpRetryCount(ctxHash);
|
|
1735
|
+
bumpSessionCount(sessionId);
|
|
1736
|
+
// CR-07: record the intercept with its ORIGINAL slug before the envelope calms `reason`.
|
|
1737
|
+
appendInterceptLog(turn, verdict);
|
|
1738
|
+
return emitEnvelope(buildEnforcementEnvelope(verdict));
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// The card fired (or no gate signal): the navigator got unstuck this turn, so clear the per-
|
|
1742
|
+
// gate counter AND reset the session counter (a no-intercept Stop turn ends any intercept run).
|
|
1743
|
+
clearRetryCount(ctxHash);
|
|
1744
|
+
clearSessionCount(sessionId);
|
|
1745
|
+
// TERMINAL verdict (card-fired / no-gate-signal / a relevance pass): this Stop
|
|
1746
|
+
// evaluation adjudicated the reached-gate records, so they are spent. Without this
|
|
1747
|
+
// the SAME mint kept forcing on every later turn inside its TURN_FRESH_MS window --
|
|
1748
|
+
// the answered-gate-refire defect (2026-07-28). See consumeReachedGatesForVerdict.
|
|
1749
|
+
consumeReachedGatesForVerdict(turn, verdict);
|
|
1750
|
+
return silentSuccess();
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
module.exports = {
|
|
1754
|
+
MAX_FORCE_RETRIES,
|
|
1755
|
+
MAX_SESSION_INTERCEPTS,
|
|
1756
|
+
SESSION_KEY_PREFIX,
|
|
1757
|
+
RETRY_TTL_MS,
|
|
1758
|
+
TRANSCRIPT_TAIL_BYTES: turnText.TRANSCRIPT_TAIL_BYTES,
|
|
1759
|
+
ASCII_BOX_GLYPH_RE,
|
|
1760
|
+
computeBackstopHit,
|
|
1761
|
+
matchedGlyphSpan,
|
|
1762
|
+
classifyCardFire,
|
|
1763
|
+
buildEnforcementEnvelope,
|
|
1764
|
+
gateReachingEntries,
|
|
1765
|
+
gateSignature,
|
|
1766
|
+
turnContextHash,
|
|
1767
|
+
deriveTurnSignals,
|
|
1768
|
+
consumeReachedGatesForVerdict,
|
|
1769
|
+
readTranscriptTurn,
|
|
1770
|
+
readTranscriptTail,
|
|
1771
|
+
classifyPrecedingUserContentSource: turnText.classifyPrecedingUserContentSource,
|
|
1772
|
+
pruneRetryStore,
|
|
1773
|
+
normalizeRetryEntry,
|
|
1774
|
+
sessionKey,
|
|
1775
|
+
loadRegistry,
|
|
1776
|
+
// mcp-first-path-retry-ceiling-hardcoded-zero (2026-07-28): the bounded-escape
|
|
1777
|
+
// accessors, exported so the SECOND enforcement path (lib/mcp/stop-gate-handler.cjs)
|
|
1778
|
+
// can drive the SAME counters main() drives instead of minting a parallel one.
|
|
1779
|
+
//
|
|
1780
|
+
// WHY THIS EXPORT AND NOT A NEW COUNTER MODULE: these six functions ARE the store.
|
|
1781
|
+
// They read and write ONE local side-file (retryFilePath(), ~/.mindrian/card-fire-
|
|
1782
|
+
// retries.json) whose per-gate entries and `__session__:` entries share a TTL prune
|
|
1783
|
+
// and a single write path. Re-deriving them anywhere else would create a second,
|
|
1784
|
+
// divergent counting mechanism against the same file -- the exact drift the sibling
|
|
1785
|
+
// RCA (card-fire-answered-gate-refires-within-ttl-window) closed for the record
|
|
1786
|
+
// lifecycle and asked not to re-open for the counters. Exporting the existing
|
|
1787
|
+
// functions is purely additive: no call site, signature, or behavior in this file
|
|
1788
|
+
// changes, so the CLI path stays byte-identical.
|
|
1789
|
+
//
|
|
1790
|
+
// The frozen constitutional floor itself (MAX_FORCE_RETRIES / MAX_SESSION_INTERCEPTS)
|
|
1791
|
+
// is still declared in exactly one place -- above, in this file. Consumers read the
|
|
1792
|
+
// COUNTS through these accessors and the CEILINGS through classifyCardFire's verdict;
|
|
1793
|
+
// neither ever re-declares a scalar.
|
|
1794
|
+
readRetryCount,
|
|
1795
|
+
bumpRetryCount,
|
|
1796
|
+
clearRetryCount,
|
|
1797
|
+
readSessionCount,
|
|
1798
|
+
bumpSessionCount,
|
|
1799
|
+
clearSessionCount,
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
if (require.main === module) {
|
|
1803
|
+
try {
|
|
1804
|
+
main();
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
process.stderr.write('[check-card-fire] uncaught: ' + e.message + '\n');
|
|
1807
|
+
silentSuccess();
|
|
1808
|
+
}
|
|
1809
|
+
}
|