@mindrian_os/cli 2.0.0-beta.3 → 2.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +14 -0
- package/CHANGELOG.md +467 -0
- package/agents/analogy-query-fetcher.md +119 -0
- package/agents/brain-query.md +91 -0
- package/agents/competitor-watch-fetcher.md +94 -0
- package/agents/framework-runner.md +258 -0
- package/agents/grading.md +216 -0
- package/agents/grant-reviewer.md +191 -0
- package/agents/investor.md +141 -0
- package/agents/larry-extended.md +215 -0
- package/agents/meeting-perspective-extractor.md +94 -0
- package/agents/opportunity-scanner.md +105 -0
- package/agents/persona-analyst.md +201 -0
- package/agents/research.md +103 -0
- package/agents/reverse-salient-agent.md +42 -0
- package/agents/vault-section-reviewer.md +107 -0
- package/assets/banner-showcase.html +537 -0
- package/assets/fonts/BebasNeue-Regular.ttf +0 -0
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/logo.svg +10 -0
- package/bin/local-chain-recommender.cjs +256 -0
- package/bin/mindrian-brain-mcp-client.cjs +319 -0
- package/bin/mindrian-mcp-server.cjs +445 -0
- package/bin/mindrian-mcp-shim.cjs +100 -0
- package/bin/mindrian-tools.cjs +784 -0
- package/commands/act.md +529 -0
- package/commands/admin.md +439 -0
- package/commands/agentshield.md +49 -0
- package/commands/analyze-needs.md +78 -0
- package/commands/analyze-systems.md +73 -0
- package/commands/analyze-timing.md +98 -0
- package/commands/auto-explore.md +85 -0
- package/commands/beautiful-question.md +76 -0
- package/commands/bono.md +152 -0
- package/commands/brain-derive.md +137 -0
- package/commands/build-knowledge.md +121 -0
- package/commands/build-thesis.md +94 -0
- package/commands/causal.md +269 -0
- package/commands/challenge-assumptions.md +78 -0
- package/commands/compare-ventures.md +117 -0
- package/commands/correct-reference-now.md +88 -0
- package/commands/dashboard.md +144 -0
- package/commands/deck.md +117 -0
- package/commands/deep-grade.md +214 -0
- package/commands/diagnose.md +131 -0
- package/commands/diagnostics.md +195 -0
- package/commands/dial-memory-refresh.md +113 -0
- package/commands/diffusion.md +129 -0
- package/commands/discover.md +221 -0
- package/commands/doctor.md +271 -0
- package/commands/dogfood-flush.md +130 -0
- package/commands/dominant-designs.md +76 -0
- package/commands/eureka.md +315 -0
- package/commands/explain-decision.md +135 -0
- package/commands/explore-domains.md +78 -0
- package/commands/explore-futures.md +67 -0
- package/commands/explore-opportunity.md +172 -0
- package/commands/explore-trends.md +78 -0
- package/commands/export.md +128 -0
- package/commands/feynman-timeline-refresh.md +102 -0
- package/commands/file-meeting.md +1195 -0
- package/commands/find-analogies.md +369 -0
- package/commands/find-bottlenecks.md +108 -0
- package/commands/find-connections.md +111 -0
- package/commands/funding.md +114 -0
- package/commands/futures.md +128 -0
- package/commands/grade-grant.md +297 -0
- package/commands/grade.md +257 -0
- package/commands/graph.md +218 -0
- package/commands/hat-briefing.md +163 -0
- package/commands/heal.md +72 -0
- package/commands/help.md +176 -0
- package/commands/hmi-status.md +75 -0
- package/commands/ignite.md +229 -0
- package/commands/ingest-methodology.md +128 -0
- package/commands/intel-pipeline.md +133 -0
- package/commands/jtbd.md +262 -0
- package/commands/leadership.md +113 -0
- package/commands/lean-canvas.md +74 -0
- package/commands/macro-trends.md +88 -0
- package/commands/map-unknowns.md +76 -0
- package/commands/memory-cortex-reach.md +79 -0
- package/commands/memory.md +213 -0
- package/commands/models.md +200 -0
- package/commands/mos-reason.md +489 -0
- package/commands/mos.md +162 -0
- package/commands/mullins.md +155 -0
- package/commands/mva-brief.md +86 -0
- package/commands/mva-option.md +119 -0
- package/commands/mva-report.md +76 -0
- package/commands/new-project.md +633 -0
- package/commands/new-surface.md +116 -0
- package/commands/onboard.md +489 -0
- package/commands/operator.md +168 -0
- package/commands/opportunities.md +177 -0
- package/commands/organize.md +74 -0
- package/commands/persona.md +276 -0
- package/commands/pipeline.md +167 -0
- package/commands/present.md +125 -0
- package/commands/publish.md +227 -0
- package/commands/pws-brain.md +154 -0
- package/commands/qualify-opportunity.md +113 -0
- package/commands/query.md +70 -0
- package/commands/radar.md +110 -0
- package/commands/reanalyze.md +124 -0
- package/commands/research.md +467 -0
- package/commands/room.md +385 -0
- package/commands/rooms.md +682 -0
- package/commands/root-cause.md +77 -0
- package/commands/rs-experts.md +153 -0
- package/commands/rs-explain.md +134 -0
- package/commands/rs-fetch.md +119 -0
- package/commands/rs-thesis.md +131 -0
- package/commands/scenario-plan.md +80 -0
- package/commands/scheduled-tasks.md +308 -0
- package/commands/score-innovation.md +78 -0
- package/commands/scout.md +379 -0
- package/commands/setup.md +658 -0
- package/commands/show.md +71 -0
- package/commands/skill.md +108 -0
- package/commands/snapshot.md +179 -0
- package/commands/speakers.md +118 -0
- package/commands/splash.md +55 -0
- package/commands/stance.md +91 -0
- package/commands/status.md +109 -0
- package/commands/structure-argument.md +82 -0
- package/commands/suggest-next.md +141 -0
- package/commands/systems-thinking.md +224 -0
- package/commands/think-hats.md +86 -0
- package/commands/trending-to-absurd.md +151 -0
- package/commands/update.md +256 -0
- package/commands/user-needs.md +77 -0
- package/commands/validate.md +77 -0
- package/commands/value-proposition.md +116 -0
- package/commands/vault.md +330 -0
- package/commands/visualize.md +71 -0
- package/commands/whitespace.md +541 -0
- package/commands/wiki.md +96 -0
- package/data/ROOM.md +47 -0
- package/data/brain-census.generated.json +2617 -0
- package/data/brain-orchestration-projection.json +4973 -0
- package/data/brain-packet-schema.json +545 -0
- package/data/brain-surface-contract.json +32 -0
- package/data/capability-ledger.json +391 -0
- package/data/capability-map-registry.json +23 -0
- package/data/command-registry.json +2433 -0
- package/data/connector-coverage-ledger.json +1618 -0
- package/data/connector-out-of-spine-allowlist.json +71 -0
- package/data/connector-registry.json +3637 -0
- package/data/corpus-stats-source.json +11 -0
- package/data/corpus-stats.generated.json +10 -0
- package/data/cross-domain-analogues.json +15 -0
- package/data/deck-aliases.json +6 -0
- package/data/deck-styles.json +85 -0
- package/data/deployment-surfaces.json +69 -0
- package/data/dispatch-framework-map.json +18 -0
- package/data/doctor-modules.json +206 -0
- package/data/dual-graph-health-baseline.json +12 -0
- package/data/eureka-critic-tags.json +32 -0
- package/data/first-reward-surfaces.json +66 -0
- package/data/first-touch-surfaces.json +60 -0
- package/data/flagship-floor-set.json +37 -0
- package/data/framework-names.json +121 -0
- package/data/grant-rubric-fixtures/early-stage-pre-seed.json +18 -0
- package/data/grant-rubric-fixtures/early-stage-round-a.json +18 -0
- package/data/grant-rubric-fixtures/early-stage-seed.json +18 -0
- package/data/grant-rubric-fixtures/hybrid-seed-incentive.json +18 -0
- package/data/grant-rubric-fixtures/rd-fund.json +18 -0
- package/data/grant-rubric-fixtures/regional-innovation-incubators.json +18 -0
- package/data/grant-rubric-fixtures/technology-incubators.json +18 -0
- package/data/grant-rubric-fixtures/tnufa.json +167 -0
- package/data/grant-rubric-schema.json +33 -0
- package/data/harness-fixtures/converged-room/.context/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/.intelligence/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/.snapshots/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/MINTO.md +19 -0
- package/data/harness-fixtures/converged-room/STATE.md +73 -0
- package/data/harness-fixtures/converged-room/USER.md +23 -0
- package/data/harness-fixtures/converged-room/assets/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/business-model/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/business-model/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/competitive-analysis/CONTEXT.md +33 -0
- package/data/harness-fixtures/converged-room/competitive-analysis/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/financial-model/CONTEXT.md +30 -0
- package/data/harness-fixtures/converged-room/financial-model/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/funding/CONTEXT.md +41 -0
- package/data/harness-fixtures/converged-room/funding/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/legal-ip/CONTEXT.md +30 -0
- package/data/harness-fixtures/converged-room/legal-ip/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/market-analysis/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/market-analysis/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/opportunity-bank/CONTEXT.md +39 -0
- package/data/harness-fixtures/converged-room/opportunity-bank/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/problem-definition/CONTEXT.md +32 -0
- package/data/harness-fixtures/converged-room/problem-definition/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/references/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/references/SECTION-SCHEMA.md +213 -0
- package/data/harness-fixtures/converged-room/references/SUB-SCHEMAS.md +91 -0
- package/data/harness-fixtures/converged-room/solution-design/CONTEXT.md +36 -0
- package/data/harness-fixtures/converged-room/solution-design/ROOM.md +36 -0
- package/data/harness-fixtures/converged-room/strategy/CONTEXT.md +35 -0
- package/data/harness-fixtures/converged-room/strategy/ROOM.md +35 -0
- package/data/harness-fixtures/converged-room/team/ROOM.md +13 -0
- package/data/harness-fixtures/converged-room/team-execution/CONTEXT.md +36 -0
- package/data/harness-fixtures/converged-room/team-execution/ROOM.md +35 -0
- package/data/harness-manifest.json +74 -0
- package/data/harness-policies/CONTEXT.md +55 -0
- package/data/harness-policies/_schema.json +42 -0
- package/data/harness-policies/contract-parity-larry.json +63 -0
- package/data/harness-policies/gate-card-fire.json +13 -0
- package/data/harness-policies/gate-graph-derive-health.json +13 -0
- package/data/harness-policies/gate-shape-declaration.json +13 -0
- package/data/harness-policies/gate-tool-honesty.json +13 -0
- package/data/harness-policies/gate-worktree-hygiene.json +13 -0
- package/data/harness-policies/memory-write-policy.json +28 -0
- package/data/harness-policies/registry-drift.json +13 -0
- package/data/harness-policies/release-payload-ceiling.json +13 -0
- package/data/harness-policies/voice-backend-noun-free.json +13 -0
- package/data/harness-policies/voice-fork-as-card.json +13 -0
- package/data/harness-policies/voice-glyph-present.json +13 -0
- package/data/harness-policies/voice-hyphens-only.json +13 -0
- package/data/help-groups.json +236 -0
- package/data/hitl-shape-backfill.json +777 -0
- package/data/hitl-shape-declaration-fixtures/invalid-bad-shape.json +5 -0
- package/data/hitl-shape-declaration-fixtures/invalid-missing-why.json +4 -0
- package/data/hitl-shape-declaration-fixtures/invalid-ranker-contradiction.json +6 -0
- package/data/hitl-shape-declaration-fixtures/invalid-skill-gap.json +3 -0
- package/data/hitl-shape-declaration-fixtures/valid-multi-stage.json +7 -0
- package/data/hitl-shape-declaration-fixtures/valid-single-shape.json +5 -0
- package/data/hitl-shape-declaration-fixtures/valid-skill-exempt.json +5 -0
- package/data/hitl-shape-declaration-schema.json +34 -0
- package/data/hitl-stages-fixtures/bono.json +10 -0
- package/data/hitl-stages-fixtures/pipeline-1.json +9 -0
- package/data/hitl-stages-fixtures/pipeline-2.json +9 -0
- package/data/hitl-stages-fixtures/pipeline-3.json +10 -0
- package/data/hitl-stages-fixtures/research.json +10 -0
- package/data/hitl-stages-fixtures/rs-convergence.json +10 -0
- package/data/hitl-stages-fixtures/rs-family.json +11 -0
- package/data/hitl-stages-fixtures/rs-reverse-salient.json +10 -0
- package/data/hitl-stages-fixtures/trending-to-absurd.json +11 -0
- package/data/hitl-stages-schema.json +26 -0
- package/data/mcp-tool-connectors.json +464 -0
- package/data/mva-agent-prompts.json +31 -0
- package/data/mva-deck-template.html +15 -0
- package/data/mva-heuristic-keywords.json +35 -0
- package/data/orchestration-command-ledger.json +576 -0
- package/data/orchestration-unwired-allowlist.json +6 -0
- package/data/portfolio-ahp-matrix.json +16 -0
- package/data/publish-needs.json +76 -0
- package/data/render-coverage-registry.json +1152 -0
- package/data/research-sources.json +58 -0
- package/data/roadmap-type-chains.json +28 -0
- package/data/room-blueprints.json +156 -0
- package/data/subagent-dispatch-grants.json +150 -0
- package/hooks/hooks.json +493 -0
- package/hooks/run-hook.cmd +64 -0
- package/lib/__init__.py +0 -0
- package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/agents/auto-explore-agent.cjs +1163 -0
- package/lib/agents/mva/brain-classic-traps.cjs +55 -0
- package/lib/agents/mva/brain-cross-domain.cjs +79 -0
- package/lib/agents/mva/brain-similar-ventures.cjs +93 -0
- package/lib/agents/mva/dashboard-graph-neighborhood.cjs +72 -0
- package/lib/agents/mva/index.cjs +42 -0
- package/lib/agents/mva/six-hats-red-black.cjs +137 -0
- package/lib/agents/mva/tavily-funding-scan.cjs +147 -0
- package/lib/agents/mva/test-all-six-agents.cjs +467 -0
- package/lib/agents/reverse-salient-agent.cjs +757 -0
- package/lib/agents/tension-hook-agent.cjs +586 -0
- package/lib/brain/ROOM.md +44 -0
- package/lib/brain/chain-recommender.cjs +784 -0
- package/lib/brain/curation-batch.cjs +519 -0
- package/lib/brain/framework-chain-slice.cjs +252 -0
- package/lib/brain/hypergraph-event-schema.cjs +184 -0
- package/lib/chat/chat-context.js +185 -0
- package/lib/chat/chat-panel.js +721 -0
- package/lib/chat/fabric-chat.cjs +288 -0
- package/lib/chat/generative-tools.js +219 -0
- package/lib/conversation/ROOM.md +39 -0
- package/lib/conversation/classifier-rules.json +38 -0
- package/lib/conversation/classifier.cjs +264 -0
- package/lib/conversation/operator-epistemic-cap.test.cjs +156 -0
- package/lib/conversation/operator.cjs +492 -0
- package/lib/conversation/operator.test.cjs +160 -0
- package/lib/copy/115-spec-strings.cjs +55 -0
- package/lib/core/__init__.py +0 -0
- package/lib/core/__nav-stub.cjs +14 -0
- package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus_exclude.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
- package/lib/core/abstraction-gate.cjs +133 -0
- package/lib/core/act-jtbd-blurb.cjs +231 -0
- package/lib/core/artifact-id.cjs +148 -0
- package/lib/core/asset-ops.cjs +151 -0
- package/lib/core/auto-commit-throttle.cjs +129 -0
- package/lib/core/bearer-token.cjs +199 -0
- package/lib/core/behavioral/observation-schema.cjs +86 -0
- package/lib/core/bono/cell-fanout.cjs +305 -0
- package/lib/core/bono/debate-composition.cjs +495 -0
- package/lib/core/bono/hat-governance.cjs +367 -0
- package/lib/core/bono/persona-research.cjs +288 -0
- package/lib/core/bono/reviewer-governance.cjs +331 -0
- package/lib/core/brain-client.cjs +2365 -0
- package/lib/core/brain-derivation-prompts.cjs +331 -0
- package/lib/core/brain-derivation-queue.cjs +535 -0
- package/lib/core/brain-derivation.cjs +740 -0
- package/lib/core/brain-md-schema.cjs +528 -0
- package/lib/core/brain-md-staleness.cjs +357 -0
- package/lib/core/brain-response-sanitize.cjs +396 -0
- package/lib/core/breakthrough/canary.cjs +134 -0
- package/lib/core/breakthrough/canary.test.cjs +136 -0
- package/lib/core/breakthrough/detectors.cjs +359 -0
- package/lib/core/breakthrough/detectors.test.cjs +333 -0
- package/lib/core/breakthrough/ethics-fence.cjs +129 -0
- package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
- package/lib/core/breakthrough/resurfacing.cjs +150 -0
- package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
- package/lib/core/breakthrough/review-queue.cjs +168 -0
- package/lib/core/breakthrough/review-queue.test.cjs +160 -0
- package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
- package/lib/core/breakthrough/scanner.cjs +435 -0
- package/lib/core/breakthrough/scanner.test.cjs +267 -0
- package/lib/core/breakthrough/schema.cjs +180 -0
- package/lib/core/breakthrough/schema.test.cjs +256 -0
- package/lib/core/breakthrough/scoring.cjs +293 -0
- package/lib/core/breakthrough/scoring.test.cjs +423 -0
- package/lib/core/breakthrough/verb-dispatch.cjs +230 -0
- package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
- package/lib/core/breakthrough/voice-scaffold.cjs +249 -0
- package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
- package/lib/core/bridge-writer.cjs +477 -0
- package/lib/core/cache-prune.cjs +314 -0
- package/lib/core/card-fire-gate.cjs +107 -0
- package/lib/core/card-fire-sidechannel.cjs +518 -0
- package/lib/core/chain-executor.cjs +939 -0
- package/lib/core/chain-retry.cjs +214 -0
- package/lib/core/chain-step-dispatcher.cjs +397 -0
- package/lib/core/chat-context-builder.cjs +253 -0
- package/lib/core/check-plugin-enabled.cjs +145 -0
- package/lib/core/close-loop-writer.cjs +492 -0
- package/lib/core/command-registration-check.cjs +246 -0
- package/lib/core/correlation-label-index.cjs +230 -0
- package/lib/core/correlation.cjs +112 -0
- package/lib/core/coverage-monitor.cjs +183 -0
- package/lib/core/coverage-rollup.cjs +232 -0
- package/lib/core/cross-room-aggregator.cjs +1127 -0
- package/lib/core/cross-room-store.cjs +292 -0
- package/lib/core/cross-room-triggers.cjs +222 -0
- package/lib/core/daily-briefing.cjs +438 -0
- package/lib/core/decision-axes.cjs +178 -0
- package/lib/core/decision-capture.cjs +618 -0
- package/lib/core/deck-design-rules.cjs +211 -0
- package/lib/core/deep-links.cjs +82 -0
- package/lib/core/directive-envelope.cjs +217 -0
- package/lib/core/directive-envelope.test.cjs +225 -0
- package/lib/core/dispatch-optimizer.cjs +354 -0
- package/lib/core/doc-text-extractor.cjs +133 -0
- package/lib/core/doctor/capability-ledger-module.cjs +170 -0
- package/lib/core/doctor/card-fire-health-module.cjs +159 -0
- package/lib/core/doctor/cascade-rooms-active-module.cjs +127 -0
- package/lib/core/doctor/cascade-rooms-module.cjs +184 -0
- package/lib/core/doctor/class-m-brain-smoke.cjs +479 -0
- package/lib/core/doctor/class-m-brain-smoke.test.cjs +448 -0
- package/lib/core/doctor/class-s-eureka-smoke.cjs +380 -0
- package/lib/core/doctor/deployment-surfaces-module.cjs +408 -0
- package/lib/core/doctor/deprecated-usage-module.cjs +155 -0
- package/lib/core/doctor/eureka-fts-health-module.cjs +236 -0
- package/lib/core/doctor/graph-derive-heal-retrofit-module.cjs +148 -0
- package/lib/core/doctor/graph-derive-health-module.cjs +486 -0
- package/lib/core/doctor/install-incomplete-module.cjs +320 -0
- package/lib/core/doctor/install-state-module.cjs +503 -0
- package/lib/core/doctor/mcp-surface-module.cjs +369 -0
- package/lib/core/doctor/mode-select-checkpoint-module.cjs +113 -0
- package/lib/core/doctor/plugin-enabled-state-module.cjs +86 -0
- package/lib/core/doctor/room-graph-density-module.cjs +181 -0
- package/lib/core/doctor/room-md-module.cjs +198 -0
- package/lib/core/doctor/shared.cjs +555 -0
- package/lib/core/doctor/stale-first-touch-copy-module.cjs +83 -0
- package/lib/core/doctor/statusline-visibility-module.cjs +295 -0
- package/lib/core/doctor/ui-compliance-module.cjs +223 -0
- package/lib/core/doctor/umbilical-module.cjs +486 -0
- package/lib/core/doctor/verify-surface-module.cjs +82 -0
- package/lib/core/doctor/voice-style-log-module.cjs +146 -0
- package/lib/core/domain-insight-sweep.cjs +224 -0
- package/lib/core/drift-baseline.cjs +362 -0
- package/lib/core/drift-runtime-reachability.cjs +283 -0
- package/lib/core/dual-path-detector.cjs +84 -0
- package/lib/core/dual-path-detector.test.cjs +334 -0
- package/lib/core/enrichment-queue.cjs +636 -0
- package/lib/core/eureka/ahp-weights.cjs +232 -0
- package/lib/core/eureka/analogy-fitness.cjs +326 -0
- package/lib/core/eureka/compression-meter.cjs +165 -0
- package/lib/core/eureka/embedding-classifier.cjs +226 -0
- package/lib/core/eureka/embedding-spine.cjs +563 -0
- package/lib/core/eureka/entity-classifier.cjs +232 -0
- package/lib/core/eureka/entity-extractor.cjs +356 -0
- package/lib/core/eureka/eureka-enable.cjs +228 -0
- package/lib/core/eureka/eureka-offer.cjs +225 -0
- package/lib/core/eureka/eureka-reach-runner.cjs +430 -0
- package/lib/core/eureka/explore-chain.cjs +834 -0
- package/lib/core/eureka/explored-artifact.cjs +456 -0
- package/lib/core/eureka/fts-index-lifecycle.cjs +265 -0
- package/lib/core/eureka/grade-grant-examine.cjs +544 -0
- package/lib/core/eureka/grade-grant.cjs +596 -0
- package/lib/core/eureka/hybrid-retrieve.cjs +256 -0
- package/lib/core/eureka/lateral-engine-adapter.cjs +118 -0
- package/lib/core/eureka/lexical-overlap.cjs +98 -0
- package/lib/core/eureka/online-pattern-query.cjs +142 -0
- package/lib/core/eureka/opportunity-harvest.cjs +752 -0
- package/lib/core/eureka/opportunity-statement.cjs +519 -0
- package/lib/core/eureka/portfolio-dimensions.cjs +255 -0
- package/lib/core/eureka/qualify-opportunity.cjs +607 -0
- package/lib/core/eureka/reasoning-mode.cjs +481 -0
- package/lib/core/eureka/report-html.cjs +368 -0
- package/lib/core/eureka/research-filing.cjs +547 -0
- package/lib/core/eureka/room-native-substrate.cjs +343 -0
- package/lib/core/eureka/tail-quadrant.cjs +177 -0
- package/lib/core/eureka/tri-modal-index.cjs +564 -0
- package/lib/core/eureka/vector-store.cjs +414 -0
- package/lib/core/eureka-critic.cjs +733 -0
- package/lib/core/eureka-deps-resolver.cjs +179 -0
- package/lib/core/expert-genesis.cjs +437 -0
- package/lib/core/expert-library.cjs +303 -0
- package/lib/core/exports-log.cjs +79 -0
- package/lib/core/feynman/ROOM.md +25 -0
- package/lib/core/feynman/dial-memory-renderer.cjs +279 -0
- package/lib/core/feynman/dial-memory-runner.cjs +173 -0
- package/lib/core/feynman/feynman-seed-writer.cjs +187 -0
- package/lib/core/feynman/timeline-renderer.cjs +197 -0
- package/lib/core/feynman/timeline-runner.cjs +281 -0
- package/lib/core/feynman-minto-invariants.cjs +609 -0
- package/lib/core/findings-wirer.cjs +360 -0
- package/lib/core/first-touch-version-stamper.cjs +113 -0
- package/lib/core/folder-memory-async.cjs +424 -0
- package/lib/core/folder-memory-shared.cjs +890 -0
- package/lib/core/folder-memory.cjs +546 -0
- package/lib/core/framework-chain-composer.cjs +583 -0
- package/lib/core/frontmatter-schemas.cjs +468 -0
- package/lib/core/fusion-router.cjs +540 -0
- package/lib/core/futures/causal-cue.cjs +91 -0
- package/lib/core/futures/orchestrator.cjs +1391 -0
- package/lib/core/futures/subsystem-render.cjs +164 -0
- package/lib/core/gate-relevance.cjs +278 -0
- package/lib/core/git-ops.cjs +141 -0
- package/lib/core/graph-backfill.cjs +562 -0
- package/lib/core/graph-candidate-producer.cjs +235 -0
- package/lib/core/graph-derivation.cjs +546 -0
- package/lib/core/graph-derive-classifier.cjs +352 -0
- package/lib/core/graph-ops.cjs +274 -0
- package/lib/core/graph-refine-loop.cjs +194 -0
- package/lib/core/graph-self-heal.cjs +289 -0
- package/lib/core/greeting-intent-detector.cjs +201 -0
- package/lib/core/grill-engine.cjs +459 -0
- package/lib/core/hat-persistence.cjs +274 -0
- package/lib/core/hsi-engine.cjs +439 -0
- package/lib/core/hsi-lsa.cjs +246 -0
- package/lib/core/hsi-spectral.cjs +554 -0
- package/lib/core/hsi-to-graph.test.cjs +168 -0
- package/lib/core/icm-forest.cjs +703 -0
- package/lib/core/ignite-branch-gate.cjs +66 -0
- package/lib/core/index.cjs +60 -0
- package/lib/core/insight-sensors.cjs +1000 -0
- package/lib/core/install-state.cjs +242 -0
- package/lib/core/integration-registry.cjs +232 -0
- package/lib/core/intel-pipeline.cjs +545 -0
- package/lib/core/intelligence-cascade.cjs +852 -0
- package/lib/core/issue-tree.cjs +269 -0
- package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
- package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
- package/lib/core/lazygraph-ops.cjs +1453 -0
- package/lib/core/lens-engine.cjs +434 -0
- package/lib/core/leverage-scan.cjs +218 -0
- package/lib/core/llm-name-suggester.cjs +194 -0
- package/lib/core/llm-name-suggester.test.cjs +132 -0
- package/lib/core/lru-cache.cjs +139 -0
- package/lib/core/mcp-dep-heal.cjs +475 -0
- package/lib/core/mcp-dep-heal.test.cjs +253 -0
- package/lib/core/mcp-profiles.cjs +185 -0
- package/lib/core/meeting-ops.cjs +54 -0
- package/lib/core/memory/governance-candidate-raiser.cjs +205 -0
- package/lib/core/memory/governance-candidate.cjs +95 -0
- package/lib/core/memory/reconcile-memory-runner.cjs +653 -0
- package/lib/core/memory-ops.cjs +641 -0
- package/lib/core/meter/gate-density-reader.cjs +112 -0
- package/lib/core/meter/transfer-reader.cjs +276 -0
- package/lib/core/meter/two-gauge.cjs +245 -0
- package/lib/core/methodology-ingest.cjs +346 -0
- package/lib/core/metrics/larryreach-loop-health.cjs +142 -0
- package/lib/core/migration-snapshot.cjs +287 -0
- package/lib/core/migration-snapshot.test.cjs +174 -0
- package/lib/core/migrations/ROOM.md +33 -0
- package/lib/core/migrations/phase-109-nodes-provenance.cjs +400 -0
- package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
- package/lib/core/migrations/phase-160-nodes-bitemporal.cjs +168 -0
- package/lib/core/migrations/phase-160-nodes-bitemporal.test.cjs +140 -0
- package/lib/core/migrations/phase-162-section-nodes.cjs +156 -0
- package/lib/core/migrations/phase-222-ranker-weights.cjs +62 -0
- package/lib/core/migrations/phase-224-edge-review-status.cjs +87 -0
- package/lib/core/mindrian-brain-shim.test.cjs +221 -0
- package/lib/core/mode-select-sidechannel.cjs +259 -0
- package/lib/core/model-profiles.cjs +246 -0
- package/lib/core/mullins-scaffold.cjs +258 -0
- package/lib/core/mullins-scaffold.test.cjs +128 -0
- package/lib/core/mva-agent-contract.cjs +170 -0
- package/lib/core/mva-agent-contract.test.cjs +169 -0
- package/lib/core/mva-budget.cjs +75 -0
- package/lib/core/mva-budget.test.cjs +68 -0
- package/lib/core/mva-classifier.cjs +392 -0
- package/lib/core/mva-classifier.test.cjs +248 -0
- package/lib/core/mva-deck-builder.cjs +452 -0
- package/lib/core/mva-deck-builder.test.cjs +287 -0
- package/lib/core/mva-detect.smoke.test.cjs +209 -0
- package/lib/core/mva-dispatcher.cjs +110 -0
- package/lib/core/mva-dispatcher.test.cjs +216 -0
- package/lib/core/mva-option-router.cjs +414 -0
- package/lib/core/mva-option-router.test.cjs +483 -0
- package/lib/core/mva-orchestrator.cjs +365 -0
- package/lib/core/mva-orchestrator.test.cjs +916 -0
- package/lib/core/mva-progressive-renderer.cjs +194 -0
- package/lib/core/mva-progressive-renderer.test.cjs +157 -0
- package/lib/core/mva-rule-linter.cjs +458 -0
- package/lib/core/mva-rule-linter.test.cjs +484 -0
- package/lib/core/mva-state.cjs +159 -0
- package/lib/core/mva-telemetry.cjs +58 -0
- package/lib/core/mva-telemetry.test.cjs +196 -0
- package/lib/core/mva-vercel-deploy.cjs +168 -0
- package/lib/core/mva-vercel-deploy.test.cjs +239 -0
- package/lib/core/nav-dial.cjs +440 -0
- package/lib/core/navigation/ROOM.md +15 -0
- package/lib/core/navigation/abstraction-claim.cjs +185 -0
- package/lib/core/navigation/calibration-gate.cjs +284 -0
- package/lib/core/navigation/calibration-log.cjs +141 -0
- package/lib/core/navigation/confirm-node.cjs +73 -0
- package/lib/core/navigation/dashboard-helpers.cjs +145 -0
- package/lib/core/navigation/edges.cjs +1150 -0
- package/lib/core/navigation/evidence-claim.cjs +186 -0
- package/lib/core/navigation/explanation.cjs +48 -0
- package/lib/core/navigation/file-evidence-readback.cjs +242 -0
- package/lib/core/navigation/focus.cjs +155 -0
- package/lib/core/navigation/get-domains-for-trends.cjs +277 -0
- package/lib/core/navigation/governance.cjs +101 -0
- package/lib/core/navigation/grant-rubric.cjs +218 -0
- package/lib/core/navigation/graph-export.cjs +325 -0
- package/lib/core/navigation/ingestion.cjs +109 -0
- package/lib/core/navigation/insights-stale.test.cjs +127 -0
- package/lib/core/navigation/insights.cjs +574 -0
- package/lib/core/navigation/lens-nodes.cjs +315 -0
- package/lib/core/navigation/memory-artifacts.cjs +437 -0
- package/lib/core/navigation/memory-cortex-packet.cjs +274 -0
- package/lib/core/navigation/memory-events.cjs +811 -0
- package/lib/core/navigation/neighborhood.cjs +79 -0
- package/lib/core/navigation/packet.cjs +420 -0
- package/lib/core/navigation/planning-artifacts.cjs +219 -0
- package/lib/core/navigation/projections.cjs +295 -0
- package/lib/core/navigation/ranker-weights.cjs +133 -0
- package/lib/core/navigation/reasoning-write.cjs +211 -0
- package/lib/core/navigation/reconcile-guard.cjs +98 -0
- package/lib/core/navigation/reified-claim.cjs +287 -0
- package/lib/core/navigation/research-preflight.cjs +156 -0
- package/lib/core/navigation/room-birth.cjs +1213 -0
- package/lib/core/navigation/room-context.cjs +380 -0
- package/lib/core/navigation/room-home.cjs +142 -0
- package/lib/core/navigation/spine-events.cjs +548 -0
- package/lib/core/navigation/synthetic-expert.cjs +177 -0
- package/lib/core/navigation/transitions.cjs +253 -0
- package/lib/core/navigation/typed-claim.cjs +208 -0
- package/lib/core/navigation/typed-domain.cjs +249 -0
- package/lib/core/navigation/typed-entity.cjs +236 -0
- package/lib/core/navigation/typed-frame.cjs +257 -0
- package/lib/core/navigation/typed-open-question.cjs +130 -0
- package/lib/core/navigation/typed-opportunity.cjs +418 -0
- package/lib/core/navigation-engine-offer.cjs +776 -0
- package/lib/core/navigation-engine-shared.cjs +256 -0
- package/lib/core/navigation-engine.cjs +1696 -0
- package/lib/core/navigation.cjs +686 -0
- package/lib/core/nl-graph-queries.cjs +164 -0
- package/lib/core/node-insert-epistemic.test.cjs +212 -0
- package/lib/core/node-insert-overrides.test.cjs +273 -0
- package/lib/core/node-insert.cjs +274 -0
- package/lib/core/npm-cli-resolve.cjs +151 -0
- package/lib/core/npm-cli-resolve.test.cjs +153 -0
- package/lib/core/npm-install-lock.cjs +326 -0
- package/lib/core/npm-install-lock.test.cjs +379 -0
- package/lib/core/numeric/sklearn-stopwords-v1.json +325 -0
- package/lib/core/numeric/svd.cjs +377 -0
- package/lib/core/numeric/tfidf.cjs +241 -0
- package/lib/core/offer-presenter.cjs +406 -0
- package/lib/core/opportunity-extractor.cjs +183 -0
- package/lib/core/opportunity-ops.cjs +1423 -0
- package/lib/core/orchestration-candidate-lift.cjs +284 -0
- package/lib/core/part8-egress-guard.cjs +536 -0
- package/lib/core/part8-egress-guard.test.cjs +194 -0
- package/lib/core/part8-egress-ontology.cjs +174 -0
- package/lib/core/part8-egress-ontology.test.cjs +133 -0
- package/lib/core/persona-ops.cjs +692 -0
- package/lib/core/persona-override.cjs +327 -0
- package/lib/core/persona-taxonomy.cjs +328 -0
- package/lib/core/pinecone-inference.cjs +176 -0
- package/lib/core/pitch-feedback-schemas.cjs +303 -0
- package/lib/core/planning/artifact-brain-packet.cjs +212 -0
- package/lib/core/planning/reconcile-runner.cjs +347 -0
- package/lib/core/platform-gates.cjs +120 -0
- package/lib/core/platform.cjs +257 -0
- package/lib/core/proactive-intelligence.cjs +528 -0
- package/lib/core/problem-type-router.cjs +315 -0
- package/lib/core/publish-needs-default-lane.cjs +110 -0
- package/lib/core/ralph-loop-gate.cjs +48 -0
- package/lib/core/reader/ab-harness.cjs +177 -0
- package/lib/core/reader/decide-projection-reader.cjs +358 -0
- package/lib/core/reasoning-ops.cjs +639 -0
- package/lib/core/recipe-maps.cjs +442 -0
- package/lib/core/recovery/case-file.cjs +358 -0
- package/lib/core/recovery/controller.cjs +1008 -0
- package/lib/core/recovery/dispatcher.cjs +533 -0
- package/lib/core/recovery/result-semantics.cjs +488 -0
- package/lib/core/recovery/stage-envelope.cjs +338 -0
- package/lib/core/refusal-messaging.cjs +569 -0
- package/lib/core/refusal-messaging.test.cjs +220 -0
- package/lib/core/repo-version.cjs +134 -0
- package/lib/core/research-cache.cjs +165 -0
- package/lib/core/research-cache.test.cjs +137 -0
- package/lib/core/research-context-extractor.cjs +283 -0
- package/lib/core/research-corpus.cjs +814 -0
- package/lib/core/research-corpus.test.cjs +280 -0
- package/lib/core/research-filing-selector.cjs +189 -0
- package/lib/core/resolve-active-room.cjs +602 -0
- package/lib/core/resolve-brain-key.cjs +273 -0
- package/lib/core/resolve-umbilical-target.cjs +288 -0
- package/lib/core/resolve-vercel-key.cjs +107 -0
- package/lib/core/resolve-vercel-key.test.cjs +137 -0
- package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
- package/lib/core/room-auto-create.cjs +318 -0
- package/lib/core/room-auto-create.test.cjs +198 -0
- package/lib/core/room-chooser.cjs +244 -0
- package/lib/core/room-classifier-strict-mode.cjs +235 -0
- package/lib/core/room-db.cjs +348 -0
- package/lib/core/room-discard-cascade.cjs +238 -0
- package/lib/core/room-discard-cascade.test.cjs +135 -0
- package/lib/core/room-name-validator.cjs +132 -0
- package/lib/core/room-name-validator.test.cjs +156 -0
- package/lib/core/room-naming-selector.cjs +357 -0
- package/lib/core/room-naming-selector.test.cjs +277 -0
- package/lib/core/room-open.cjs +259 -0
- package/lib/core/room-ops-async.cjs +92 -0
- package/lib/core/room-ops-shared.cjs +64 -0
- package/lib/core/room-ops-sync.cjs +70 -0
- package/lib/core/room-ops.cjs +32 -0
- package/lib/core/room-receipt-emit.cjs +63 -0
- package/lib/core/room-root.cjs +93 -0
- package/lib/core/room-skeleton-scaffold.cjs +669 -0
- package/lib/core/room-skeleton-scaffold.test.cjs +296 -0
- package/lib/core/room-type-detector.cjs +386 -0
- package/lib/core/rs-backend-dispatch.cjs +64 -0
- package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
- package/lib/core/rs-brain-substrate.cjs +570 -0
- package/lib/core/rs-breakthrough-scorer.cjs +255 -0
- package/lib/core/rs-canon-violations.cjs +82 -0
- package/lib/core/rs-chain-feeder.cjs +381 -0
- package/lib/core/rs-commercial-assessor.cjs +280 -0
- package/lib/core/rs-corpus-quality-gate.cjs +62 -0
- package/lib/core/rs-differential-scorer.cjs +629 -0
- package/lib/core/rs-domain-analyzer.cjs +385 -0
- package/lib/core/rs-egress-prompts.cjs +113 -0
- package/lib/core/rs-egress-telemetry.cjs +225 -0
- package/lib/core/rs-egress-violations.cjs +53 -0
- package/lib/core/rs-engine.cjs +597 -0
- package/lib/core/rs-expert-brain-projection.cjs +280 -0
- package/lib/core/rs-expert-mapper.cjs +467 -0
- package/lib/core/rs-fetcher-academic.cjs +697 -0
- package/lib/core/rs-fetcher-experts.cjs +314 -0
- package/lib/core/rs-fetcher-industry.cjs +731 -0
- package/lib/core/rs-fetcher-patents.cjs +564 -0
- package/lib/core/rs-innovation-classifier.cjs +194 -0
- package/lib/core/rs-math.cjs +458 -0
- package/lib/core/rs-mind-map.cjs +656 -0
- package/lib/core/rs-neo4j-writer.cjs +388 -0
- package/lib/core/rs-nl-to-query.cjs +435 -0
- package/lib/core/rs-pinecone-bridge.cjs +344 -0
- package/lib/core/rs-preprocessor.cjs +350 -0
- package/lib/core/rs-query-matrix.cjs +316 -0
- package/lib/core/rs-query-to-text.cjs +438 -0
- package/lib/core/rs-sqlite-mirror.cjs +495 -0
- package/lib/core/rs-thesis-generator.cjs +188 -0
- package/lib/core/rs_cache.py +635 -0
- package/lib/core/rs_corpus.py +562 -0
- package/lib/core/rs_corpus_exclude.py +62 -0
- package/lib/core/rs_hybrid.py +589 -0
- package/lib/core/rs_math.py +287 -0
- package/lib/core/rs_rooms.py +196 -0
- package/lib/core/salient-governance.cjs +315 -0
- package/lib/core/scheduled-scanner.cjs +658 -0
- package/lib/core/scratchpad-ops.cjs +316 -0
- package/lib/core/seam-liveness.cjs +191 -0
- package/lib/core/seam-liveness.test.cjs +177 -0
- package/lib/core/section-8-trace-schema.cjs +138 -0
- package/lib/core/section-registry.cjs +167 -0
- package/lib/core/security/agentshield-adapters.test.cjs +104 -0
- package/lib/core/security/agentshield-run.cjs +343 -0
- package/lib/core/security/agentshield-scanner.cjs +314 -0
- package/lib/core/security/agentshield-scanner.test.cjs +259 -0
- package/lib/core/sensors/hat-scoping-table.cjs +103 -0
- package/lib/core/sensors/sensor-circularity.cjs +278 -0
- package/lib/core/sensors/sensor-content-relevance.cjs +407 -0
- package/lib/core/sensors/sensor-diffusion-adoption.cjs +230 -0
- package/lib/core/sensors/sensor-eureka.cjs +208 -0
- package/lib/core/sensors/sensor-expert-skill.cjs +293 -0
- package/lib/core/sensors/sensor-external-fact.cjs +184 -0
- package/lib/core/sensors/sensor-gate-approach.cjs +104 -0
- package/lib/core/sensors/sensor-jtbd-reweight.cjs +141 -0
- package/lib/core/sensors/sensor-lagging-component.cjs +115 -0
- package/lib/core/sensors/sensor-memory-cortex.cjs +111 -0
- package/lib/core/sensors/sensor-methodology-decision.cjs +131 -0
- package/lib/core/sensors/sensor-opportunity-harvest.cjs +195 -0
- package/lib/core/sensors/sensor-perspective-lock.cjs +174 -0
- package/lib/core/sensors/sensor-priority.cjs +183 -0
- package/lib/core/sensors/sensor-recency.cjs +151 -0
- package/lib/core/sensors/sensor-roadmap-type.cjs +421 -0
- package/lib/core/sensors/sensor-room-pick.cjs +238 -0
- package/lib/core/sensors/sensor-show-share.cjs +215 -0
- package/lib/core/sensors/sensor-temporal-blindness.cjs +123 -0
- package/lib/core/sensors/sensor-types.cjs +294 -0
- package/lib/core/sensors/sensor-url-ingest.cjs +234 -0
- package/lib/core/session-binding.cjs +208 -0
- package/lib/core/session-presence.cjs +404 -0
- package/lib/core/session-register.cjs +138 -0
- package/lib/core/session-state.cjs +144 -0
- package/lib/core/shallow-doc-parser.cjs +353 -0
- package/lib/core/shallow-doc-parser.test.cjs +226 -0
- package/lib/core/skill-activation-router.cjs +284 -0
- package/lib/core/skillopt-schemas.cjs +509 -0
- package/lib/core/stale-copy-scanner.cjs +190 -0
- package/lib/core/stance-state.cjs +153 -0
- package/lib/core/state-aware-router.cjs +78 -0
- package/lib/core/state-ops.cjs +80 -0
- package/lib/core/state-version.cjs +350 -0
- package/lib/core/statusline-cache.cjs +266 -0
- package/lib/core/statusline-liveness-gate.cjs +105 -0
- package/lib/core/surface-fence.cjs +127 -0
- package/lib/core/synthesizers/comparison-matrix.cjs +82 -0
- package/lib/core/synthesizers/convergence-map.cjs +107 -0
- package/lib/core/synthesizers/domain-hierarchy.cjs +138 -0
- package/lib/core/synthesizers/source-comparison.cjs +56 -0
- package/lib/core/synthesizers/tension-map.cjs +119 -0
- package/lib/core/synthetic-expert-behavior-gate.cjs +99 -0
- package/lib/core/synthetic-expert-construction-gate.cjs +170 -0
- package/lib/core/telemetry/schema.cjs +168 -0
- package/lib/core/telemetry/schema.test.cjs +124 -0
- package/lib/core/telemetry/validator.cjs +200 -0
- package/lib/core/telemetry/validator.test.cjs +188 -0
- package/lib/core/telemetry/writer.cjs +141 -0
- package/lib/core/telemetry/writer.test.cjs +331 -0
- package/lib/core/temporal/date-sync-gate.cjs +256 -0
- package/lib/core/temporal/date-sync-gate.test.cjs +191 -0
- package/lib/core/temporal/dual-stamp.cjs +172 -0
- package/lib/core/temporal/dual-stamp.test.cjs +172 -0
- package/lib/core/temporal/point-in-time.cjs +76 -0
- package/lib/core/temporal/point-in-time.test.cjs +169 -0
- package/lib/core/temporal/recency-decay.cjs +119 -0
- package/lib/core/temporal/recency-decay.test.cjs +140 -0
- package/lib/core/temporal/reference-now.cjs +281 -0
- package/lib/core/temporal/reference-now.test.cjs +225 -0
- package/lib/core/temporal/resolve-relative-time.cjs +116 -0
- package/lib/core/temporal/resolve-relative-time.test.cjs +138 -0
- package/lib/core/temporal/supersession.cjs +250 -0
- package/lib/core/temporal/supersession.test.cjs +103 -0
- package/lib/core/terminal-capability.cjs +88 -0
- package/lib/core/token-estimator.cjs +348 -0
- package/lib/core/tone-filter.cjs +147 -0
- package/lib/core/trending-to-absurd/orchestrator.cjs +341 -0
- package/lib/core/trending-to-absurd/stage7-roadmap.cjs +317 -0
- package/lib/core/trending-to-absurd/variance.cjs +169 -0
- package/lib/core/unknowns/bandit.cjs +332 -0
- package/lib/core/unknowns/corpus-adapter.cjs +327 -0
- package/lib/core/unknowns/dsp.cjs +356 -0
- package/lib/core/unknowns/edge-writer.cjs +211 -0
- package/lib/core/unknowns/iface.cjs +212 -0
- package/lib/core/unknowns/orchestrator.cjs +375 -0
- package/lib/core/unknowns/pattern-miner.cjs +214 -0
- package/lib/core/unknowns/rumsfeld-matrix.cjs +155 -0
- package/lib/core/unknowns/verdict.cjs +254 -0
- package/lib/core/update-path.cjs +57 -0
- package/lib/core/url-ingest.cjs +568 -0
- package/lib/core/user-archetype.cjs +284 -0
- package/lib/core/user-md-ops.cjs +578 -0
- package/lib/core/venture-shape-nudge.cjs +178 -0
- package/lib/core/venture-shape-nudge.test.cjs +266 -0
- package/lib/core/verb-reach-affinity.cjs +299 -0
- package/lib/core/visual-ops.cjs +692 -0
- package/lib/core/voice-transition-detector.cjs +125 -0
- package/lib/core/watched-sources.cjs +237 -0
- package/lib/core/write-lock.cjs +149 -0
- package/lib/graph/canvas-graph.js +467 -0
- package/lib/graph/constellation-config.cjs +299 -0
- package/lib/graph/graph-detail-panel.js +165 -0
- package/lib/hmi/ROOM.md +47 -0
- package/lib/hmi/across-session-memory.cjs +669 -0
- package/lib/hmi/agentshield-drift-gate.cjs +142 -0
- package/lib/hmi/brain-review-packet.cjs +221 -0
- package/lib/hmi/cortex-reach-adapter.cjs +271 -0
- package/lib/hmi/cross-room-memory.cjs +618 -0
- package/lib/hmi/decoy-tier.cjs +395 -0
- package/lib/hmi/depth-state.cjs +164 -0
- package/lib/hmi/dial-label-composer.cjs +410 -0
- package/lib/hmi/dial-presenter.cjs +508 -0
- package/lib/hmi/dial-reach-orchestrator.cjs +377 -0
- package/lib/hmi/dial-selector.cjs +239 -0
- package/lib/hmi/f1-pick-capture-cli.cjs +131 -0
- package/lib/hmi/f3-depth-capture-cli.cjs +77 -0
- package/lib/hmi/f4-scope-capture-cli.cjs +86 -0
- package/lib/hmi/f8-action-capture-cli.cjs +145 -0
- package/lib/hmi/f8-capture.test.cjs +77 -0
- package/lib/hmi/f9-ordered-capture-cli.cjs +138 -0
- package/lib/hmi/harvest-scope-state.cjs +202 -0
- package/lib/hmi/hats-persona-cache.cjs +142 -0
- package/lib/hmi/jtbd-classifier.cjs +219 -0
- package/lib/hmi/jtbd-state.cjs +263 -0
- package/lib/hmi/jtbd-taxonomy.json +402 -0
- package/lib/hmi/mindrian-ascii-logo.cjs +96 -0
- package/lib/hmi/part8-egress-gate.cjs +149 -0
- package/lib/hmi/reach-component-map.json +47 -0
- package/lib/hmi/reach-relevance-gate.cjs +147 -0
- package/lib/hmi/selector-dispatcher.cjs +1201 -0
- package/lib/hmi/selector-dispatcher.test.cjs +141 -0
- package/lib/hmi/selector-telemetry.cjs +269 -0
- package/lib/hmi/shape-f0-renderer.cjs +145 -0
- package/lib/hmi/shape-f1-fallback.cjs +80 -0
- package/lib/hmi/shape-f1-renderer.cjs +219 -0
- package/lib/hmi/shape-f2-renderer.cjs +132 -0
- package/lib/hmi/shape-f3-parity.test.cjs +64 -0
- package/lib/hmi/shape-f3-renderer.cjs +66 -0
- package/lib/hmi/shape-f4-parity.test.cjs +72 -0
- package/lib/hmi/shape-f4-renderer.cjs +72 -0
- package/lib/hmi/shape-f5-renderer.cjs +155 -0
- package/lib/hmi/shape-f6-plan-review-renderer.cjs +324 -0
- package/lib/hmi/shape-f6-renderer.cjs +144 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.cjs +234 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
- package/lib/hmi/shape-f8-renderer.cjs +150 -0
- package/lib/hmi/shape-f8-renderer.test.cjs +70 -0
- package/lib/hmi/shape-f9-renderer.cjs +187 -0
- package/lib/hmi/shape-f9-renderer.test.cjs +80 -0
- package/lib/hmi/shape-g-renderer.cjs +219 -0
- package/lib/hmi/shape-h-renderer.cjs +222 -0
- package/lib/hmi/tier-check.cjs +63 -0
- package/lib/hmi/turn-text.cjs +268 -0
- package/lib/hmi/voice-color-mark.cjs +318 -0
- package/lib/hmi/voice-style-log.cjs +276 -0
- package/lib/import/PRECONDITIONS.md +41 -0
- package/lib/import/branding.cjs +210 -0
- package/lib/import/branding.test.cjs +235 -0
- package/lib/import/classifications-sync.cjs +104 -0
- package/lib/import/classifications-sync.test.cjs +129 -0
- package/lib/import/enricher.cjs +296 -0
- package/lib/import/enricher.test.cjs +273 -0
- package/lib/import/integration.test.cjs +376 -0
- package/lib/import/manifest.cjs +129 -0
- package/lib/import/manifest.schema.json +185 -0
- package/lib/import/manifest.test.cjs +123 -0
- package/lib/import/meeting-detector.cjs +92 -0
- package/lib/import/meeting-detector.test.cjs +100 -0
- package/lib/import/person-detector.cjs +229 -0
- package/lib/import/person-detector.test.cjs +149 -0
- package/lib/import/report.cjs +186 -0
- package/lib/import/report.test.cjs +186 -0
- package/lib/import/room-md-scaffolder.cjs +49 -0
- package/lib/import/router.cjs +224 -0
- package/lib/import/router.test.cjs +356 -0
- package/lib/import/run-all-tests.cjs +36 -0
- package/lib/import/smoke-test.cjs +213 -0
- package/lib/import/smoke-test.test.cjs +148 -0
- package/lib/import/undo.test.cjs +199 -0
- package/lib/import/vault-scanner.cjs +105 -0
- package/lib/import/vault-scanner.test.cjs +67 -0
- package/lib/lens-engine/correlation-resolver.cjs +174 -0
- package/lib/lens-engine/source-lens-driver.cjs +736 -0
- package/lib/mcp/adapter-client.cjs +103 -0
- package/lib/mcp/app-html/dashboard.html +316 -0
- package/lib/mcp/app-html/graph.html +428 -0
- package/lib/mcp/app-html/mindrian-platform.html +1841 -0
- package/lib/mcp/app-html/wiki.html +383 -0
- package/lib/mcp/app-views.cjs +322 -0
- package/lib/mcp/brain-composition-census.cjs +184 -0
- package/lib/mcp/brain-router.cjs +530 -0
- package/lib/mcp/capability-registry.cjs +62 -0
- package/lib/mcp/contract-version.cjs +59 -0
- package/lib/mcp/daemon-lifecycle.cjs +324 -0
- package/lib/mcp/gate-dedup.cjs +120 -0
- package/lib/mcp/gate-ledger.cjs +145 -0
- package/lib/mcp/gate-render.cjs +483 -0
- package/lib/mcp/hook-adapter-audit.cjs +199 -0
- package/lib/mcp/larry-context.cjs +42 -0
- package/lib/mcp/mcp-first-flag.cjs +147 -0
- package/lib/mcp/no-instructions.test.cjs +270 -0
- package/lib/mcp/pipeline-state.cjs +424 -0
- package/lib/mcp/prompts.cjs +421 -0
- package/lib/mcp/register-core-tools.cjs +78 -0
- package/lib/mcp/resources.cjs +359 -0
- package/lib/mcp/runtime-instructions.cjs +38 -0
- package/lib/mcp/session-catchup.cjs +349 -0
- package/lib/mcp/session-registry.cjs +113 -0
- package/lib/mcp/session-room.cjs +124 -0
- package/lib/mcp/sse-event-bus.cjs +98 -0
- package/lib/mcp/stop-gate-handler.cjs +589 -0
- package/lib/mcp/surface-detect.cjs +186 -0
- package/lib/mcp/tool-router.cjs +2113 -0
- package/lib/mcp/tools/chain.cjs +674 -0
- package/lib/mcp/tools/claim.cjs +180 -0
- package/lib/mcp/tools/context.cjs +92 -0
- package/lib/mcp/tools/dual-path.cjs +79 -0
- package/lib/mcp/tools/gate.cjs +392 -0
- package/lib/mcp/tools/graph-reason.cjs +130 -0
- package/lib/mcp/tools/graph.cjs +321 -0
- package/lib/mcp/tools/identity.cjs +174 -0
- package/lib/mcp/tools/room.cjs +449 -0
- package/lib/mcp/tools/sensors.cjs +450 -0
- package/lib/mcp/tools/status.cjs +183 -0
- package/lib/mcp/tools/stop-gate.cjs +103 -0
- package/lib/mcp/tools/views.cjs +382 -0
- package/lib/mcp/tree-watcher.cjs +199 -0
- package/lib/memory/.mindrian/auto-explore-mat-test-001.json +11 -0
- package/lib/memory/aaak-compress.cjs +403 -0
- package/lib/memory/aaak-compress.test.cjs +288 -0
- package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
- package/lib/memory/bearer-token.test.cjs +315 -0
- package/lib/memory/body-shape-coverage.test.cjs +268 -0
- package/lib/memory/brain-cache-lru.test.cjs +259 -0
- package/lib/memory/brain-client-query-shape.test.cjs +160 -0
- package/lib/memory/brain-cypher-chain-slice.test.cjs +368 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
- package/lib/memory/brain-derivation-queue.test.cjs +553 -0
- package/lib/memory/brain-derivation.test.cjs +639 -0
- package/lib/memory/brain-derive-command.test.cjs +534 -0
- package/lib/memory/brain-derive-curation.test.cjs +243 -0
- package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
- package/lib/memory/brain-md-schema.test.cjs +467 -0
- package/lib/memory/brain-md-staleness.test.cjs +525 -0
- package/lib/memory/brain-server-resolution.test.cjs +314 -0
- package/lib/memory/brain-suggestion-template.test.cjs +200 -0
- package/lib/memory/chain-recommender-canonical.test.cjs +208 -0
- package/lib/memory/chain-recommender.test.cjs +233 -0
- package/lib/memory/chat-context.test.cjs +128 -0
- package/lib/memory/command-registry.test.cjs +220 -0
- package/lib/memory/connector-registry.test.cjs +94 -0
- package/lib/memory/correlation-label-index.test.cjs +119 -0
- package/lib/memory/correlation.test.cjs +253 -0
- package/lib/memory/cross-room-aggregator.test.cjs +909 -0
- package/lib/memory/curation-132-03-dedup-held-rename.test.cjs +251 -0
- package/lib/memory/curation-132-05-pseudonymize.test.cjs +205 -0
- package/lib/memory/curation-batch.test.cjs +213 -0
- package/lib/memory/dashboard-server.test.cjs +256 -0
- package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
- package/lib/memory/decision-capture.test.cjs +632 -0
- package/lib/memory/decision-capture.worker.cjs +70 -0
- package/lib/memory/doctor-deprecation-surface.test.cjs +188 -0
- package/lib/memory/dual-graph-health.test.cjs +485 -0
- package/lib/memory/explain-decision-command.test.cjs +521 -0
- package/lib/memory/explain-decision-footer.test.cjs +316 -0
- package/lib/memory/explored-materials-store.cjs +392 -0
- package/lib/memory/f-selector-ranker.test.cjs +593 -0
- package/lib/memory/feynman-minto-guardian.test.cjs +863 -0
- package/lib/memory/feynman-minto-invariants.test.cjs +554 -0
- package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
- package/lib/memory/feynman-prompts.cjs +151 -0
- package/lib/memory/feynman-prompts.test.cjs +96 -0
- package/lib/memory/first-touch-version.test.cjs +198 -0
- package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
- package/lib/memory/folder-memory.test.cjs +503 -0
- package/lib/memory/framework-chain-composer.test.cjs +549 -0
- package/lib/memory/frontmatter-schema-validator.test.cjs +344 -0
- package/lib/memory/guardian-onstop-reaches-user.test.cjs +396 -0
- package/lib/memory/heal-command.test.cjs +604 -0
- package/lib/memory/help-coverage.test.cjs +108 -0
- package/lib/memory/help-renderer.test.cjs +145 -0
- package/lib/memory/huji-run-one-async-parity.test.cjs +242 -0
- package/lib/memory/hypergraph-event-schema.test.cjs +116 -0
- package/lib/memory/index-artifact-transaction.test.cjs +333 -0
- package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
- package/lib/memory/local-chain-recommender.test.cjs +172 -0
- package/lib/memory/mcp-input-validation.test.cjs +240 -0
- package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
- package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
- package/lib/memory/minto-debounce-consumer-census.test.cjs +567 -0
- package/lib/memory/minto-debouncer.test.cjs +407 -0
- package/lib/memory/minto-debouncer.worker.cjs +46 -0
- package/lib/memory/minto-migration-v88.test.cjs +265 -0
- package/lib/memory/minto-schema-v88.test.cjs +390 -0
- package/lib/memory/mos-status-renderer.test.cjs +631 -0
- package/lib/memory/narrative-schema.cjs +376 -0
- package/lib/memory/narrative-schema.test.cjs +209 -0
- package/lib/memory/nav-dial.test.cjs +414 -0
- package/lib/memory/navigation-engine-core.test.cjs +712 -0
- package/lib/memory/navigation-engine-offer.test.cjs +404 -0
- package/lib/memory/navigation-hook-resolver.test.cjs +177 -0
- package/lib/memory/navigation-invariants.test.cjs +483 -0
- package/lib/memory/navigation-projections.test.cjs +241 -0
- package/lib/memory/navigation-write-edge.test.cjs +206 -0
- package/lib/memory/offer-closer.test.cjs +258 -0
- package/lib/memory/offer-presenter.test.cjs +554 -0
- package/lib/memory/on-stop-snapshot.test.cjs +404 -0
- package/lib/memory/orchestration-projection.test.cjs +706 -0
- package/lib/memory/packet-chain-hint.test.cjs +407 -0
- package/lib/memory/packet-schema-validation.test.cjs +317 -0
- package/lib/memory/palette-consistency.test.cjs +127 -0
- package/lib/memory/pending-tension-store.cjs +453 -0
- package/lib/memory/per-command-jtbd-derivation.test.cjs +130 -0
- package/lib/memory/per-command-teaching.test.cjs +110 -0
- package/lib/memory/post-compact-reinjection.test.cjs +854 -0
- package/lib/memory/post-write-triple.test.cjs +317 -0
- package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
- package/lib/memory/precommit-real-commit.test.cjs +301 -0
- package/lib/memory/problem-type-router.test.cjs +656 -0
- package/lib/memory/query-efficiency-telemetry.test.cjs +612 -0
- package/lib/memory/recompile-room-references.test.cjs +392 -0
- package/lib/memory/recompile-room-references.worker.cjs +42 -0
- package/lib/memory/record-decision-dual-write.test.cjs +454 -0
- package/lib/memory/render-v2-disposition.test.cjs +199 -0
- package/lib/memory/room-classifier-strict-mode.test.cjs +430 -0
- package/lib/memory/room-minto-hook.test.cjs +398 -0
- package/lib/memory/rs-discovery-engine.test.cjs +323 -0
- package/lib/memory/run-feynman-tests.cjs +2101 -0
- package/lib/memory/security-trifecta.test.cjs +329 -0
- package/lib/memory/selector-alias-map.test.cjs +172 -0
- package/lib/memory/selector-decisions.test.cjs +417 -0
- package/lib/memory/selector-miss.test.cjs +290 -0
- package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
- package/lib/memory/session-start-triple-injection.test.cjs +514 -0
- package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
- package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
- package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
- package/lib/memory/skill-activation-router.test.cjs +465 -0
- package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
- package/lib/memory/soft-alias.test.cjs +144 -0
- package/lib/memory/spine-events-correlation.test.cjs +198 -0
- package/lib/memory/stale-copy-scanner.test.cjs +294 -0
- package/lib/memory/stamp-artifact-write.test.cjs +304 -0
- package/lib/memory/state-aware-router.test.cjs +90 -0
- package/lib/memory/statusline-active-room-write.test.cjs +245 -0
- package/lib/memory/statusline-active-room.test.cjs +315 -0
- package/lib/memory/statusline-minto-segment.test.cjs +292 -0
- package/lib/memory/statusline-two-row.test.cjs +338 -0
- package/lib/memory/suggest-next-workflow.test.cjs +176 -0
- package/lib/memory/sync-async-entry-points.test.cjs +204 -0
- package/lib/memory/terminal-capability.test.cjs +155 -0
- package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
- package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
- package/lib/memory/test-rs-brain-substrate.cjs +636 -0
- package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
- package/lib/memory/test-rs-canon-violations.cjs +218 -0
- package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
- package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
- package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
- package/lib/memory/test-rs-differential-scorer.cjs +480 -0
- package/lib/memory/test-rs-discovery-engine.cjs +648 -0
- package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
- package/lib/memory/test-rs-egress-primitives.cjs +420 -0
- package/lib/memory/test-rs-expert-mapper.cjs +547 -0
- package/lib/memory/test-rs-explain-command.cjs +443 -0
- package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
- package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
- package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
- package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
- package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
- package/lib/memory/test-rs-mind-map.cjs +646 -0
- package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
- package/lib/memory/test-rs-nl-to-query.cjs +449 -0
- package/lib/memory/test-rs-pinecone-bridge.cjs +282 -0
- package/lib/memory/test-rs-preprocessor.cjs +433 -0
- package/lib/memory/test-rs-query-matrix.cjs +391 -0
- package/lib/memory/test-rs-query-to-text.cjs +551 -0
- package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
- package/lib/memory/test-rs-thesis-generator.cjs +360 -0
- package/lib/memory/triple-context-formatter.cjs +473 -0
- package/lib/memory/triple-context-formatter.test.cjs +442 -0
- package/lib/memory/user-md-persona.test.cjs +565 -0
- package/lib/memory/userpromptsubmit-integration.test.cjs +787 -0
- package/lib/memory/validators/README.md +157 -0
- package/lib/memory/validators/brain-md-invariants.cjs +475 -0
- package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
- package/lib/memory/validators/external-academic-invariants.cjs +249 -0
- package/lib/memory/validators/external-industry-invariants.cjs +271 -0
- package/lib/memory/validators/external-patents-invariants.cjs +266 -0
- package/lib/memory/validators/minto-invariants.cjs +62 -0
- package/lib/memory/validators/navigation-invariants.cjs +340 -0
- package/lib/memory/validators/queue-health.cjs +95 -0
- package/lib/memory/validators/snapshot-integrity.cjs +129 -0
- package/lib/memory/validators/stale-lifecycle.cjs +116 -0
- package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
- package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
- package/lib/memory/workflow-layer-e2e.test.cjs +262 -0
- package/lib/memory/write-lock-atomic.test.cjs +137 -0
- package/lib/memory/write-lock-atomic.worker.cjs +55 -0
- package/lib/parity/check-parity.cjs +83 -0
- package/lib/presentation/presentation-server.cjs +101 -0
- package/lib/presentation/presentation-watcher.cjs +123 -0
- package/lib/quickview/hub-server.cjs +719 -0
- package/lib/quickview/server.cjs +533 -0
- package/lib/render/JTBD-PALETTES.md +145 -0
- package/lib/render/ROOM.md +111 -0
- package/lib/render/render-v2.cjs +532 -0
- package/lib/render/render-v2.test.cjs +267 -0
- package/lib/render/render.cjs +65 -0
- package/lib/sessionstart/budget-compressor.cjs +130 -0
- package/lib/sessionstart/contributor-interface.cjs +134 -0
- package/lib/sessionstart/contributor-isolator.cjs +128 -0
- package/lib/sessionstart/precedence-ladder.cjs +47 -0
- package/lib/state/ROOM.md +46 -0
- package/lib/state/state-md-parser.cjs +215 -0
- package/lib/statusline/ROOM.md +38 -0
- package/lib/statusline/banner-suppression.cjs +50 -0
- package/lib/statusline/cockpit-renderer.cjs +443 -0
- package/lib/statusline/cockpit-signals.cjs +312 -0
- package/lib/statusline/cockpit-telemetry.cjs +152 -0
- package/lib/statusline/ctx-window.cjs +135 -0
- package/lib/statusline/governing-thought-truncator.cjs +45 -0
- package/lib/statusline/next-move-cache.cjs +235 -0
- package/lib/statusline/ratification-next.cjs +164 -0
- package/lib/statusline/room-health-cache.cjs +200 -0
- package/lib/statusline/surface-detect.cjs +85 -0
- package/lib/statusline/two-row-renderer.cjs +207 -0
- package/lib/statusline/version-resolver.cjs +81 -0
- package/lib/ui/design-system.cjs +19 -0
- package/lib/update-bootstrap.sh.template +145 -0
- package/lib/vault/frontmatter-schema.cjs +297 -0
- package/lib/vault/room-scanner.cjs +365 -0
- package/lib/vault/wikilink-builder.cjs +231 -0
- package/lib/vault/wikilink-builder.test.cjs +182 -0
- package/lib/wiki/briefing.cjs +193 -0
- package/lib/wiki/editor-dist/wiki-editor.css +1 -0
- package/lib/wiki/editor-dist/wiki-editor.js +1327 -0
- package/lib/wiki/graph-links.cjs +281 -0
- package/lib/wiki/page-renderer.cjs +229 -0
- package/lib/wiki/room-home.cjs +353 -0
- package/lib/wiki/wiki-chat.cjs +81 -0
- package/lib/wiki/wiki-export.cjs +364 -0
- package/lib/wiki/wiki-layout.cjs +1143 -0
- package/lib/wiki/wiki-search.cjs +142 -0
- package/lib/wiki/wiki-server.cjs +748 -0
- package/lib/wiki/wiki-watcher.cjs +105 -0
- package/lib/workflow/ROOM.md +47 -0
- package/lib/workflow/chain-source.cjs +179 -0
- package/lib/workflow/command-resolver.cjs +155 -0
- package/lib/workflow/command-resolver.test.cjs +235 -0
- package/lib/workflow/cross-room-umbilical-closer.cjs +364 -0
- package/lib/workflow/dial-close-reach.cjs +405 -0
- package/lib/workflow/f-selector-ranker.cjs +1148 -0
- package/lib/workflow/f1-pick-consumer.cjs +232 -0
- package/lib/workflow/f3-depth-consumer.cjs +98 -0
- package/lib/workflow/f4-scope-consumer.cjs +112 -0
- package/lib/workflow/f8-consumer.test.cjs +82 -0
- package/lib/workflow/f8-fanout-consumer.cjs +208 -0
- package/lib/workflow/f9-consumer.test.cjs +71 -0
- package/lib/workflow/f9-ordered-consumer.cjs +199 -0
- package/lib/workflow/local-chain-recommender.cjs +330 -0
- package/lib/workflow/memory-cascade-f9-adapter.cjs +273 -0
- package/lib/workflow/memory-governance-closer.cjs +384 -0
- package/lib/workflow/offer-closer.cjs +407 -0
- package/lib/workflow/reach-hedge-ranker.cjs +809 -0
- package/lib/workflow/reach-reject-reader.cjs +372 -0
- package/lib/workflow/reconcile-f9-adapter.cjs +299 -0
- package/lib/workflow/selector-decisions.cjs +478 -0
- package/lib/workflow/session-binding-consumer.cjs +249 -0
- package/npm-shrinkwrap.json +1544 -0
- package/output-styles/destijl.md +67 -0
- package/package.json +22 -4
- package/pipelines/PWS_grading/01-deep-grade.md +48 -0
- package/pipelines/PWS_grading/02-mullins.md +50 -0
- package/pipelines/PWS_grading/03-build-thesis.md +60 -0
- package/pipelines/PWS_grading/04-structure-argument.md +77 -0
- package/pipelines/PWS_grading/CHAIN.md +79 -0
- package/pipelines/analogy/01-decompose.md +80 -0
- package/pipelines/analogy/02-abstract.md +87 -0
- package/pipelines/analogy/03-search.md +135 -0
- package/pipelines/analogy/04-transfer.md +101 -0
- package/pipelines/analogy/05-validate.md +106 -0
- package/pipelines/analogy/CHAIN.md +73 -0
- package/pipelines/discovery/01-explore-domains.md +44 -0
- package/pipelines/discovery/02-think-hats.md +50 -0
- package/pipelines/discovery/03-analyze-needs.md +54 -0
- package/pipelines/discovery/CHAIN.md +48 -0
- package/pipelines/thesis/01-structure-argument.md +45 -0
- package/pipelines/thesis/02-challenge-assumptions.md +48 -0
- package/pipelines/thesis/03-build-thesis.md +54 -0
- package/pipelines/thesis/CHAIN.md +48 -0
- package/references/brain/causal-directives.md +91 -0
- package/references/brain/causal-enrichment.cypher +165 -0
- package/references/brain/command-triggers-schema.md +15 -0
- package/references/brain/graph-architecture.md +317 -0
- package/references/brain/query-patterns.md +504 -0
- package/references/brain/room-hierarchy-schema.md +218 -0
- package/references/brain/schema.md +86 -0
- package/references/capability-radar/capabilities-index.md +269 -0
- package/references/capability-radar/changelog-cache.md +95 -0
- package/references/causal/causal-schema.md +103 -0
- package/references/design/email-template-standard.md +169 -0
- package/references/design/graph-visualization-standard.md +178 -0
- package/references/design/newsletter-email-template.html +307 -0
- package/references/document-generation.md +179 -0
- package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
- package/references/import-config.md +141 -0
- package/references/integrations/detection-patterns.md +101 -0
- package/references/meeting/artifact-template.md +377 -0
- package/references/meeting/cross-meeting-intelligence.md +216 -0
- package/references/meeting/cross-relationship-patterns.md +202 -0
- package/references/meeting/extraction-perspectives.md +225 -0
- package/references/meeting/filing-protocol.md +222 -0
- package/references/meeting/knowledge-typing.md +242 -0
- package/references/meeting/live-join-interface.md +244 -0
- package/references/meeting/section-mapping.md +192 -0
- package/references/meeting/segment-classification.md +258 -0
- package/references/meeting/speaker-profile-template.md +219 -0
- package/references/meeting/summary-template.md +348 -0
- package/references/meeting/transcript-patterns.md +226 -0
- package/references/methodology/analyze-needs.md +135 -0
- package/references/methodology/analyze-systems.md +121 -0
- package/references/methodology/analyze-timing.md +149 -0
- package/references/methodology/beautiful-question.md +109 -0
- package/references/methodology/build-knowledge.md +161 -0
- package/references/methodology/build-thesis-scored.md +60 -0
- package/references/methodology/build-thesis.md +237 -0
- package/references/methodology/causal-loop-diagrams.md +65 -0
- package/references/methodology/challenge-assumptions.md +127 -0
- package/references/methodology/diagnose.md +169 -0
- package/references/methodology/dominant-designs.md +212 -0
- package/references/methodology/explore-domains.md +147 -0
- package/references/methodology/explore-futures.md +163 -0
- package/references/methodology/explore-trends.md +129 -0
- package/references/methodology/find-bottlenecks.md +131 -0
- package/references/methodology/grade.md +211 -0
- package/references/methodology/huji-stage-a-intake.md +246 -0
- package/references/methodology/index.md +34 -0
- package/references/methodology/leadership.md +200 -0
- package/references/methodology/lean-canvas.md +116 -0
- package/references/methodology/leverage-scan-signatures.md +83 -0
- package/references/methodology/macro-trends.md +192 -0
- package/references/methodology/map-unknowns.md +137 -0
- package/references/methodology/mullins-7-domains.md +104 -0
- package/references/methodology/problem-types.md +65 -0
- package/references/methodology/root-cause.md +178 -0
- package/references/methodology/rubric-huji.md +160 -0
- package/references/methodology/sapphire-encoding.md +355 -0
- package/references/methodology/scenario-plan.md +190 -0
- package/references/methodology/score-innovation.md +154 -0
- package/references/methodology/skillopt-judge-rubric.md +48 -0
- package/references/methodology/skillopt-queries-rubric.md +60 -0
- package/references/methodology/skillopt-refute-rubric.md +59 -0
- package/references/methodology/skillopt-review-rubric.md +72 -0
- package/references/methodology/skillopt-revise-rubric.md +48 -0
- package/references/methodology/structure-argument.md +158 -0
- package/references/methodology/systems-thinking.md +231 -0
- package/references/methodology/think-hats.md +147 -0
- package/references/methodology/triz-matrix.json +751 -0
- package/references/methodology/triz-principles.md +501 -0
- package/references/methodology/user-needs.md +199 -0
- package/references/methodology/validate.md +163 -0
- package/references/methodology/value-proposition.md +244 -0
- package/references/opportunities/funding-lifecycle.md +103 -0
- package/references/opportunities/grant-api-patterns.md +99 -0
- package/references/opportunities/opportunity-template.md +84 -0
- package/references/opportunities/tnufa-rubric.md +81 -0
- package/references/personality/assessment-philosophy.md +72 -0
- package/references/personality/lexicon.md +100 -0
- package/references/personality/persona-chains.md +56 -0
- package/references/personality/pws-lexicon-full.md +499 -0
- package/references/personality/voice-dna.md +156 -0
- package/references/personas/hat-perspectives.md +76 -0
- package/references/personas/persona-template.md +63 -0
- package/references/pipeline/act-output-contract.md +88 -0
- package/references/pipeline/chains-index.md +39 -0
- package/references/pws-profile-generation.md +79 -0
- package/references/reasoning/reasoning-schema.md +143 -0
- package/references/reasoning/reasoning-template.md +68 -0
- package/references/reasoning/run-template.md +38 -0
- package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
- package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
- package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
- package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
- package/references/security/agentshield-baseline.json +8 -0
- package/references/security/cve-db.json +220 -0
- package/references/taxonomy/TAXONOMY.md +192 -0
- package/references/templates/MINTO.md +36 -0
- package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
- package/references/vault-kit/README.md +35 -0
- package/references/vault-kit/app.json +12 -0
- package/references/vault-kit/appearance.json +12 -0
- package/references/vault-kit/graph.json +35 -0
- package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
- package/references/vault-kit/templates/new-artifact.md +37 -0
- package/references/vault-kit/templates/new-meeting-note.md +35 -0
- package/references/vault-kit/templates/new-team-profile.md +29 -0
- package/references/vault-kit/templates/new-xref.md +35 -0
- package/references/visual/ROOM.md +55 -0
- package/references/visual/palette.json +54 -0
- package/references/visual/symbol-system.md +151 -0
- package/scripts/189-plurai-gate-check.cjs +88 -0
- package/scripts/198-plurai-gate-check.cjs +118 -0
- package/scripts/198-rollback-rehearsal.cjs +233 -0
- package/scripts/83-scope-injection.test.cjs +648 -0
- package/scripts/PYTHON_GATES.md +78 -0
- package/scripts/__pycache__/compute-hsi.cpython-312.pyc +0 -0
- package/scripts/__pycache__/detect-reverse-salients.cpython-312.pyc +0 -0
- package/scripts/__pycache__/rs-engine.cpython-312.pyc +0 -0
- package/scripts/act-command.cjs +518 -0
- package/scripts/admin-brain-write.cjs +442 -0
- package/scripts/admin-command-gate.cjs +178 -0
- package/scripts/agentshield-scan-cli.cjs +226 -0
- package/scripts/agentshield-sessionstart-scan.cjs +282 -0
- package/scripts/analogy-fitness-report.cjs +266 -0
- package/scripts/analyze-room +1079 -0
- package/scripts/apply-teaching-content.cjs +298 -0
- package/scripts/async-artifact-auto-commit.cjs +485 -0
- package/scripts/audit-active-phase-resolver.cjs +160 -0
- package/scripts/audit-body-shape-coverage.cjs +213 -0
- package/scripts/auto-explore-drain.cjs +274 -0
- package/scripts/auto-explore-fingerprint.cjs +354 -0
- package/scripts/auto-explore-fire.cjs +359 -0
- package/scripts/backfill-correlation-id.cjs +425 -0
- package/scripts/backfill-hitl-shape.cjs +276 -0
- package/scripts/backfill-hitl-shape.test.cjs +307 -0
- package/scripts/backup-modifications +68 -0
- package/scripts/banner +182 -0
- package/scripts/brain-derivation-drain.cjs +291 -0
- package/scripts/brain-derive-command.cjs +846 -0
- package/scripts/brain-normalize-books.cypher +505 -0
- package/scripts/brain-normalize-final.cypher +1355 -0
- package/scripts/brain-normalize-problemtype.cypher +475 -0
- package/scripts/brain-normalize-supplement.cypher +449 -0
- package/scripts/brain-normalize-targeted.cypher +344 -0
- package/scripts/brain-normalize-v194.cypher +61 -0
- package/scripts/brain-normalize.cypher +530 -0
- package/scripts/brain-response-sanitize-hook.cjs +165 -0
- package/scripts/build-brain-census.cjs +957 -0
- package/scripts/build-brain-packet-schema.cjs +225 -0
- package/scripts/build-command-registry.cjs +587 -0
- package/scripts/build-connector-registry.cjs +1331 -0
- package/scripts/build-corpus-stats.cjs +408 -0
- package/scripts/build-dist-bundles.cjs +495 -0
- package/scripts/build-ecosystem-graph.cjs +490 -0
- package/scripts/build-graph +1462 -0
- package/scripts/build-graph-from-sqlite.cjs +97 -0
- package/scripts/build-harness-manifest.cjs +1090 -0
- package/scripts/build-jtbd-nudges +205 -0
- package/scripts/build-new-surface.cjs +805 -0
- package/scripts/build-orchestration-projection.cjs +1221 -0
- package/scripts/build-render-coverage.cjs +637 -0
- package/scripts/build-skill-mirrors.cjs +681 -0
- package/scripts/cache-hitrate-report.cjs +216 -0
- package/scripts/causal-to-graph.cjs +163 -0
- package/scripts/check-abstraction-fixture-neutral.cjs +160 -0
- package/scripts/check-admin-identity.cjs +212 -0
- package/scripts/check-brain-tool-liveness.cjs +502 -0
- package/scripts/check-card-fire.cjs +1809 -0
- package/scripts/check-cirs-declaration.cjs +279 -0
- package/scripts/check-deck-design.cjs +104 -0
- package/scripts/check-dual-graph-health.cjs +645 -0
- package/scripts/check-first-touch-drift.cjs +220 -0
- package/scripts/check-flagship-floor.cjs +391 -0
- package/scripts/check-framework-vocabulary-drift.cjs +563 -0
- package/scripts/check-gate-seam.cjs +178 -0
- package/scripts/check-graph-derive-health.cjs +208 -0
- package/scripts/check-graph-export-typemap.cjs +118 -0
- package/scripts/check-help-coverage.cjs +290 -0
- package/scripts/check-hitl-stages.cjs +219 -0
- package/scripts/check-hook-schema-compatibility.cjs +231 -0
- package/scripts/check-hsi-deps +42 -0
- package/scripts/check-kuzu-reintroduction.cjs +251 -0
- package/scripts/check-onboard +43 -0
- package/scripts/check-onboard-statusline.cjs +363 -0
- package/scripts/check-palette-consistency.cjs +106 -0
- package/scripts/check-pending-ambiguous.cjs +244 -0
- package/scripts/check-pending-breakthrough.cjs +189 -0
- package/scripts/check-pending-breakthrough.test.cjs +166 -0
- package/scripts/check-pending-naming-decision.cjs +97 -0
- package/scripts/check-plugin-path-anchoring.cjs +830 -0
- package/scripts/check-publish-needs.cjs +126 -0
- package/scripts/check-registry-drift.cjs +206 -0
- package/scripts/check-release-payload-ceiling.cjs +269 -0
- package/scripts/check-render-coverage.cjs +558 -0
- package/scripts/check-research-isomorphism.cjs +446 -0
- package/scripts/check-reward-before-investment.cjs +261 -0
- package/scripts/check-room-blueprints.cjs +213 -0
- package/scripts/check-schema-aliases.cjs +532 -0
- package/scripts/check-shape-declaration.cjs +958 -0
- package/scripts/check-shape-declaration.test.cjs +185 -0
- package/scripts/check-skill-spec.cjs +312 -0
- package/scripts/check-skill-vs-code-drift.cjs +178 -0
- package/scripts/check-substrate.cjs +513 -0
- package/scripts/check-tool-honesty.cjs +1601 -0
- package/scripts/check-update +60 -0
- package/scripts/check-version-and-sha.cjs +366 -0
- package/scripts/check-voice-style.cjs +184 -0
- package/scripts/check-worktree-hygiene.cjs +530 -0
- package/scripts/classify-insight +59 -0
- package/scripts/coherence-smoke-test.cjs +489 -0
- package/scripts/compute-bayesian-surprise.py +175 -0
- package/scripts/compute-blindspot-mass.py +209 -0
- package/scripts/compute-disruption-index.py +211 -0
- package/scripts/compute-element-novelty.py +254 -0
- package/scripts/compute-external-whitespace.py +563 -0
- package/scripts/compute-hsi.py +947 -0
- package/scripts/compute-meetings-intelligence +716 -0
- package/scripts/compute-opportunity-state +17 -0
- package/scripts/compute-state +393 -0
- package/scripts/compute-team +1112 -0
- package/scripts/compute-whitespace-embeddings.py +464 -0
- package/scripts/compute-whitespace-gaps.py +562 -0
- package/scripts/compute_topic_forest.py +684 -0
- package/scripts/consolidate-pinecone.py +265 -0
- package/scripts/context-monitor +943 -0
- package/scripts/create-speaker-profile +311 -0
- package/scripts/cross-room-detect.cjs +195 -0
- package/scripts/csv-to-idea-graph.cjs +755 -0
- package/scripts/ctxl-eval.cjs +846 -0
- package/scripts/curation-132-03-dedup-held-rename.cjs +395 -0
- package/scripts/curation-132-05-pseudonymize.cjs +189 -0
- package/scripts/derive-verb-reach-affinity.cjs +507 -0
- package/scripts/detect-reverse-salients.py +242 -0
- package/scripts/diagnostics-command.cjs +345 -0
- package/scripts/dial-memory-refresh-command.cjs +147 -0
- package/scripts/discover-analogy-whitespace.py +433 -0
- package/scripts/discover-hsi-whitespace.py +352 -0
- package/scripts/discover-rs-whitespace.py +327 -0
- package/scripts/discovery-cycle.cjs +625 -0
- package/scripts/disposition-render-v2.cjs +229 -0
- package/scripts/doctor-preflight-format.cjs +101 -0
- package/scripts/doctor.cjs +4327 -0
- package/scripts/dogfood-bridge/append-to-queue.sh +59 -0
- package/scripts/dogfood-bridge/path-filter.cjs +86 -0
- package/scripts/dogfood-derive.cjs +158 -0
- package/scripts/dogfood-emit.cjs +126 -0
- package/scripts/empathy-observation-emit.cjs +108 -0
- package/scripts/enrichment-queue-append.cjs +157 -0
- package/scripts/ensure-brain-baseline.cjs +117 -0
- package/scripts/entity-extract.cjs +1135 -0
- package/scripts/eureka-command.cjs +687 -0
- package/scripts/eureka-critic-run.cjs +662 -0
- package/scripts/eureka-portfolio-report.cjs +2038 -0
- package/scripts/eureka-room-report.cjs +419 -0
- package/scripts/explain-decision-command.cjs +499 -0
- package/scripts/export-pinecone-embeddings.cjs +516 -0
- package/scripts/extract-room-intelligence.cjs +714 -0
- package/scripts/fetch-brain-baseline.cjs +190 -0
- package/scripts/fetch-brain-baseline.py +360 -0
- package/scripts/feynman-minto-guardian.cjs +672 -0
- package/scripts/feynman-timeline-refresh-command.cjs +270 -0
- package/scripts/file-asset +253 -0
- package/scripts/fire-sensor-event.cjs +83 -0
- package/scripts/first-install-router.cjs +968 -0
- package/scripts/frontmatter-schema-validator.cjs +339 -0
- package/scripts/fts-index-drain.cjs +221 -0
- package/scripts/generate-chat-embed.cjs +179 -0
- package/scripts/generate-deck.cjs +322 -0
- package/scripts/generate-export.cjs +474 -0
- package/scripts/generate-hub.cjs +2553 -0
- package/scripts/generate-lobby.cjs +718 -0
- package/scripts/generate-presentation.cjs +988 -0
- package/scripts/generate-presentation.test.cjs +405 -0
- package/scripts/generate-section-intelligence.cjs +330 -0
- package/scripts/generate-snapshot.cjs +1410 -0
- package/scripts/generate-standalone +159 -0
- package/scripts/git-ops +211 -0
- package/scripts/graph-heal-pipeline.cjs +321 -0
- package/scripts/gsd-artifact-graph-hook.cjs +219 -0
- package/scripts/gsd-graph-derive-drain.cjs +571 -0
- package/scripts/gsd-graph-derive-sweep.cjs +195 -0
- package/scripts/heal-command.cjs +1365 -0
- package/scripts/hedge-refit-pipeline.cjs +207 -0
- package/scripts/help-renderer.cjs +303 -0
- package/scripts/hmi-compliance-poll.cjs +451 -0
- package/scripts/hmi-status-command.cjs +510 -0
- package/scripts/hooked-rescore-117.cjs +224 -0
- package/scripts/hooks/pre-commit +533 -0
- package/scripts/hooks/pre-commit-room-minto-guard.cmd +43 -0
- package/scripts/hooks/pre-commit-room-minto-guard.sh +533 -0
- package/scripts/hsi-to-graph.cjs +194 -0
- package/scripts/huji-batch.cjs +821 -0
- package/scripts/huji-eval.cjs +1158 -0
- package/scripts/huji-intake.cjs +563 -0
- package/scripts/huji-pin-smoketest.cjs +454 -0
- package/scripts/huji-run-one-async.cjs +305 -0
- package/scripts/huji-run-one.cjs +671 -0
- package/scripts/install-pre-commit.sh +113 -0
- package/scripts/intent-classifier +106 -0
- package/scripts/intent-classifier.cjs +3620 -0
- package/scripts/interpret-whitespace.cjs +691 -0
- package/scripts/jtbd-command.cjs +810 -0
- package/scripts/jtbd-update.cjs +299 -0
- package/scripts/label-topic-forest.cjs +667 -0
- package/scripts/learn-from-usage +168 -0
- package/scripts/lib/__pycache__/ensure_ml_deps.cpython-312.pyc +0 -0
- package/scripts/lib/ensure_ml_deps.py +97 -0
- package/scripts/load-embeddings-into-neo4j.cjs +569 -0
- package/scripts/memory-artifact-graph-hook.cjs +191 -0
- package/scripts/memory-command.cjs +687 -0
- package/scripts/memory-completion-detector.cjs +217 -0
- package/scripts/memory-lifecycle.cjs +620 -0
- package/scripts/memory-resume-nudge.cjs +233 -0
- package/scripts/methodology-ingest-local.sh +36 -0
- package/scripts/migrate-brain-mcp-from-http-to-stdio.cjs +190 -0
- package/scripts/migrate-brain-mcp-from-http-to-stdio.test.cjs +242 -0
- package/scripts/migrate-hats-to-roomdb.cjs +217 -0
- package/scripts/migrate-lazygraph.cjs +250 -0
- package/scripts/migrate-minto-schema-v88.cjs +296 -0
- package/scripts/migrate-plugin-root-refs.cjs +168 -0
- package/scripts/migrate-room-sections-v275.cjs +556 -0
- package/scripts/migrate-rooms +417 -0
- package/scripts/migrate-skill-frontmatter.cjs +472 -0
- package/scripts/migrate-stale-user-settings.cjs +296 -0
- package/scripts/migrate-telemetry-v1.cjs +384 -0
- package/scripts/migrate-telemetry-v1.test.cjs +356 -0
- package/scripts/minto-debouncer.cjs +428 -0
- package/scripts/mos-status.cjs +622 -0
- package/scripts/mva-detect.cjs +229 -0
- package/scripts/mva-run.cjs +36 -0
- package/scripts/on-agent-complete +163 -0
- package/scripts/on-cwd-changed +149 -0
- package/scripts/on-file-changed +68 -0
- package/scripts/on-stop +617 -0
- package/scripts/on-task-complete +194 -0
- package/scripts/operator-command.cjs +858 -0
- package/scripts/operator-update.cjs +308 -0
- package/scripts/part8-egress-guard-hook.cjs +229 -0
- package/scripts/pipeline-command.cjs +271 -0
- package/scripts/post-compact +383 -0
- package/scripts/post-update-activation.cjs +339 -0
- package/scripts/post-write +283 -0
- package/scripts/power-demo-prompt.md +621 -0
- package/scripts/pre-compact +273 -0
- package/scripts/preflight-auto-explore.cjs +314 -0
- package/scripts/preflight-doctor.cjs +225 -0
- package/scripts/preflight-release-drift.cjs +168 -0
- package/scripts/preflight-tension-surface.cjs +414 -0
- package/scripts/probe-brain-contract.cjs +502 -0
- package/scripts/publish-ops +346 -0
- package/scripts/query-efficiency-telemetry.cjs +364 -0
- package/scripts/query-semantic-scholar.cjs +545 -0
- package/scripts/reapply-modifications +49 -0
- package/scripts/reassign-primary.cjs +125 -0
- package/scripts/recompile-room-references.cjs +601 -0
- package/scripts/release-beta-preflight.sh +69 -0
- package/scripts/render-pdf +588 -0
- package/scripts/render-viz +190 -0
- package/scripts/research-speaker +239 -0
- package/scripts/resolve-room +204 -0
- package/scripts/restore-post-compact-context.cjs +320 -0
- package/scripts/room-auto-create-nudge.cjs +136 -0
- package/scripts/room-naming-selector.cjs +201 -0
- package/scripts/room-registry +767 -0
- package/scripts/rs-discovery-engine.cjs +725 -0
- package/scripts/rs-engine.py +2073 -0
- package/scripts/rs-experts-command.cjs +300 -0
- package/scripts/rs-explain-command.cjs +323 -0
- package/scripts/rs-fetch-command.cjs +170 -0
- package/scripts/rs-thesis-command.cjs +203 -0
- package/scripts/rs-vector-bridge.cjs +410 -0
- package/scripts/run-harness.cjs +890 -0
- package/scripts/scout-cadence-guard.cjs +323 -0
- package/scripts/scout-cadence-runner.cjs +509 -0
- package/scripts/scout-telemetry-aggregator.cjs +268 -0
- package/scripts/sealed-walker.py +96 -0
- package/scripts/seed-brain-commands.cjs +324 -0
- package/scripts/self-update +105 -0
- package/scripts/sentinel-deadline-monitor +241 -0
- package/scripts/sentinel-health-check +198 -0
- package/scripts/sentinel-snapshot +42 -0
- package/scripts/serve-dashboard +103 -0
- package/scripts/serve-dashboard-live +932 -0
- package/scripts/serve-presentation +97 -0
- package/scripts/serve-wiki +90 -0
- package/scripts/session-end-presence.cjs +79 -0
- package/scripts/session-start +1942 -0
- package/scripts/sessionstart-coordinator.cjs +249 -0
- package/scripts/sessionstart-npm-reconcile.cjs +111 -0
- package/scripts/sessionstart-post-update-preflight.cjs +303 -0
- package/scripts/sessionstart-reference-now-seed.cjs +137 -0
- package/scripts/setup-hooks.sh +112 -0
- package/scripts/skillopt-codereview.cjs +830 -0
- package/scripts/skillopt-eval.cjs +519 -0
- package/scripts/skillopt-funnel.cjs +1076 -0
- package/scripts/skillopt-genqueries.cjs +571 -0
- package/scripts/skillopt-inventory.cjs +319 -0
- package/scripts/skillopt-merge.cjs +656 -0
- package/scripts/skillopt-triggerloop.cjs +1126 -0
- package/scripts/soft-alias-runner.cjs +116 -0
- package/scripts/stamp-artifact-write.cjs +276 -0
- package/scripts/stamp-firing-block.cjs +325 -0
- package/scripts/state-write.cjs +97 -0
- package/scripts/statusline-fallback-echo.cjs +297 -0
- package/scripts/statusline-mos +116 -0
- package/scripts/statusline-mos-dispatch +127 -0
- package/scripts/suggest-next-command.cjs +416 -0
- package/scripts/sweep-skill-descriptions.cjs +263 -0
- package/scripts/sync-rooms-brain +438 -0
- package/scripts/sync-rooms-graph +264 -0
- package/scripts/telemetry-command-invocation.cjs +120 -0
- package/scripts/test-doctor-recovery.cjs +242 -0
- package/scripts/test-fresh-install.md +140 -0
- package/scripts/track-analytics +156 -0
- package/scripts/transcribe-audio +148 -0
- package/scripts/transcript-ingest.cjs +225 -0
- package/scripts/update-icm-index +153 -0
- package/scripts/v182-brain-optimize.cypher +1191 -0
- package/scripts/validate-model-profiles +93 -0
- package/scripts/vault-content-reformatter.cjs +581 -0
- package/scripts/vault-export-orchestrator.cjs +400 -0
- package/scripts/vault-footer-injector.cjs +315 -0
- package/scripts/vault-import.cjs +839 -0
- package/scripts/vault-integration-test.cjs +318 -0
- package/scripts/vault-regenerate-all.cjs +216 -0
- package/scripts/vault-regenerate-all.test.cjs +138 -0
- package/scripts/vault-rules-generator.cjs +220 -0
- package/scripts/vault-section-minto-generator.cjs +1642 -0
- package/scripts/vault-section-minto-generator.integration.test.cjs +576 -0
- package/scripts/vault-section-minto-generator.test.cjs +291 -0
- package/scripts/vault-section-state-generator.cjs +385 -0
- package/scripts/vault-welcome-generator.cjs +403 -0
- package/scripts/vault-wikilink-injector.cjs +449 -0
- package/scripts/verify-phase-132.cjs +338 -0
- package/scripts/verify-release +744 -0
- package/scripts/whitespace-command.cjs +831 -0
- package/scripts/whitespace-to-brain.cjs +383 -0
- package/scripts/whitespace-to-graph.cjs +220 -0
- package/scripts/wikilink-batch.cjs +102 -0
- package/scripts/wikilink-file.cjs +139 -0
- package/scripts/write-bridge-artifacts.cjs +140 -0
- package/scripts/write-scope-check +12 -0
- package/scripts/write-scope-check.cjs +461 -0
- package/scripts/write-whitespace-sections.cjs +483 -0
- package/settings.json +7 -0
- package/skills/act/SKILL.md +523 -0
- package/skills/admin/SKILL.md +437 -0
- package/skills/agentshield/SKILL.md +50 -0
- package/skills/analyze-needs/SKILL.md +74 -0
- package/skills/analyze-systems/SKILL.md +69 -0
- package/skills/analyze-timing/SKILL.md +94 -0
- package/skills/auto-explore/SKILL.md +84 -0
- package/skills/beautiful-question/SKILL.md +72 -0
- package/skills/bono/SKILL.md +146 -0
- package/skills/brain-connector/SKILL.md +206 -0
- package/skills/brain-derive/SKILL.md +140 -0
- package/skills/build-knowledge/SKILL.md +118 -0
- package/skills/build-thesis/SKILL.md +90 -0
- package/skills/causal/SKILL.md +265 -0
- package/skills/challenge-assumptions/SKILL.md +74 -0
- package/skills/client-discovery-interview/SKILL.md +94 -0
- package/skills/client-discovery-interview/discovery-scaffold.json +41 -0
- package/skills/client-discovery-interview/question-bank.md +149 -0
- package/skills/compare-ventures/SKILL.md +113 -0
- package/skills/context-engine/SKILL.md +169 -0
- package/skills/conversation-mode/SKILL.md +189 -0
- package/skills/correct-reference-now/SKILL.md +86 -0
- package/skills/dashboard/SKILL.md +142 -0
- package/skills/deck/SKILL.md +113 -0
- package/skills/deep-grade/SKILL.md +210 -0
- package/skills/diagnose/SKILL.md +128 -0
- package/skills/diagnostics/SKILL.md +193 -0
- package/skills/dial-memory-refresh/SKILL.md +116 -0
- package/skills/diffusion/SKILL.md +125 -0
- package/skills/discover/SKILL.md +216 -0
- package/skills/doctor/SKILL.md +269 -0
- package/skills/dogfood-flush/SKILL.md +133 -0
- package/skills/dominant-designs/SKILL.md +72 -0
- package/skills/eureka/SKILL.md +313 -0
- package/skills/explain-decision/SKILL.md +138 -0
- package/skills/explore-domains/SKILL.md +74 -0
- package/skills/explore-futures/SKILL.md +64 -0
- package/skills/explore-opportunity/SKILL.md +168 -0
- package/skills/explore-trends/SKILL.md +74 -0
- package/skills/export/SKILL.md +126 -0
- package/skills/feynman-timeline-refresh/SKILL.md +105 -0
- package/skills/file-meeting/SKILL.md +1190 -0
- package/skills/find-analogies/SKILL.md +360 -0
- package/skills/find-bottlenecks/SKILL.md +104 -0
- package/skills/find-connections/SKILL.md +108 -0
- package/skills/funding/SKILL.md +110 -0
- package/skills/futures/SKILL.md +124 -0
- package/skills/grade/SKILL.md +252 -0
- package/skills/grade-grant/SKILL.md +293 -0
- package/skills/graph/SKILL.md +216 -0
- package/skills/hat-briefing/SKILL.md +160 -0
- package/skills/heal/SKILL.md +69 -0
- package/skills/help/SKILL.md +172 -0
- package/skills/hmi-status/SKILL.md +73 -0
- package/skills/ignite/SKILL.md +225 -0
- package/skills/ingest-methodology/SKILL.md +125 -0
- package/skills/intel-pipeline/SKILL.md +128 -0
- package/skills/intelligence-orchestrator/SKILL.md +262 -0
- package/skills/jtbd/SKILL.md +261 -0
- package/skills/larry-personality/SKILL.md +570 -0
- package/skills/larry-personality/framework-chains.md +92 -0
- package/skills/larry-personality/mode-engine.md +185 -0
- package/skills/leadership/SKILL.md +109 -0
- package/skills/lean-canvas/SKILL.md +70 -0
- package/skills/macro-trends/SKILL.md +84 -0
- package/skills/map-unknowns/SKILL.md +72 -0
- package/skills/memory/SKILL.md +212 -0
- package/skills/memory-cortex-reach/SKILL.md +80 -0
- package/skills/models/SKILL.md +197 -0
- package/skills/mos/SKILL.md +160 -0
- package/skills/mos-deck-engine/SKILL.md +329 -0
- package/skills/mos-reason/SKILL.md +484 -0
- package/skills/mullins/SKILL.md +151 -0
- package/skills/mullins-scaffold/SKILL.md +132 -0
- package/skills/mullins-scaffold/scaffold.json +223 -0
- package/skills/mva-brief/SKILL.md +87 -0
- package/skills/mva-option/SKILL.md +120 -0
- package/skills/mva-pipeline/SKILL.md +158 -0
- package/skills/mva-report/SKILL.md +79 -0
- package/skills/new-project/SKILL.md +629 -0
- package/skills/new-surface/SKILL.md +112 -0
- package/skills/onboard/SKILL.md +485 -0
- package/skills/operator/SKILL.md +167 -0
- package/skills/opportunities/SKILL.md +171 -0
- package/skills/organize/SKILL.md +69 -0
- package/skills/persona/SKILL.md +272 -0
- package/skills/pipeline/SKILL.md +164 -0
- package/skills/present/SKILL.md +123 -0
- package/skills/publish/SKILL.md +224 -0
- package/skills/pws-brain/SKILL.md +152 -0
- package/skills/pws-methodology/SKILL.md +92 -0
- package/skills/qualify-opportunity/SKILL.md +111 -0
- package/skills/query/SKILL.md +68 -0
- package/skills/radar/SKILL.md +106 -0
- package/skills/reanalyze/SKILL.md +122 -0
- package/skills/research/SKILL.md +462 -0
- package/skills/room/SKILL.md +381 -0
- package/skills/room-passive/SKILL.md +174 -0
- package/skills/room-proactive/SKILL.md +344 -0
- package/skills/rooms/SKILL.md +678 -0
- package/skills/root-cause/SKILL.md +73 -0
- package/skills/rs-experts/SKILL.md +151 -0
- package/skills/rs-explain/SKILL.md +130 -0
- package/skills/rs-fetch/SKILL.md +115 -0
- package/skills/rs-thesis/SKILL.md +129 -0
- package/skills/scenario-plan/SKILL.md +76 -0
- package/skills/scheduled-tasks/SKILL.md +301 -0
- package/skills/score-innovation/SKILL.md +74 -0
- package/skills/scout/SKILL.md +370 -0
- package/skills/setup/SKILL.md +654 -0
- package/skills/show/SKILL.md +68 -0
- package/skills/skill/SKILL.md +104 -0
- package/skills/snapshot/SKILL.md +178 -0
- package/skills/speakers/SKILL.md +116 -0
- package/skills/splash/SKILL.md +54 -0
- package/skills/stance/SKILL.md +89 -0
- package/skills/status/SKILL.md +107 -0
- package/skills/structure-argument/SKILL.md +78 -0
- package/skills/suggest-next/SKILL.md +137 -0
- package/skills/systems-thinking/SKILL.md +220 -0
- package/skills/think-hats/SKILL.md +82 -0
- package/skills/trending-to-absurd/SKILL.md +100 -0
- package/skills/ui-system/SKILL.md +474 -0
- package/skills/ui-system/design-system/M-OS-DESIGN-SYSTEM.md +273 -0
- package/skills/ui-system/design-system/SPEC.md +72 -0
- package/skills/ui-system/design-system/image-prompt-style.md +29 -0
- package/skills/ui-system/design-system/mos-design-system.css +240 -0
- package/skills/ui-system/rules/design-system.md +60 -0
- package/skills/ui-system/rules/dual-palette.md +156 -0
- package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
- package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
- package/skills/update/SKILL.md +254 -0
- package/skills/user-needs/SKILL.md +73 -0
- package/skills/validate/SKILL.md +73 -0
- package/skills/value-proposition/SKILL.md +141 -0
- package/skills/vault/SKILL.md +326 -0
- package/skills/visualize/SKILL.md +69 -0
- package/skills/whitespace/SKILL.md +539 -0
- package/skills/wiki/SKILL.md +94 -0
- package/templates/_base.html +20 -0
- package/templates/chat-panel.html +205 -0
- package/templates/constellation-chat-hooks.js +89 -0
- package/templates/constellation.html +1272 -0
- package/templates/destijl-base.css +268 -0
- package/templates/discovery/discovery-brief-template.md +97 -0
- package/templates/due-diligence.html +49 -0
- package/templates/executive-summary.html +40 -0
- package/templates/icm/CLAUDE.md +31 -0
- package/templates/icm/GROUP-CLAUDE.md +33 -0
- package/templates/icm/INDEX.md +26 -0
- package/templates/import/stage-contracts/01-ingest.md +34 -0
- package/templates/import/stage-contracts/02-classify.md +48 -0
- package/templates/import/stage-contracts/03-route.md +42 -0
- package/templates/import/stage-contracts/04-enrich.md +40 -0
- package/templates/investment-thesis.html +25 -0
- package/templates/meeting-report.html +361 -0
- package/templates/presentation/dashboard.html +1150 -0
- package/templates/presentation/deck.html +540 -0
- package/templates/presentation/diagrams.html +437 -0
- package/templates/presentation/graph.html +391 -0
- package/templates/presentation/insights.html +548 -0
- package/templates/presentation/wiki.html +533 -0
- package/templates/profile.html +136 -0
- package/templates/room-context/last-session.md +56 -0
- package/templates/room-context/methodology-history.md +28 -0
- package/templates/room-context/rejection-log.md +25 -0
- package/templates/room-context/weekly-digest.md +54 -0
- package/templates/room-skeleton/MINTO.md.tmpl +19 -0
- package/templates/room-skeleton/ROOM.md.identity.tmpl +13 -0
- package/templates/room-skeleton/ROOM.md.section.tmpl +33 -0
- package/templates/room-skeleton/STATE.md.tmpl +33 -0
- package/templates/room-skeleton/USER.md.tmpl +23 -0
- package/templates/room-skeleton/references/.gitkeep +0 -0
- package/templates/room-skeleton/references/SECTION-SCHEMA.md +213 -0
- package/templates/room-skeleton/references/SUB-SCHEMAS.md +91 -0
- package/templates/room-skeleton/section-contracts/.gitkeep +0 -0
- package/templates/room-skeleton/section-contracts/business-model.md +32 -0
- package/templates/room-skeleton/section-contracts/competitive-analysis.md +33 -0
- package/templates/room-skeleton/section-contracts/financial-model.md +30 -0
- package/templates/room-skeleton/section-contracts/funding.md +41 -0
- package/templates/room-skeleton/section-contracts/legal-ip.md +30 -0
- package/templates/room-skeleton/section-contracts/market-analysis.md +32 -0
- package/templates/room-skeleton/section-contracts/opportunity-bank.md +39 -0
- package/templates/room-skeleton/section-contracts/problem-definition.md +32 -0
- package/templates/room-skeleton/section-contracts/solution-design.md +36 -0
- package/templates/room-skeleton/section-contracts/strategy.md +35 -0
- package/templates/room-skeleton/section-contracts/team-execution.md +36 -0
- package/templates/shared.css +427 -0
- package/templates/showcase/blueprint.html +432 -0
- package/templates/showcase/library.html +571 -0
- package/templates/showcase/narrative.html +361 -0
- package/templates/showcase/overview.html +468 -0
- package/templates/showcase/synthesis.html +473 -0
- package/templates/signature-footer.html +34 -0
|
@@ -0,0 +1,2365 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brain HTTP Client -- calls the Brain HTTP server. Default is
|
|
5
|
+
* `https://theo-mcp.onrender.com`, a BARE origin (no path, no trailing
|
|
6
|
+
* slash): this client appends `/mcp` for tool calls and `/register` for the
|
|
7
|
+
* key ladder, so a `/mcp` suffix here would yield `/mcp/mcp` and a trailing
|
|
8
|
+
* slash a double slash, both 404 and both rendered as "Brain unreachable"
|
|
9
|
+
* rather than a configuration error. Override via the MINDRIAN_BRAIN_URL env
|
|
10
|
+
* var, the per-install rollback lever; that lever (and the fleet-wide
|
|
11
|
+
* patch-release revert of this line) is valid only while the previous
|
|
12
|
+
* origin is still running, because pointing it at a suspended service is a
|
|
13
|
+
* second outage stacked on the first, arriving exactly when the operator is
|
|
14
|
+
* under pressure.
|
|
15
|
+
*
|
|
16
|
+
* Replaces direct MCP tool calls (mcp__neo4j-brain__*, mcp__pinecone-brain__*)
|
|
17
|
+
* with a single HTTP API that handles the graph backend behind one key.
|
|
18
|
+
*
|
|
19
|
+
* Falls back gracefully:
|
|
20
|
+
* 1. If MINDRIAN_BRAIN_KEY is set → calls Brain API
|
|
21
|
+
* 2. If Brain API returns a quota/capacity error → retries once
|
|
22
|
+
* 3. If no key → returns null (Tier 0, no Brain)
|
|
23
|
+
*
|
|
24
|
+
* Usage in commands/skills:
|
|
25
|
+
* const brain = require('./brain-client.cjs');
|
|
26
|
+
* const result = await brain.query('MATCH (f:Framework) RETURN f.name LIMIT 5');
|
|
27
|
+
* const result = await brain.search('innovation framework');
|
|
28
|
+
* const schema = await brain.schema();
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// Phase 339, 2026-09-03 (FLIP-10, D-13 locked): the flip. BARE origin only --
|
|
32
|
+
// no path, no trailing slash -- because `${BRAIN_URL}/mcp` and
|
|
33
|
+
// `${BRAIN_URL}/register` are both formed by appending below. This does NOT
|
|
34
|
+
// change: input arg keys (byte-for-byte unchanged, tests/test-247-contract-
|
|
35
|
+
// client.cjs stays green untouched), the shim server key `mindrian-brain`,
|
|
36
|
+
// BRAIN_TOOL_MATCHER / hooks/hooks.json for the CLI surface, or the
|
|
37
|
+
// brain_query {rows, diagnostics} shape adaptation (already shipped at
|
|
38
|
+
// :927-945, commit 21fdd7bc). MINDRIAN_BRAIN_URL remains the per-install
|
|
39
|
+
// override and the one-line rollback lever.
|
|
40
|
+
const BRAIN_URL = process.env.MINDRIAN_BRAIN_URL || 'https://theo-mcp.onrender.com';
|
|
41
|
+
|
|
42
|
+
// Per-request hard timeout for every Brain HTTP call (init handshake + tool
|
|
43
|
+
// calls). Node's global fetch() has NO default timeout, so without this a
|
|
44
|
+
// slow/wedged Brain hangs the calling /mos: command indefinitely. The Render
|
|
45
|
+
// service answers in ~1-2s normally; 20s is a generous-but-bounded ceiling.
|
|
46
|
+
// Override via MINDRIAN_BRAIN_TIMEOUT_MS (brain-router wants ~2000 for Tier 3).
|
|
47
|
+
const BRAIN_REQUEST_TIMEOUT_MS = Number(process.env.MINDRIAN_BRAIN_TIMEOUT_MS) || 20000;
|
|
48
|
+
|
|
49
|
+
// Phase 250-01 (AVAIL-02, navigator ruling 2026-08-10): bounded transport
|
|
50
|
+
// retry budget around the single HTTP dispatch seam every Brain tool flows
|
|
51
|
+
// through (callTool()'s tools/call POST below). Retries ONLY transport-class
|
|
52
|
+
// outcomes -- network errors and 5xx responses -- BEFORE a blip ever becomes
|
|
53
|
+
// an unreachable refusal. NEVER retries 401/403 (validation-class, mapped to
|
|
54
|
+
// their own sentinels) or any other status; those are zero-retry by design
|
|
55
|
+
// (retrying an auth failure hammers auth, taxonomy-wrong per the data4sci
|
|
56
|
+
// four-class error taxonomy). Defensive numeric-env convention (repo-wide,
|
|
57
|
+
// e.g. BRAIN_REQUEST_TIMEOUT_MS above): an invalid override falls back to
|
|
58
|
+
// the default rather than throwing or disabling the feature silently.
|
|
59
|
+
const RETRY_MAX_DEFAULT = 2; // 2 retries = 3 attempts total.
|
|
60
|
+
const RETRY_BASE_MS_DEFAULT = 300; // 300ms, then 900ms (base * 3^attempt).
|
|
61
|
+
|
|
62
|
+
function _envNonNegativeInt(name, def) {
|
|
63
|
+
const raw = process.env[name];
|
|
64
|
+
if (raw === undefined || raw === '') return def;
|
|
65
|
+
const n = Number(raw);
|
|
66
|
+
return (Number.isFinite(n) && n >= 0) ? Math.floor(n) : def;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function _retryMax() {
|
|
70
|
+
return _envNonNegativeInt('MINDRIAN_BRAIN_RETRY_MAX', RETRY_MAX_DEFAULT);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _retryBaseMs() {
|
|
74
|
+
return _envNonNegativeInt('MINDRIAN_BRAIN_RETRY_BASE_MS', RETRY_BASE_MS_DEFAULT);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _sleep(ms) {
|
|
78
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Phase 259 (TRUST-01, D-01/D-02/D-03): a dedicated retry budget for HTTP
|
|
82
|
+
// 429 (rate limited), separate from AVAIL-02's RETRY_MAX_DEFAULT /
|
|
83
|
+
// RETRY_BASE_MS_DEFAULT above. Deliberately NOT reusing
|
|
84
|
+
// MINDRIAN_BRAIN_RETRY_MAX / MINDRIAN_BRAIN_RETRY_BASE_MS: those are the
|
|
85
|
+
// operator knob for transport blips (network errors, 5xx), and sharing
|
|
86
|
+
// them would let an operator who sets MINDRIAN_BRAIN_RETRY_MAX=0 for
|
|
87
|
+
// fast-fail silently also disable Retry-After honoring, regressing into
|
|
88
|
+
// the exact bug this phase closes. D-01/D-02's schedule (3 retries,
|
|
89
|
+
// 500/1000/2000ms) is also arithmetically distinct from AVAIL-02's (2
|
|
90
|
+
// retries, base * 3^attempt).
|
|
91
|
+
const RATE_LIMIT_RETRY_MAX_DEFAULT = 3; // 3 retries = 4 attempts total (D-01).
|
|
92
|
+
const RATE_LIMIT_BASE_MS_DEFAULT = 500; // 500ms, then 1000ms, then 2000ms (base * 2^attempt) (D-02).
|
|
93
|
+
|
|
94
|
+
function _rateLimitRetryMax() {
|
|
95
|
+
return _envNonNegativeInt('MINDRIAN_BRAIN_RATELIMIT_RETRY_MAX', RATE_LIMIT_RETRY_MAX_DEFAULT);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _rateLimitBaseMs() {
|
|
99
|
+
return _envNonNegativeInt('MINDRIAN_BRAIN_RATELIMIT_BASE_MS', RATE_LIMIT_BASE_MS_DEFAULT);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// OQ-4: an explicit, operator-set, self-declaring ceiling on the wait --
|
|
103
|
+
// UNSET by default, which is what ships D-01 ("honor Retry-After exactly")
|
|
104
|
+
// literally, never a silent Math.min. The Brain's own limiter bounds
|
|
105
|
+
// Retry-After to its window (60s at defaults, verified against
|
|
106
|
+
// ProblemsWorthSolving-Brain src/http/rate-limit.mjs), so an operator
|
|
107
|
+
// only needs this when self-hosting a differently-configured limiter.
|
|
108
|
+
function _rateLimitMaxWaitMs() {
|
|
109
|
+
const raw = process.env.MINDRIAN_BRAIN_RATELIMIT_MAX_WAIT_MS;
|
|
110
|
+
if (raw === undefined || raw === '') return null;
|
|
111
|
+
const n = Number(raw);
|
|
112
|
+
return (Number.isFinite(n) && n >= 0) ? Math.floor(n) : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Phase 259 (TRUST-01): parse a Retry-After header value into milliseconds.
|
|
116
|
+
// Returns null when the header is absent or unusable, meaning "fall back
|
|
117
|
+
// to the D-02 exponential schedule". Handles both RFC 9110 forms: the
|
|
118
|
+
// delay-seconds integer form (the Brain's own live wire shape, verified
|
|
119
|
+
// this session against its deployed limiter) and the HTTP-date form
|
|
120
|
+
// (defensive only -- no known live caller sends it). Never lets NaN, a
|
|
121
|
+
// negative, or Infinity reach _sleep: _sleep(NaN) resolves on the very
|
|
122
|
+
// next tick, which is a silent zero-wait that would hammer the limiter.
|
|
123
|
+
// RFC 7231 IMF-fixdate, the exact shape Date.prototype.toUTCString() emits
|
|
124
|
+
// (e.g. "Wed, 21 Oct 2015 07:28:00 GMT") -- the only HTTP-date form worth
|
|
125
|
+
// trusting. Gating on this BEFORE calling Date.parse matters: V8's Date.parse
|
|
126
|
+
// falls back to a lenient legacy-format heuristic for anything that isn't a
|
|
127
|
+
// recognized standard form, and mis-parses garbage like "-5" or "1.5" into
|
|
128
|
+
// real (wrong) timestamps instead of NaN (verified this session).
|
|
129
|
+
const HTTP_DATE_RE = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
130
|
+
|
|
131
|
+
function _parseRetryAfterMs(raw) {
|
|
132
|
+
if (typeof raw !== 'string') return null;
|
|
133
|
+
const trimmed = raw.trim();
|
|
134
|
+
if (trimmed === '') return null;
|
|
135
|
+
if (/^\d+$/.test(trimmed)) {
|
|
136
|
+
const seconds = Number(trimmed);
|
|
137
|
+
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
138
|
+
return seconds * 1000;
|
|
139
|
+
}
|
|
140
|
+
if (!HTTP_DATE_RE.test(trimmed)) return null;
|
|
141
|
+
const parsedDate = Date.parse(trimmed);
|
|
142
|
+
if (!Number.isFinite(parsedDate)) return null;
|
|
143
|
+
const deltaMs = parsedDate - Date.now();
|
|
144
|
+
return deltaMs > 0 ? deltaMs : 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Phase 259 (TRUST-01): pure wait-schedule function -- no I/O, no clock
|
|
148
|
+
// beyond the Date.now() inside _parseRetryAfterMs. attemptIndex is 0-based
|
|
149
|
+
// over the RETRIES (0 -> first retry, 1 -> second, 2 -> third), matching
|
|
150
|
+
// Pattern 1 in 259-RESEARCH.md.
|
|
151
|
+
function _rateLimitWaitMs(attemptIndex, retryAfterHeader, baseMs) {
|
|
152
|
+
const parsed = _parseRetryAfterMs(retryAfterHeader);
|
|
153
|
+
const computed = parsed !== null ? parsed : baseMs * Math.pow(2, attemptIndex);
|
|
154
|
+
const ceiling = _rateLimitMaxWaitMs();
|
|
155
|
+
if (ceiling !== null && computed > ceiling) return ceiling;
|
|
156
|
+
return computed;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Phase 87-07 (CASCADE-06): Brain session cache with 5-minute TTL.
|
|
160
|
+
// Every callTool() previously re-ran the `initialize` handshake (~1 network
|
|
161
|
+
// round-trip). With a long-lived MCP server this is wasted work -- sessions
|
|
162
|
+
// live longer than the ~60s transport timeout. Cache the initialized
|
|
163
|
+
// sessionId (keyed by api-key-hash) for 5 minutes.
|
|
164
|
+
//
|
|
165
|
+
// R-87-07-RACE (audit): two concurrent callTool() invocations with the same
|
|
166
|
+
// api_key previously both saw a cache miss, both initialized, and the second
|
|
167
|
+
// overwrote the first -- one of the two initialize handshakes was wasted.
|
|
168
|
+
// Fix: cache the init *Promise*, not the resolved session. The first caller
|
|
169
|
+
// stores { promise: initSession(apiKey), expiresAt }; concurrent callers
|
|
170
|
+
// within the TTL `await entry.promise`. On rejection we remove the entry so
|
|
171
|
+
// the next caller re-initializes fresh.
|
|
172
|
+
//
|
|
173
|
+
// Hash: sha256 truncated to 16 hex chars (64 bits of key space, zero realistic
|
|
174
|
+
// collision). A cheaper non-crypto hash was considered but its narrower int
|
|
175
|
+
// space has non-zero collision probability once the design extends across
|
|
176
|
+
// users; sha256 is effectively free at these volumes and eliminates the
|
|
177
|
+
// concern entirely (R-87-07-RACE).
|
|
178
|
+
const crypto = require('node:crypto');
|
|
179
|
+
const fs = require('node:fs');
|
|
180
|
+
const path = require('node:path');
|
|
181
|
+
// Phase 123 Plan-07: getApiKey() delegates to the single Brain-key resolver.
|
|
182
|
+
// The legacy inline 3-path lookup (env -> CWD .env -> ~/.mindrian.env) is gone;
|
|
183
|
+
// the resolver does env -> ~/.mindrian.env -> CWD .env (D-31 order) + SEC-02
|
|
184
|
+
// POSIX permission check + explicit reason strings. See HARNESS-123-15.
|
|
185
|
+
const { resolveBrainKey } = require('./resolve-brain-key.cjs');
|
|
186
|
+
const SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
187
|
+
/** @type {Map<string, {promise: Promise<string>, expiresAt: number}>} */
|
|
188
|
+
const sessionCache = new Map();
|
|
189
|
+
|
|
190
|
+
function _hashKey(key) {
|
|
191
|
+
return crypto.createHash('sha256').update(String(key)).digest('hex').slice(0, 16);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* SEC-01: Sanitize any user-origin string before interpolation into a
|
|
196
|
+
* Cypher query. Whitelist from 87-CONTEXT.md lines 121-127:
|
|
197
|
+
* [a-zA-Z0-9 ._-]
|
|
198
|
+
* Every other char (including `"`, `'`, backtick, newline, `{`, `}`, `$`,
|
|
199
|
+
* `\`, `;`, `/`, `*`) is stripped. Null/undefined return ''. Non-strings
|
|
200
|
+
* are coerced via String() defensively so the caller never crashes.
|
|
201
|
+
*
|
|
202
|
+
* This replaces the legacy single-quote-escape pattern that only
|
|
203
|
+
* escaped one metacharacter (double-quote) and was trivially bypassable
|
|
204
|
+
* via backticks, newlines, `${...}` expansions, or Cypher comments.
|
|
205
|
+
*
|
|
206
|
+
* @param {*} value
|
|
207
|
+
* @returns {string}
|
|
208
|
+
*/
|
|
209
|
+
function sanitizeCypherInput(value) {
|
|
210
|
+
if (value === null || value === undefined) return '';
|
|
211
|
+
if (typeof value !== 'string') {
|
|
212
|
+
try { value = String(value); } catch (_e) { return ''; }
|
|
213
|
+
}
|
|
214
|
+
return value.replace(/[^a-zA-Z0-9 ._-]/g, '');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* SEC-02: Refuse to load a Brain API key from a .env file whose permissions
|
|
219
|
+
* expose it to group or world readers. Unix semantics only -- on Windows
|
|
220
|
+
* POSIX mode bits are not meaningful for NTFS ACLs, so we return true and
|
|
221
|
+
* warn once per process.
|
|
222
|
+
*
|
|
223
|
+
* mode & 0o077 !== 0 => any group/world bit is set => reject
|
|
224
|
+
* mode 0o600 (-rw-------) and 0o400 (-r--------) pass; 0o644, 0o664 fail.
|
|
225
|
+
*
|
|
226
|
+
* On stat failure we return false (no key beats a key we cannot verify).
|
|
227
|
+
*
|
|
228
|
+
* @param {string} envPath
|
|
229
|
+
* @returns {boolean}
|
|
230
|
+
*/
|
|
231
|
+
function checkFilePermissions(envPath) {
|
|
232
|
+
try {
|
|
233
|
+
const fs = require('fs');
|
|
234
|
+
if (process.platform === 'win32') {
|
|
235
|
+
if (!checkFilePermissions._warned) {
|
|
236
|
+
process.stderr.write(
|
|
237
|
+
'[mindrian-os] Note: API key file permission check is Linux/macOS only; '
|
|
238
|
+
+ 'on Windows rely on NTFS ACLs.\n'
|
|
239
|
+
);
|
|
240
|
+
checkFilePermissions._warned = true;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const stat = fs.statSync(envPath);
|
|
245
|
+
if ((stat.mode & 0o077) !== 0) {
|
|
246
|
+
process.stderr.write(
|
|
247
|
+
`[mindrian-os] Refusing to load API key from ${envPath}: `
|
|
248
|
+
+ `permissions too open (must be 0600). chmod 600 ${envPath}\n`
|
|
249
|
+
);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
} catch (_e) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
checkFilePermissions._warned = false;
|
|
258
|
+
|
|
259
|
+
// Phase 123 Plan-07: getApiKey() delegates to lib/core/resolve-brain-key.cjs.
|
|
260
|
+
//
|
|
261
|
+
// Order: MINDRIAN_BRAIN_KEY env -> ~/.mindrian.env -> CWD .env -> not-found.
|
|
262
|
+
// NOTE: this REVERSES the previous CWD-first-then-~/.mindrian.env order.
|
|
263
|
+
// Rationale: the global backup (~/.mindrian.env, mode 0600 per SEC-02) is more
|
|
264
|
+
// trustworthy than a project's potentially-stale .env file. Cited: Phase 123
|
|
265
|
+
// D-31. The resolver returns { key, source, available, reason }; we surface
|
|
266
|
+
// the key (or null) here and log a non-null reason ONCE per process via
|
|
267
|
+
// console.error -- SEC-02 group/world-bit rejection routes through this
|
|
268
|
+
// channel, never as a silent null.
|
|
269
|
+
//
|
|
270
|
+
// The legacy inline 3-path lookup and the per-file checkFilePermissions gate
|
|
271
|
+
// previously inlined here are gone -- the resolver owns both responsibilities
|
|
272
|
+
// now. checkFilePermissions remains exported on _test for backward-compat
|
|
273
|
+
// with security-trifecta.test.cjs (the helper itself still works locally;
|
|
274
|
+
// it's just not called by getApiKey anymore).
|
|
275
|
+
let _memoizedKey = null;
|
|
276
|
+
let _memoizedAt = 0;
|
|
277
|
+
let _reasonLoggedThisProcess = false;
|
|
278
|
+
const _GETKEY_MEMO_MS = 60 * 1000;
|
|
279
|
+
function getApiKey() {
|
|
280
|
+
if (_memoizedAt && (Date.now() - _memoizedAt) < _GETKEY_MEMO_MS) {
|
|
281
|
+
return _memoizedKey;
|
|
282
|
+
}
|
|
283
|
+
const r = resolveBrainKey();
|
|
284
|
+
if (r && r.available) {
|
|
285
|
+
_memoizedKey = r.key;
|
|
286
|
+
_memoizedAt = Date.now();
|
|
287
|
+
return _memoizedKey;
|
|
288
|
+
}
|
|
289
|
+
if (r && r.reason && !_reasonLoggedThisProcess) {
|
|
290
|
+
// SEC-02 reject + not-found-with-reason both route through stderr ONCE
|
|
291
|
+
// per process -- never a silent null. The session-start status line is
|
|
292
|
+
// the user-visible surface; this is the in-process diagnostic.
|
|
293
|
+
process.stderr.write('[mindrian-os] Brain key not loaded: ' + r.reason + '\n');
|
|
294
|
+
_reasonLoggedThisProcess = true;
|
|
295
|
+
}
|
|
296
|
+
_memoizedKey = null;
|
|
297
|
+
_memoizedAt = Date.now();
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Check if Brain is available (key exists).
|
|
303
|
+
*/
|
|
304
|
+
function isAvailable() {
|
|
305
|
+
return !!getApiKey();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// Phase 250-04 (HONEST-03, SEED-011 Option A) -- per-install silent
|
|
310
|
+
// registration. Design doc: docs/BRAIN-IDENTITY-DESIGN.md.
|
|
311
|
+
//
|
|
312
|
+
// The ladder's fourth leg (resolve-brain-key.cjs, read-only) resolves an
|
|
313
|
+
// existing cached install token; THIS module owns the minting side. When the
|
|
314
|
+
// ladder resolves nothing at the first Brain consult, mint a fresh UUID,
|
|
315
|
+
// POST it to the Brain's /register endpoint, cache the returned token at
|
|
316
|
+
// mode 0600, and let the SAME process's next getApiKey() call pick it up.
|
|
317
|
+
//
|
|
318
|
+
// Once-per-process cap: exactly ONE registration attempt per process,
|
|
319
|
+
// success or failure. A failed attempt is the failure edge (registration
|
|
320
|
+
// failed / Brain offline) -- it is NEVER retried within the process (the
|
|
321
|
+
// AVAIL-02 bounded-retry budget explicitly does not apply here; a fresh
|
|
322
|
+
// process retries fresh on its own next launch). NEVER thrown into the
|
|
323
|
+
// caller; NEVER blocks a non-methodology path (this only runs from the
|
|
324
|
+
// Brain-consult chokepoint below, never from session-start or a hot path).
|
|
325
|
+
let _autoRegisterAttemptedThisProcess = false;
|
|
326
|
+
let _autoRegisterFailureReason = null;
|
|
327
|
+
|
|
328
|
+
function _installTokenPath(home) {
|
|
329
|
+
const h = home || process.env.HOME || process.env.USERPROFILE || require('node:os').homedir();
|
|
330
|
+
return path.join(h, '.mindrian-install.json');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Attempt the one-shot silent registration. Returns the minted key string on
|
|
335
|
+
* success, or null (opt-out, already-attempted, or any failure). On success,
|
|
336
|
+
* updates the module's own memoized key so the CALLER's next getApiKey()
|
|
337
|
+
* returns it immediately (no redundant resolver re-read needed, though a
|
|
338
|
+
* fresh resolveBrainKey() call would ALSO see the cache file now).
|
|
339
|
+
*
|
|
340
|
+
* @returns {Promise<string|null>}
|
|
341
|
+
*/
|
|
342
|
+
async function _tryAutoRegister() {
|
|
343
|
+
if (_autoRegisterAttemptedThisProcess) return null;
|
|
344
|
+
_autoRegisterAttemptedThisProcess = true;
|
|
345
|
+
|
|
346
|
+
if (process.env.MINDRIAN_DISABLE_AUTO_REGISTER) {
|
|
347
|
+
_autoRegisterFailureReason = 'auto-registration disabled (MINDRIAN_DISABLE_AUTO_REGISTER set)';
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const installId = crypto.randomUUID();
|
|
353
|
+
const res = await fetch(`${BRAIN_URL}/register`, {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
signal: AbortSignal.timeout(BRAIN_REQUEST_TIMEOUT_MS),
|
|
356
|
+
headers: { 'Content-Type': 'application/json' },
|
|
357
|
+
// Part 8 posture (T-250-13): install_id ONLY ever crosses in this body.
|
|
358
|
+
body: JSON.stringify({ install_id: installId }),
|
|
359
|
+
});
|
|
360
|
+
if (!res.ok) {
|
|
361
|
+
try { await res.arrayBuffer(); } catch (_e) { /* drain, see _ensureSession's precedent */ }
|
|
362
|
+
_autoRegisterFailureReason = 'registration failed (HTTP ' + res.status + ', offline or unreachable)';
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
const body = await res.json();
|
|
366
|
+
if (!body || typeof body.token !== 'string' || body.token.length === 0) {
|
|
367
|
+
_autoRegisterFailureReason = 'registration returned a malformed response (offline or unreachable)';
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const cachePath = _installTokenPath();
|
|
372
|
+
const payload = { install_id: installId, token: body.token, minted_at: new Date().toISOString() };
|
|
373
|
+
fs.writeFileSync(cachePath, JSON.stringify(payload, null, 2) + '\n', { mode: 0o600 });
|
|
374
|
+
// writeFileSync's mode option only applies on file CREATION; force it
|
|
375
|
+
// explicitly so a pre-existing looser-permission file is corrected too
|
|
376
|
+
// (SEC-02 posture, mirrors resolve-brain-key.cjs's own gate on this file).
|
|
377
|
+
try { fs.chmodSync(cachePath, 0o600); } catch (_e) { /* best-effort on platforms without chmod semantics */ }
|
|
378
|
+
|
|
379
|
+
_memoizedKey = body.token;
|
|
380
|
+
_memoizedAt = Date.now();
|
|
381
|
+
return body.token;
|
|
382
|
+
} catch (e) {
|
|
383
|
+
_autoRegisterFailureReason = 'registration failed: ' + (e && e.message || String(e)) + ' (offline or unreachable)';
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Async-aware availability gate: the "first Brain consult" seam every
|
|
390
|
+
* MCP-surface entry point passes through before dispatching a tool
|
|
391
|
+
* (bin/mindrian-brain-mcp-client.cjs's per-tool gates use this INSTEAD of
|
|
392
|
+
* the synchronous isAvailable(), so silent registration actually fires on
|
|
393
|
+
* that path -- isAvailable() alone cannot attempt an async network mint).
|
|
394
|
+
* Direct brain-client consumers (query/ask/schema/... via callTool) do not
|
|
395
|
+
* need to call this explicitly -- callTool() below performs the identical
|
|
396
|
+
* fallback internally, so BOTH surfaces share the SAME once-per-process cap.
|
|
397
|
+
*
|
|
398
|
+
* @returns {Promise<boolean>}
|
|
399
|
+
*/
|
|
400
|
+
async function ensureAvailable() {
|
|
401
|
+
if (isAvailable()) return true;
|
|
402
|
+
await _tryAutoRegister();
|
|
403
|
+
return isAvailable();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The honest reason the last auto-registration attempt failed (or the
|
|
408
|
+
* opt-out reason), or null if no attempt has failed this process (either
|
|
409
|
+
* none was attempted yet, or the last attempt succeeded). Consumed by the
|
|
410
|
+
* no_key failure-edge refusal copy (refusal-messaging.cjs) so the reason names
|
|
411
|
+
* "registration" / "offline" rather than a generic "no key" framing.
|
|
412
|
+
*
|
|
413
|
+
* @returns {string|null}
|
|
414
|
+
*/
|
|
415
|
+
function getAutoRegisterFailureReason() {
|
|
416
|
+
return _autoRegisterFailureReason;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Phase 87-07: ensure we have a valid initialized Brain session for the given
|
|
421
|
+
* api key, reusing the cached one if non-expired. Uses the pending-promise
|
|
422
|
+
* pattern so concurrent callers share a single in-flight init (R-87-07-RACE).
|
|
423
|
+
*
|
|
424
|
+
* Returns the resolved session marker (an opaque string -- the Brain Streamable
|
|
425
|
+
* HTTP transport does not require us to echo a sessionId on subsequent requests
|
|
426
|
+
* inside the same cache window, but awaiting this promise proves the key is
|
|
427
|
+
* valid against the Brain endpoint exactly once per TTL window).
|
|
428
|
+
*
|
|
429
|
+
* On any init rejection (network error, 401, etc.) the cache entry is removed
|
|
430
|
+
* in the .catch() tail so the next caller retries fresh rather than inheriting
|
|
431
|
+
* a poisoned promise.
|
|
432
|
+
*
|
|
433
|
+
* Sentinel `{ error: 'invalid_key' }` is returned *through* the promise (not
|
|
434
|
+
* thrown) so callers treat 401 identically to the pre-cache flow.
|
|
435
|
+
*
|
|
436
|
+
* @param {string} apiKey
|
|
437
|
+
* @returns {Promise<string|{error:string,message:string}|null>}
|
|
438
|
+
*/
|
|
439
|
+
async function _ensureSession(apiKey) {
|
|
440
|
+
const keyHash = _hashKey(apiKey);
|
|
441
|
+
const cached = sessionCache.get(keyHash);
|
|
442
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
443
|
+
// Cache hit. Works whether the promise is still pending (concurrent init
|
|
444
|
+
// in flight) or already resolved (TTL reuse). Awaiting a resolved promise
|
|
445
|
+
// is a microtask no-op, so the fast path stays fast.
|
|
446
|
+
return cached.promise;
|
|
447
|
+
}
|
|
448
|
+
// Cache miss. Build the promise FIRST, install it in the cache BEFORE the
|
|
449
|
+
// first real await, so concurrent callers within the same event-loop tick
|
|
450
|
+
// see the same in-flight promise (R-87-07-RACE pending-promise pattern).
|
|
451
|
+
const promise = (async () => {
|
|
452
|
+
const initRes = await fetch(`${BRAIN_URL}/mcp`, {
|
|
453
|
+
method: 'POST',
|
|
454
|
+
signal: AbortSignal.timeout(BRAIN_REQUEST_TIMEOUT_MS),
|
|
455
|
+
headers: {
|
|
456
|
+
'Content-Type': 'application/json',
|
|
457
|
+
'Accept': 'application/json, text/event-stream',
|
|
458
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
459
|
+
},
|
|
460
|
+
body: JSON.stringify({
|
|
461
|
+
jsonrpc: '2.0',
|
|
462
|
+
id: 1,
|
|
463
|
+
method: 'initialize',
|
|
464
|
+
params: {
|
|
465
|
+
protocolVersion: '2024-11-05',
|
|
466
|
+
capabilities: {},
|
|
467
|
+
clientInfo: { name: 'mindrian-cli', version: '1.0.0' },
|
|
468
|
+
},
|
|
469
|
+
}),
|
|
470
|
+
});
|
|
471
|
+
if (!initRes.ok) {
|
|
472
|
+
// Debug session doctor-brain-smoke-win-crash (2026-05-30): drain the
|
|
473
|
+
// response body on the non-OK path. Node global fetch (undici) keeps the
|
|
474
|
+
// underlying TLS socket in a keep-alive, not-yet-released state until its
|
|
475
|
+
// body is consumed. An un-drained socket is a live libuv handle at exit;
|
|
476
|
+
// a synchronous process.exit() in a caller tears it down mid-close and
|
|
477
|
+
// asserts on Windows (src/win/async.c UV_HANDLE_CLOSING). Consuming the
|
|
478
|
+
// body lets undici release/recycle the socket cleanly. Cross-platform.
|
|
479
|
+
try { await initRes.arrayBuffer(); } catch (_) { /* body already gone */ }
|
|
480
|
+
if (initRes.status === 401) {
|
|
481
|
+
return { error: 'invalid_key', message: 'Brain API key is invalid.' };
|
|
482
|
+
}
|
|
483
|
+
// Any other non-OK status becomes a throw so the cache entry is purged
|
|
484
|
+
// by the .catch() below and the next caller retries.
|
|
485
|
+
throw new Error(`Brain init HTTP ${initRes.status}`);
|
|
486
|
+
}
|
|
487
|
+
// Opaque session marker. Subsequent tools/call requests don't need to
|
|
488
|
+
// echo this back -- the transport is stateless at the HTTP level. What
|
|
489
|
+
// matters is that we validated the key is live within this TTL window.
|
|
490
|
+
return 'validated-' + Date.now();
|
|
491
|
+
})();
|
|
492
|
+
sessionCache.set(keyHash, { promise, expiresAt: Date.now() + SESSION_TTL_MS });
|
|
493
|
+
// On reject, purge the entry so the next caller initializes fresh. Swallow
|
|
494
|
+
// here (we re-throw in the awaiter below) so Node doesn't see an
|
|
495
|
+
// unhandledRejection on the cache handle itself.
|
|
496
|
+
promise.catch(() => { sessionCache.delete(keyHash); });
|
|
497
|
+
return promise;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Phase 254 / COMP-02 (D-02 Option A): attach an additive `egress_disclosure`
|
|
502
|
+
* field to a callTool() success return when the Part 8 belt captured an
|
|
503
|
+
* `ambiguous` verdict for this call. Module-private -- used only at
|
|
504
|
+
* callTool()'s three success-return sites below.
|
|
505
|
+
*
|
|
506
|
+
* Returns `result` UNCHANGED when `disclosure` is falsy, when `result` is
|
|
507
|
+
* not a non-null plain object, or when `result` already carries an own
|
|
508
|
+
* `error` property (so `tier_denied` / `invalid_key` / `rate_limited` /
|
|
509
|
+
* `egress_blocked` sentinels are never decorated -- those are refusals, not
|
|
510
|
+
* success returns). Never throws: the assignment is wrapped so a frozen or
|
|
511
|
+
* exotic result object degrades to the undecorated value rather than
|
|
512
|
+
* crashing a Brain call.
|
|
513
|
+
*
|
|
514
|
+
* COPY-ON-ATTACH FOR PLAIN OBJECTS, MUTATE FOR ARRAYS (WR-02, 254-REVIEW.md):
|
|
515
|
+
* for a plain object, this returns a NEW shallow-copied object rather than
|
|
516
|
+
* mutating `result` in place -- every call site hands in a value freshly
|
|
517
|
+
* parsed via `JSON.parse` two lines above, so no other reference exists to
|
|
518
|
+
* preserve, and copy-on-attach closes the "same reference silently gains a
|
|
519
|
+
* field" hazard at zero real cost. For an ARRAY, mutation is structurally
|
|
520
|
+
* required: `callTool`'s contract for `brain_query` must stay a bare array
|
|
521
|
+
* per the 2026-05-11 hotfix note, so wrapping it in a new object here would
|
|
522
|
+
* break the `Array.isArray(result)` shape every `brain_query` consumer keys
|
|
523
|
+
* on. The disclosure is therefore attached as a non-index own property of
|
|
524
|
+
* the SAME array reference -- this is load-bearing for `query()`'s own
|
|
525
|
+
* `Array.isArray(result)` normalization branch below, which reads this
|
|
526
|
+
* property off the array and copies + deletes it onto the new top-level
|
|
527
|
+
* object it constructs (CR-01, 254-REVIEW.md). Any OTHER caller reading a
|
|
528
|
+
* bare array off `callTool('brain_query', ...)` directly (bypassing
|
|
529
|
+
* `query()`) must do the same: check `result.egress_disclosure` before
|
|
530
|
+
* treating `result` as a plain row array, since `JSON.stringify` silently
|
|
531
|
+
* drops non-index array properties.
|
|
532
|
+
*
|
|
533
|
+
* @param {*} result
|
|
534
|
+
* @param {object|null} disclosure
|
|
535
|
+
* @returns {*}
|
|
536
|
+
*/
|
|
537
|
+
function _attachEgressDisclosure(result, disclosure) {
|
|
538
|
+
if (!disclosure) return result;
|
|
539
|
+
if (result === null || typeof result !== 'object') return result;
|
|
540
|
+
if (Object.prototype.hasOwnProperty.call(result, 'error')) return result;
|
|
541
|
+
try {
|
|
542
|
+
if (Array.isArray(result)) {
|
|
543
|
+
result.egress_disclosure = disclosure;
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
546
|
+
return Object.assign({}, result, { egress_disclosure: disclosure });
|
|
547
|
+
} catch (_e) {
|
|
548
|
+
// Frozen or exotic result object -- degrade to the undecorated value. A
|
|
549
|
+
// belt that can crash a Brain call is worse than the gap it closes.
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Call a Brain MCP tool via HTTP.
|
|
556
|
+
* @param {string} toolName - e.g., 'brain_query', 'brain_search', 'brain_schema'
|
|
557
|
+
* @param {object} args - tool arguments
|
|
558
|
+
* @returns {object|null} - result or null if unavailable
|
|
559
|
+
*/
|
|
560
|
+
async function callTool(toolName, args) {
|
|
561
|
+
let key = getApiKey();
|
|
562
|
+
if (!key) {
|
|
563
|
+
// Phase 250-04: the ladder resolved nothing -- this IS the first Brain
|
|
564
|
+
// consult. Attempt the one-shot silent registration before giving up;
|
|
565
|
+
// shares the same _autoRegisterAttemptedThisProcess cap as
|
|
566
|
+
// ensureAvailable() (the shim's gate), so whichever surface consults
|
|
567
|
+
// first performs the real network attempt and the other reuses it.
|
|
568
|
+
key = await _tryAutoRegister();
|
|
569
|
+
}
|
|
570
|
+
if (!key) return null;
|
|
571
|
+
|
|
572
|
+
// Quick 260819-c8j (WS-D2): the Part 8 in-process classify() belt at the
|
|
573
|
+
// single dispatch seam every callTool() caller flows through. Placement is
|
|
574
|
+
// load-bearing: AFTER the key gate above (so the no-key `null` contract
|
|
575
|
+
// stays byte-unchanged) and BEFORE _ensureSession below (so a blocked
|
|
576
|
+
// payload opens no socket at all -- not even a session init).
|
|
577
|
+
//
|
|
578
|
+
// MECHANISM CHOICE: one guard in one function covers all 16 wrappers,
|
|
579
|
+
// versus ~12 near-identical per-wrapper edits that drift the moment a 17th
|
|
580
|
+
// wrapper lands. Double-classification is NOT double-prompting --
|
|
581
|
+
// classify() returns a verdict, it does not elicit. The 4 existing
|
|
582
|
+
// raw-field guards (query() :601, hatAwareRecommend() :942,
|
|
583
|
+
// suggestValidationSteps() :1056, sendPacket() :1643) are NOT removed and
|
|
584
|
+
// NOT weakened by this belt: their own docblocks prove why the belt cannot
|
|
585
|
+
// replace them (classifying the ASSEMBLED template string instead of the
|
|
586
|
+
// raw field lets the template's own word "Framework" launder an embedded
|
|
587
|
+
// canary from ambiguous to allow, and sanitizeCypherInput strips the '@'
|
|
588
|
+
// the PII pattern keys on) -- this is a belt UNDERNEATH them, never a
|
|
589
|
+
// substitute. An 'ambiguous' verdict never blocks and never prompts, the
|
|
590
|
+
// same posture sendPacket()'s belt already takes at :1643 in spirit.
|
|
591
|
+
//
|
|
592
|
+
// `egress_blocked` is a sentinel OBJECT, not `null`: `null` is the
|
|
593
|
+
// test-pinned transport-failure signal across ~82 degradation tests, and
|
|
594
|
+
// conflating a constitutional refusal with an outage would make the
|
|
595
|
+
// refusal invisible. The sentinel-object idiom is already established by
|
|
596
|
+
// `tier_denied` (:497 area) and `invalid_key` above.
|
|
597
|
+
//
|
|
598
|
+
// Phase 254 / COMP-02 (D-02 Option A, navigator-ruled 2026-09-02):
|
|
599
|
+
// `ambiguous` now DISCLOSES and still PROCEEDS. `ambiguousDisclosure`
|
|
600
|
+
// (declared below, function-scoped so it survives into the retry loop and
|
|
601
|
+
// the SSE parse) captures the typed disclosure ADDITIVELY -- borrowing the
|
|
602
|
+
// idiom `brain-router.cjs:414-422` already uses for `brain_refusal`
|
|
603
|
+
// (an additive field, best-effort, never blocking), not the function
|
|
604
|
+
// itself. Option B (fail-closed on `ambiguous` too) was considered and
|
|
605
|
+
// explicitly rejected for this phase: it would change the behaviour of all
|
|
606
|
+
// 16 `callTool` wrappers at once and needs its own canary suite, out of
|
|
607
|
+
// this phase's scope. A Shape F.1 gate belongs at the MCP handler
|
|
608
|
+
// (`lib/mcp/gate-render.cjs::renderGate`) and never at this transport
|
|
609
|
+
// chokepoint -- inventing an elicitation path inside a stateless server
|
|
610
|
+
// handler is exactly what Option A avoids. The disclosure names the
|
|
611
|
+
// verdict class and the tool; it never carries payload bytes (every
|
|
612
|
+
// `ambiguous`-branch `reason` string in `part8-egress-guard.cjs` is a
|
|
613
|
+
// constant literal, unlike the `block` branch's reason which embeds the
|
|
614
|
+
// matched pattern -- this path never reaches that branch).
|
|
615
|
+
//
|
|
616
|
+
// The host PreToolUse hook (`scripts/part8-egress-guard-hook.cjs`) still
|
|
617
|
+
// gates `ambiguous` with a real Shape F.1 render on the model-issued path.
|
|
618
|
+
// The two enforcement points now DIVERGE BY A STATED DECISION rather than
|
|
619
|
+
// by omission -- COMP-02's literal wording.
|
|
620
|
+
let ambiguousDisclosure = null;
|
|
621
|
+
try {
|
|
622
|
+
const callToolEgressGuard = require('./part8-egress-guard.cjs');
|
|
623
|
+
const callToolEgressVerdict = callToolEgressGuard.classify(args, { toolName: toolName });
|
|
624
|
+
if (callToolEgressVerdict && callToolEgressVerdict.verdict === 'block') {
|
|
625
|
+
return {
|
|
626
|
+
error: 'egress_blocked',
|
|
627
|
+
tool: toolName,
|
|
628
|
+
egress_class: callToolEgressVerdict.class || 'content_set',
|
|
629
|
+
};
|
|
630
|
+
} else if (callToolEgressVerdict && callToolEgressVerdict.verdict === 'ambiguous') {
|
|
631
|
+
ambiguousDisclosure = {
|
|
632
|
+
verdict: 'ambiguous',
|
|
633
|
+
egress_class: callToolEgressVerdict.class || 'unknown',
|
|
634
|
+
reason: callToolEgressVerdict.reason || null,
|
|
635
|
+
tool: toolName,
|
|
636
|
+
disposition: 'proceeded',
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
} catch (_e) {
|
|
640
|
+
// Belt-internal error (guard module missing/throws): degrade to existing
|
|
641
|
+
// behavior. This belt is a belt, not the primary control.
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
// Phase 87-07: reuse cached Brain session (5-min TTL) instead of
|
|
646
|
+
// re-running initialize on every callTool. Concurrent callers share
|
|
647
|
+
// the in-flight promise via the pending-promise pattern.
|
|
648
|
+
const session = await _ensureSession(key);
|
|
649
|
+
if (session && typeof session === 'object' && session.error === 'invalid_key') {
|
|
650
|
+
return session;
|
|
651
|
+
}
|
|
652
|
+
if (!session) return null;
|
|
653
|
+
|
|
654
|
+
// Phase 250-01 (AVAIL-02): bounded retry loop around the tools/call
|
|
655
|
+
// dispatch -- the single HTTP seam every Brain tool flows through after
|
|
656
|
+
// session establishment. NULL CONTRACT PRESERVED: the retry changes WHEN
|
|
657
|
+
// null returns (after the budget instead of after one attempt), never
|
|
658
|
+
// WHAT returns null, and never which statuses map to which sentinels
|
|
659
|
+
// (247-02 do-not-widen note; 82 degradation tests key on this).
|
|
660
|
+
const retryMax = _retryMax();
|
|
661
|
+
const baseMs = _retryBaseMs();
|
|
662
|
+
// Phase 259 (TRUST-01): a SEPARATE counter from the loop's `attempt`,
|
|
663
|
+
// so a 5xx blip earlier in this same callTool invocation cannot eat
|
|
664
|
+
// into the 429 budget, and vice versa. Declared outside the `for` so
|
|
665
|
+
// it survives iterations.
|
|
666
|
+
let rlAttempt = 0;
|
|
667
|
+
let rlTruncated = false; // OQ-4: set when MINDRIAN_BRAIN_RATELIMIT_MAX_WAIT_MS truncated a wait this call.
|
|
668
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
669
|
+
let toolRes;
|
|
670
|
+
try {
|
|
671
|
+
toolRes = await fetch(`${BRAIN_URL}/mcp`, {
|
|
672
|
+
method: 'POST',
|
|
673
|
+
signal: AbortSignal.timeout(BRAIN_REQUEST_TIMEOUT_MS),
|
|
674
|
+
headers: {
|
|
675
|
+
'Content-Type': 'application/json',
|
|
676
|
+
'Accept': 'application/json, text/event-stream',
|
|
677
|
+
'Authorization': `Bearer ${key}`,
|
|
678
|
+
},
|
|
679
|
+
body: JSON.stringify({
|
|
680
|
+
jsonrpc: '2.0',
|
|
681
|
+
id: 2,
|
|
682
|
+
method: 'tools/call',
|
|
683
|
+
params: { name: toolName, arguments: args },
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
} catch (fetchErr) {
|
|
687
|
+
// Network error / timeout at the tools/call seam -- transport-class,
|
|
688
|
+
// retryable. Every other thrown error (outside this inner try) still
|
|
689
|
+
// falls through to the outer catch below, unchanged.
|
|
690
|
+
if (attempt < retryMax) {
|
|
691
|
+
await _sleep(baseMs * Math.pow(3, attempt));
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (!toolRes.ok) {
|
|
698
|
+
// Phase 247-02 (CONTRACT-01 error semantics): HTTP 403 means the Brain's
|
|
699
|
+
// tier gate refused this tool on this key (a MoatViolation), NOT that the
|
|
700
|
+
// Brain is unreachable. Mirrors the invalid_key sentinel precedent in
|
|
701
|
+
// _ensureSession above -- a plain object returned through the promise,
|
|
702
|
+
// never thrown, so callers that already understand sentinel passthrough
|
|
703
|
+
// (query()'s {error:...} passthrough is the existing example) keep
|
|
704
|
+
// working unchanged. 401/403 are validation-class -- ZERO retry, ever
|
|
705
|
+
// (AVAIL-02: retrying an auth failure hammers auth, taxonomy-wrong).
|
|
706
|
+
if (toolRes.status === 403) {
|
|
707
|
+
let rawText = '';
|
|
708
|
+
try { rawText = await toolRes.text(); } catch (_e) { rawText = ''; }
|
|
709
|
+
let message = null;
|
|
710
|
+
try {
|
|
711
|
+
const parsedBody = JSON.parse(rawText);
|
|
712
|
+
if (parsedBody && parsedBody.error && typeof parsedBody.error.message === 'string') {
|
|
713
|
+
message = parsedBody.error.message;
|
|
714
|
+
}
|
|
715
|
+
} catch (_e) {
|
|
716
|
+
// Unparseable body -- fall through to the raw-text fallback below.
|
|
717
|
+
}
|
|
718
|
+
if (!message) {
|
|
719
|
+
message = rawText
|
|
720
|
+
? rawText.slice(0, 300)
|
|
721
|
+
: 'Brain denied tier access (403, no body).';
|
|
722
|
+
}
|
|
723
|
+
return { error: 'tier_denied', tool: toolName, message: message };
|
|
724
|
+
}
|
|
725
|
+
// Phase 259 (TRUST-01): 429 previously fell through the generic
|
|
726
|
+
// non-OK ladder into the bare `return null` at the bottom of this
|
|
727
|
+
// block, which the MCP shim renders as BRAIN_UNREACHABLE with copy
|
|
728
|
+
// claiming a retry budget was spent -- factually false, since zero
|
|
729
|
+
// retries ran on that leg. Verified wire contract (this session,
|
|
730
|
+
// ProblemsWorthSolving-Brain src/http/rate-limit.mjs::
|
|
731
|
+
// perKeyRateLimit): 120 requests per 60s per key at defaults,
|
|
732
|
+
// Retry-After ALWAYS set as an integer delay-seconds string,
|
|
733
|
+
// floored at 1 and bounded above by the window. D-01: honor it
|
|
734
|
+
// exactly. D-02: fall back to 500/1000/2000ms when absent. D-03:
|
|
735
|
+
// after the budget exhausts, return a distinct rate_limited
|
|
736
|
+
// sentinel, never null.
|
|
737
|
+
if (toolRes.status === 429) {
|
|
738
|
+
const retryAfterRaw = toolRes.headers.get('retry-after');
|
|
739
|
+
// Drain this branch's own body before bailing/continuing, exactly
|
|
740
|
+
// as the 403 block above consumes its own body (an un-drained
|
|
741
|
+
// undici socket is a live libuv handle that asserts on Windows).
|
|
742
|
+
try { await toolRes.arrayBuffer(); } catch (_e) { /* body already gone */ }
|
|
743
|
+
if (rlAttempt < _rateLimitRetryMax()) {
|
|
744
|
+
const ceilingMsForWait = _rateLimitMaxWaitMs();
|
|
745
|
+
const computedWaitMs = _rateLimitWaitMs(rlAttempt, retryAfterRaw, _rateLimitBaseMs());
|
|
746
|
+
if (ceilingMsForWait !== null && computedWaitMs >= ceilingMsForWait) rlTruncated = true;
|
|
747
|
+
await _sleep(computedWaitMs);
|
|
748
|
+
rlAttempt += 1;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const parsedRetryAfterMs = _parseRetryAfterMs(retryAfterRaw);
|
|
752
|
+
const retryAfterS = parsedRetryAfterMs !== null ? Math.ceil(parsedRetryAfterMs / 1000) : null;
|
|
753
|
+
const ceilingMs = _rateLimitMaxWaitMs();
|
|
754
|
+
let message = `Brain rate-limited ${toolName} after ${rlAttempt + 1} attempts (429).`;
|
|
755
|
+
if (rlTruncated && ceilingMs !== null) {
|
|
756
|
+
message += ` At least one Retry-After wait was truncated to the operator-set ceiling of ${ceilingMs}ms.`;
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
error: 'rate_limited',
|
|
760
|
+
tool: toolName,
|
|
761
|
+
retry_after_s: retryAfterS,
|
|
762
|
+
attempts: rlAttempt + 1,
|
|
763
|
+
message: message.slice(0, 300),
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
// Drain the body before bailing so undici releases the keep-alive socket
|
|
767
|
+
// (see _ensureSession non-OK note: an un-drained socket is a live libuv
|
|
768
|
+
// handle that asserts on Windows when a caller force-exits).
|
|
769
|
+
try { await toolRes.arrayBuffer(); } catch (_) { /* body already gone */ }
|
|
770
|
+
// 5xx is transport-class (transient) -- retryable within budget.
|
|
771
|
+
// Every OTHER non-OK, non-retried status still returns null -- that
|
|
772
|
+
// remains the sole transport-failure signal (research Pitfall 4: 82
|
|
773
|
+
// degradation tests key on it; do not widen this branch).
|
|
774
|
+
if (toolRes.status >= 500 && toolRes.status < 600 && attempt < retryMax) {
|
|
775
|
+
await _sleep(baseMs * Math.pow(3, attempt));
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const text = await toolRes.text();
|
|
782
|
+
// Parse SSE response
|
|
783
|
+
const dataLine = text.split('\n').find(l => l.startsWith('data: '));
|
|
784
|
+
if (!dataLine) return null;
|
|
785
|
+
|
|
786
|
+
const parsed = JSON.parse(dataLine.slice(6));
|
|
787
|
+
if (parsed.result && parsed.result.content) {
|
|
788
|
+
const textContent = parsed.result.content.find(c => c.type === 'text');
|
|
789
|
+
if (textContent) {
|
|
790
|
+
try {
|
|
791
|
+
return _attachEgressDisclosure(JSON.parse(textContent.text), ambiguousDisclosure);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
return _attachEgressDisclosure({ text: textContent.text }, ambiguousDisclosure);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// The helper's own non-object guard already handles the null case
|
|
798
|
+
// here (parsed.result || null); wrapped anyway so the intent is
|
|
799
|
+
// legible at the call site rather than relying on the guard alone.
|
|
800
|
+
return _attachEgressDisclosure(parsed.result || null, ambiguousDisclosure);
|
|
801
|
+
}
|
|
802
|
+
} catch (err) {
|
|
803
|
+
// Network error, timeout, etc. (outside the retry loop -- e.g. during
|
|
804
|
+
// _ensureSession).
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Query Neo4j via Brain (Cypher query).
|
|
811
|
+
* This does NOT use Pinecone, no embedding quota consumed.
|
|
812
|
+
*
|
|
813
|
+
* NOTE (Finding I, v1.10.9 hotfix 2026-04-15): the Brain MCP brain_query
|
|
814
|
+
* tool expects the parameter name `cypher`, not `query`. Previously this
|
|
815
|
+
* function sent { query: cypher } which tripped an MCP input validation
|
|
816
|
+
* error (code -32602, path ["cypher"], "Required"). Downstream scripts
|
|
817
|
+
* like fetch-brain-baseline.cjs and compute-whitespace-gaps.py then
|
|
818
|
+
* silently fell through to empty-baseline mode even though Brain was
|
|
819
|
+
* fully reachable and the key was valid. Witnessed against the live
|
|
820
|
+
* iia-deeptech-centers room on 2026-04-15. brain_search uses `query`
|
|
821
|
+
* which is why Pinecone semantic search kept working and masked this.
|
|
822
|
+
*
|
|
823
|
+
* NOTE (2026-05-11, graph-on-graph P0): `query` now accepts an optional
|
|
824
|
+
* second argument `params` and forwards it to the `brain_query` MCP tool
|
|
825
|
+
* as { cypher, params }. The Brain tool declares `params:
|
|
826
|
+
* z.record(z.any()).optional()`, so a parameterized Cypher gets its
|
|
827
|
+
* bindings through cleanly. `params` MUST be a generic-handles-only object
|
|
828
|
+
* -- framework names, phase identifiers, problem types per Canon Part 8 --
|
|
829
|
+
* NEVER user content (artifact bodies, meeting text, personal identifiers,
|
|
830
|
+
* proprietary numbers). Previously the second arg was silently dropped, so
|
|
831
|
+
* callers (rs-explain-command.cjs, rs-thesis-command.cjs, rs-nl-to-query)
|
|
832
|
+
* that generated parameterized Cypher had their bindings disappear or were
|
|
833
|
+
* pushed toward unsafe string interpolation. A param-less call still sends
|
|
834
|
+
* only { cypher } and behaves exactly as before.
|
|
835
|
+
*
|
|
836
|
+
* NOTE (2026-05-11, graph-on-graph P0 cont.): RESULT-SHAPE NORMALIZATION.
|
|
837
|
+
* The Brain MCP `brain_query` tool serializes its result as
|
|
838
|
+
* `JSON.stringify(records)` where `records` is a BARE ARRAY of row objects.
|
|
839
|
+
* `callTool` returns that array directly (or `{ text: 'Error: ...' }` on a
|
|
840
|
+
* Cypher error, or `null` when the Brain is unreachable / no API key).
|
|
841
|
+
* Consumers across the codebase -- brain-router.cjs, brain-derivation.cjs's
|
|
842
|
+
* `renderRecords`, rs-chain-feeder.cjs, rs-experts-command.cjs,
|
|
843
|
+
* rs-explain-command.cjs, rs-thesis-command.cjs -- all read `result.records`,
|
|
844
|
+
* so the bare-array shape silently dropped every row. `query` therefore now
|
|
845
|
+
* ALWAYS returns `{ records: [...] }` on a successful brain_query; an
|
|
846
|
+
* unreachable Brain / missing key still returns `null`; a Cypher-error
|
|
847
|
+
* response (`{ text: 'Error: ...' }` or `{ error: ... }`) passes through
|
|
848
|
+
* unchanged so callers that inspect the failure can still see it; any other
|
|
849
|
+
* unexpected shape collapses to `{ records: [] }` so callers never crash.
|
|
850
|
+
* `search`, `smartSearch`, `schema`, `stats`, `write`, `callTool` are
|
|
851
|
+
* deliberately untouched -- only `query` is normalized.
|
|
852
|
+
*
|
|
853
|
+
* NOTE (2026-09-01, quick/260901-ipp): a THIRD recognized shape. Theo's
|
|
854
|
+
* `brain_query` contract cutover returns `{ rows: [...], diagnostics: {...} }`
|
|
855
|
+
* instead of the bare array above, and that shape matched none of the
|
|
856
|
+
* branches here -- it fell through to the unexpected-shape safety net, so
|
|
857
|
+
* every post-flip `brain_query` call returned an empty result set that was
|
|
858
|
+
* indistinguishable from a legitimate no-match, with no error and no crash.
|
|
859
|
+
* `query` now recognizes `result.rows` (guarded with `Array.isArray`, never
|
|
860
|
+
* mere key presence, so a malformed `rows` value still lands on the safety
|
|
861
|
+
* net) and normalizes it into `records` while preserving every other field
|
|
862
|
+
* the Brain sent (`diagnostics`, and any future `coverage` block). Preserving
|
|
863
|
+
* `diagnostics` is load-bearing: it is what lets a caller tell a genuine
|
|
864
|
+
* `{ rows: [], diagnostics: {...} }` "nothing matched" apart from an
|
|
865
|
+
* unrecognized-shape response (see the 2026-09-03 note below for what the
|
|
866
|
+
* fallback returns now). Sources: this repo's
|
|
867
|
+
* `docs/2026-09-01-HANDOFF-phases-272-274-275-plus-theo-flip-coordination.md`
|
|
868
|
+
* finding #1, and Theo's own
|
|
869
|
+
* `.planning/phases/09-brain-contract-cutover/09-MOS-LEARNING.md`.
|
|
870
|
+
*
|
|
871
|
+
* NOTE (2026-09-03, quick/260903-eit): the safety net stopped being blind.
|
|
872
|
+
* An unrecognized shape still returns an object carrying `records: []` so
|
|
873
|
+
* callers never crash, but it now ALSO carries
|
|
874
|
+
* `error: 'brain_query_unrecognized_shape'` plus `shape_type` and
|
|
875
|
+
* `shape_keys`, and emits one warn-once `[mindrian-os]` line naming the
|
|
876
|
+
* offending top-level key names. Reason: the bare `{ records: [] }` was
|
|
877
|
+
* byte-identical to a legitimate no-match, which is precisely how the Theo
|
|
878
|
+
* `{ rows, diagnostics }` drift went unnoticed above until a human read this
|
|
879
|
+
* code. This change recognizes ZERO new response shapes -- it only makes the
|
|
880
|
+
* next mismatch audible. Sources: this task directory
|
|
881
|
+
* (`.planning/quick/260903-eit-.../260903-eit-PLAN.md`) alongside the
|
|
882
|
+
* 2026-09-01 sources above.
|
|
883
|
+
*/
|
|
884
|
+
async function query(cypher, params) {
|
|
885
|
+
// Part 8 BACKSTOP (Phase 239 / BRAIN-02). This is a BACKSTOP ONLY and is
|
|
886
|
+
// PROVABLY INSUFFICIENT ALONE: MEASURED, the template's own vocabulary word
|
|
887
|
+
// "Framework" satisfies the classifier's positive methodology recognizer
|
|
888
|
+
// and launders an embedded canary from ambiguous to allow -- classifying
|
|
889
|
+
// the payload {cypher:"CANARY7F3A2B"} on its own yields verdict ambiguous,
|
|
890
|
+
// but classifying {cypher:'MATCH (f:Framework) WHERE x="CANARY7F3A2B"'}
|
|
891
|
+
// yields verdict allow. The REAL coverage is the raw-field guard added in
|
|
892
|
+
// hatAwareRecommend and suggestValidationSteps (classify-before-sanitize,
|
|
893
|
+
// classify-before-interpolate). Anyone tempted to delete those raw-field
|
|
894
|
+
// guards because "query() already checks" must read this comment first:
|
|
895
|
+
// it does not.
|
|
896
|
+
// An 'ambiguous' verdict on an assembled template is EXPECTED policy here
|
|
897
|
+
// (every legitimate methodology query looks ambiguous at this tier) and is
|
|
898
|
+
// deliberately NOT blocked; only a proven 'block' (a CONTENT-SET hit) stops
|
|
899
|
+
// the call.
|
|
900
|
+
try {
|
|
901
|
+
const queryEgressGuard = require('./part8-egress-guard.cjs');
|
|
902
|
+
const queryEgressVerdict = queryEgressGuard.classify(
|
|
903
|
+
{ cypher: String(cypher || '') },
|
|
904
|
+
{ toolName: 'brain_query' }
|
|
905
|
+
);
|
|
906
|
+
if (queryEgressVerdict && queryEgressVerdict.verdict === 'block') {
|
|
907
|
+
_logEventBestEffort(undefined, 'brain_egress_blocked', {
|
|
908
|
+
egress_class: queryEgressVerdict.class || 'content_set',
|
|
909
|
+
verdict: 'block',
|
|
910
|
+
count: 1,
|
|
911
|
+
created_by: 'system',
|
|
912
|
+
source_path: 'system:brain-query',
|
|
913
|
+
});
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
} catch (_e) {
|
|
917
|
+
// Belt-internal error (guard module missing/throws): degrade to existing
|
|
918
|
+
// behavior. This backstop is a belt, not the primary control.
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const args = { cypher: cypher };
|
|
922
|
+
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
|
923
|
+
args.params = params;
|
|
924
|
+
}
|
|
925
|
+
const result = await callTool('brain_query', args);
|
|
926
|
+
if (result == null) return null; // unreachable / no API key
|
|
927
|
+
if (Array.isArray(result)) {
|
|
928
|
+
// the normal brain_query shape. `_attachEgressDisclosure` (Phase 254 /
|
|
929
|
+
// COMP-02) attaches `egress_disclosure` as a non-index own property of
|
|
930
|
+
// this SAME array when the Part 8 belt captured an `ambiguous` verdict.
|
|
931
|
+
// The `{ records: result }` wrapper below is a BRAND-NEW object, so that
|
|
932
|
+
// property is not carried over automatically -- it must be copied onto
|
|
933
|
+
// the new top-level object explicitly, or it silently relocates to
|
|
934
|
+
// `.records.egress_disclosure` (invisible to every documented/tested
|
|
935
|
+
// caller and to JSON.stringify). See CR-01, 254-REVIEW.md.
|
|
936
|
+
const normalized = { records: result };
|
|
937
|
+
if (Object.prototype.hasOwnProperty.call(result, 'egress_disclosure')) {
|
|
938
|
+
normalized.egress_disclosure = result.egress_disclosure;
|
|
939
|
+
// `normalized.records` is the SAME array reference as `result` (not a
|
|
940
|
+
// copy), so without this the stray non-index property would still sit
|
|
941
|
+
// on it after normalization -- accessible via result.records.egress_disclosure
|
|
942
|
+
// even though the documented, tested contract is the top-level field
|
|
943
|
+
// above. Deleting closes the "silently relocates" failure mode WR-02
|
|
944
|
+
// flagged, not just CR-01's missing top-level copy.
|
|
945
|
+
delete result.egress_disclosure;
|
|
946
|
+
}
|
|
947
|
+
return normalized;
|
|
948
|
+
}
|
|
949
|
+
if (result && Array.isArray(result.records)) return result; // already normalized (defensive)
|
|
950
|
+
// Theo's brain_query contract shape: { rows, diagnostics }. Guard on
|
|
951
|
+
// Array.isArray(result.rows), never mere key presence, so a malformed
|
|
952
|
+
// `rows` value still falls through to the safety net below. Placed
|
|
953
|
+
// adjacent to the other shape checks so all shape recognition stays in
|
|
954
|
+
// one block, ahead of the fallback. Theo's error envelope is
|
|
955
|
+
// { text: 'CODE: detail' } and carries no `rows`, so this branch can
|
|
956
|
+
// never swallow an error.
|
|
957
|
+
if (result && Array.isArray(result.rows)) {
|
|
958
|
+
return Object.assign({}, result, { records: result.rows });
|
|
959
|
+
}
|
|
960
|
+
if (result && (result.error || result.text)) return result; // error / message passthrough
|
|
961
|
+
return _unrecognizedQueryShape(result);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// ----------------------------------------------------------------------------
|
|
965
|
+
// _unrecognizedQueryShape -- the query() safety net, made loud (2026-09-03,
|
|
966
|
+
// quick/260903-eit). Mirrors the _legacyPathWarned / _warnLegacyOnce idiom
|
|
967
|
+
// this file already documents as "the existing once-per-process warning
|
|
968
|
+
// pattern in this file".
|
|
969
|
+
//
|
|
970
|
+
// CANON PART 8: key NAMES only, never VALUES. A Brain response envelope's
|
|
971
|
+
// top-level key names are generic schema handles; its values may carry graph
|
|
972
|
+
// payload. Neither the returned `shape_keys` nor the warning line may ever
|
|
973
|
+
// contain a value from the response body.
|
|
974
|
+
// ----------------------------------------------------------------------------
|
|
975
|
+
|
|
976
|
+
let _queryShapeWarned = false;
|
|
977
|
+
|
|
978
|
+
function _unrecognizedQueryShape(result) {
|
|
979
|
+
const shapeType = typeof result;
|
|
980
|
+
// Arrays never reach here in practice (Array.isArray(result) is caught by
|
|
981
|
+
// the branch above), but keep the check anyway so this helper stays
|
|
982
|
+
// correct even if it is ever called from elsewhere.
|
|
983
|
+
const isPlainObject =
|
|
984
|
+
result !== null && shapeType === 'object' && !Array.isArray(result);
|
|
985
|
+
const shapeKeys = isPlainObject
|
|
986
|
+
? Object.keys(result)
|
|
987
|
+
.slice(0, 12) // cap key count so a hostile/malformed envelope can't flood the log
|
|
988
|
+
.map((k) => String(k).slice(0, 64)) // cap key length for the same reason
|
|
989
|
+
: []; // never Object.keys() a string -- that yields character indices, not key names
|
|
990
|
+
|
|
991
|
+
if (!_queryShapeWarned) {
|
|
992
|
+
_queryShapeWarned = true;
|
|
993
|
+
// eslint-disable-next-line no-console
|
|
994
|
+
console.warn(
|
|
995
|
+
'[mindrian-os] brain_query_unrecognized_shape: the Brain returned a '
|
|
996
|
+
+ 'response shape (' + shapeType + ', keys: [' + shapeKeys.join(', ') + ']) '
|
|
997
|
+
+ 'that query() does not recognize. This is a CONTRACT MISMATCH, NOT an '
|
|
998
|
+
+ 'empty answer -- the caller still receives records: [] so it does not '
|
|
999
|
+
+ 'crash, but every downstream reader should treat this as unresolved '
|
|
1000
|
+
+ 'until the response contract is reconciled.'
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return {
|
|
1005
|
+
records: [],
|
|
1006
|
+
error: 'brain_query_unrecognized_shape',
|
|
1007
|
+
shape_type: shapeType,
|
|
1008
|
+
shape_keys: shapeKeys,
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Search Pinecone via Brain (semantic search).
|
|
1014
|
+
* If quota exhausted, returns error with fallback suggestion.
|
|
1015
|
+
*/
|
|
1016
|
+
async function search(queryText, options = {}) {
|
|
1017
|
+
const result = await callTool('brain_search', {
|
|
1018
|
+
query: queryText,
|
|
1019
|
+
namespace: options.namespace || undefined,
|
|
1020
|
+
topK: options.topK || 5,
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// Check for Pinecone quota exhaustion
|
|
1024
|
+
if (result && result.text && result.text.includes('RESOURCE_EXHAUSTED')) {
|
|
1025
|
+
return {
|
|
1026
|
+
error: 'pinecone_quota_exhausted',
|
|
1027
|
+
message: 'Pinecone embedding quota exhausted for this month. Using Neo4j Cypher fallback.',
|
|
1028
|
+
fallback: 'neo4j',
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
return result;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Search with automatic fallback: Pinecone first, Neo4j Cypher if quota exhausted.
|
|
1037
|
+
*/
|
|
1038
|
+
async function smartSearch(queryText, options = {}) {
|
|
1039
|
+
// Try Pinecone first
|
|
1040
|
+
const pineconeResult = await search(queryText, options);
|
|
1041
|
+
|
|
1042
|
+
if (pineconeResult && pineconeResult.error === 'pinecone_quota_exhausted') {
|
|
1043
|
+
// Fallback to Neo4j full-text search
|
|
1044
|
+
const cypher = `
|
|
1045
|
+
CALL db.index.fulltext.queryNodes("framework_search", $query)
|
|
1046
|
+
YIELD node, score
|
|
1047
|
+
RETURN node.name AS name, node.description AS description, score
|
|
1048
|
+
LIMIT ${options.topK || 5}
|
|
1049
|
+
`;
|
|
1050
|
+
const neo4jResult = await query(cypher.replace('$query', `"${sanitizeCypherInput(queryText)}"`));
|
|
1051
|
+
if (neo4jResult) {
|
|
1052
|
+
neo4jResult._source = 'neo4j_fallback';
|
|
1053
|
+
neo4jResult._note = 'Pinecone quota exhausted. Results from Neo4j Cypher fulltext search.';
|
|
1054
|
+
}
|
|
1055
|
+
return neo4jResult;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return pineconeResult;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// brain_schema is near-static (the teaching graph's label/relationship/property
|
|
1062
|
+
// taxonomy changes ~never) and is hit by several modules. Memoize it
|
|
1063
|
+
// process-wide for 30 minutes.
|
|
1064
|
+
//
|
|
1065
|
+
// Phase 339 (2026-09-03, FLIP-05, D-13 as corrected): _schemaCacheOrigin
|
|
1066
|
+
// (declared beside _schemaCacheAt below) keys the memo on the resolved
|
|
1067
|
+
// origin (getBrainUrl()'s own value, compared by identity), so a cached
|
|
1068
|
+
// schema can never be served across an origin change. The correction this
|
|
1069
|
+
// comment states plainly so the next reader does not re-derive it:
|
|
1070
|
+
// BRAIN_URL is a module-scope const resolved once at require time, so no
|
|
1071
|
+
// running process can ever OBSERVE an origin change -- this guard is
|
|
1072
|
+
// therefore provably inert against the incumbent today (the comparison
|
|
1073
|
+
// below is always true within one process). It ships anyway as defense in
|
|
1074
|
+
// depth: it becomes load-bearing the day anything makes the origin mutable
|
|
1075
|
+
// per process, which is exactly the class of future change that would
|
|
1076
|
+
// otherwise reintroduce this bug silently. By the same reasoning, no
|
|
1077
|
+
// exported flush-the-memo function exists here BY DECISION: it would have
|
|
1078
|
+
// zero callers (nothing can observe the origin change it would be flushing
|
|
1079
|
+
// for), which Canon Part 7 and this repo's dead-surface discipline both
|
|
1080
|
+
// argue against; tests/test-339-schema-memo-origin-keyed.cjs Arm 7 asserts
|
|
1081
|
+
// its absence. Flip-day answer for 09-MOS-LEARNING's addendum, verbatim:
|
|
1082
|
+
// "no flush needed: memo is process-local, keyed by origin since beta.17".
|
|
1083
|
+
let _schemaCache = null;
|
|
1084
|
+
let _schemaCacheAt = 0;
|
|
1085
|
+
let _schemaCacheOrigin = null;
|
|
1086
|
+
const SCHEMA_CACHE_TTL_MS = 30 * 60 * 1000;
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Get the Brain Neo4j schema (node labels, relationship types, property keys).
|
|
1090
|
+
* Memoized for 30 minutes (process-wide), keyed on the resolved origin.
|
|
1091
|
+
*/
|
|
1092
|
+
async function schema() {
|
|
1093
|
+
if (
|
|
1094
|
+
_schemaCache &&
|
|
1095
|
+
_schemaCacheOrigin === getBrainUrl() &&
|
|
1096
|
+
(Date.now() - _schemaCacheAt) < SCHEMA_CACHE_TTL_MS
|
|
1097
|
+
) {
|
|
1098
|
+
return _schemaCache;
|
|
1099
|
+
}
|
|
1100
|
+
const result = await callTool('brain_schema', {});
|
|
1101
|
+
// Phase 247-02 audit fix (Rule 1): a sentinel object (tier_denied /
|
|
1102
|
+
// invalid_key -- anything carrying .error) is NOT valid schema data. Caching
|
|
1103
|
+
// it would serve a stale denial for up to 30 minutes even after the Brain
|
|
1104
|
+
// recovers or the key is fixed. Only cache a genuine, error-free result.
|
|
1105
|
+
if (result != null && !(typeof result === 'object' && result.error)) {
|
|
1106
|
+
_schemaCache = result;
|
|
1107
|
+
_schemaCacheAt = Date.now();
|
|
1108
|
+
_schemaCacheOrigin = getBrainUrl();
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Natural-language methodology question against the Brain (wraps brain_ask).
|
|
1115
|
+
*
|
|
1116
|
+
* brain_ask auto-routes Pinecone/Neo4j server-side and handles its own
|
|
1117
|
+
* fallback -- it is the highest-level Brain entry point. Prefer it over a
|
|
1118
|
+
* hand-rolled Cypher query when the caller has a natural-language methodology
|
|
1119
|
+
* question. Canon Part 8: the question string carries only generic methodology
|
|
1120
|
+
* language -- never user artifacts, meeting text, or personal identifiers.
|
|
1121
|
+
*
|
|
1122
|
+
* Returns the parsed brain_ask payload ({ question, keyword, source, count,
|
|
1123
|
+
* results: [...] }) on success; a { text: 'Error: ...' } / { error: ... }
|
|
1124
|
+
* passthrough on a server-side error; null when the Brain is unreachable or no
|
|
1125
|
+
* API key is configured (graceful degradation -- mirrors query()).
|
|
1126
|
+
*
|
|
1127
|
+
* @param {string} question
|
|
1128
|
+
* @returns {Promise<object|null>}
|
|
1129
|
+
*/
|
|
1130
|
+
async function ask(question) {
|
|
1131
|
+
if (typeof question !== 'string' || !question.trim()) return null;
|
|
1132
|
+
return callTool('brain_ask', { question: question });
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Curated-op call against the Brain (the `op` MODE of brain_ask).
|
|
1137
|
+
*
|
|
1138
|
+
* brain_ask gained an optional curated-op surface (BUG 2 fix). Where ask()
|
|
1139
|
+
* runs the natural-language directive path, askOp() runs one of a closed set
|
|
1140
|
+
* of named, parameterized operations the Brain resolves to a FROZEN
|
|
1141
|
+
* server-side Cypher string. The three ops:
|
|
1142
|
+
*
|
|
1143
|
+
* - 'list_frameworks' params { limit? } -> rows { name, description, category }
|
|
1144
|
+
* - 'framework_edges' params { edge_type, limit? } -> rows { from, to, confidence, transform }
|
|
1145
|
+
* or { framework, problem_type }
|
|
1146
|
+
* - 'framework_chain_slice' params { seeds, max_hops?, limit? } -> rows { from, to, hop_distance }
|
|
1147
|
+
*
|
|
1148
|
+
* Canon Part 8: every param is a generic methodology handle (framework name,
|
|
1149
|
+
* closed enum, integer) -- never user content. No caller Cypher is ever sent;
|
|
1150
|
+
* the Brain owns the query text. This path is ungated -- any valid key may
|
|
1151
|
+
* call it (only query()/write() touch the admin-gated tools).
|
|
1152
|
+
*
|
|
1153
|
+
* Consumers that only need a framework chain for ONE anchor keep using
|
|
1154
|
+
* ask(question) and read next_gate.options[].framework (the directive path).
|
|
1155
|
+
*
|
|
1156
|
+
* Returns the parsed curated-op payload { op, source, count, rows, degraded? }
|
|
1157
|
+
* on success. On any transport / parse failure (Brain unreachable, no API key,
|
|
1158
|
+
* bad payload) returns a graceful { op, count: 0, rows: [], degraded: true } so
|
|
1159
|
+
* the caller never crashes -- mirrors query()'s graceful-degradation contract.
|
|
1160
|
+
*
|
|
1161
|
+
* @param {string} operation - one of the three curated op names
|
|
1162
|
+
* @param {object} [params] - generic-handles-only params object
|
|
1163
|
+
* @returns {Promise<{op: string, source?: string, count: number, rows: Array, degraded?: boolean}>}
|
|
1164
|
+
*/
|
|
1165
|
+
async function askOp(operation, params = {}) {
|
|
1166
|
+
try {
|
|
1167
|
+
const result = await callTool('brain_ask', { op: operation, params: params || {} });
|
|
1168
|
+
// callTool already parses the JSON text payload of the MCP content item,
|
|
1169
|
+
// so a well-formed curated-op response arrives as the payload object.
|
|
1170
|
+
if (result && typeof result === 'object'
|
|
1171
|
+
&& typeof result.count === 'number' && Array.isArray(result.rows)) {
|
|
1172
|
+
return {
|
|
1173
|
+
op: result.op || operation,
|
|
1174
|
+
source: result.source,
|
|
1175
|
+
count: result.count,
|
|
1176
|
+
rows: result.rows,
|
|
1177
|
+
...(result.degraded ? { degraded: true } : {}),
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
// Unreachable Brain (null), an error/text passthrough, or any unexpected
|
|
1181
|
+
// shape -> graceful degraded sentinel.
|
|
1182
|
+
return { op: operation, count: 0, rows: [], degraded: true };
|
|
1183
|
+
} catch (_err) {
|
|
1184
|
+
return { op: operation, count: 0, rows: [], degraded: true };
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Get Pinecone stats.
|
|
1190
|
+
*/
|
|
1191
|
+
async function stats() {
|
|
1192
|
+
return callTool('brain_stats', {});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Return the resolved Brain endpoint (BRAIN_URL above, the module-level
|
|
1197
|
+
* single source of truth). The doctor store-identity sense (class-m-brain-
|
|
1198
|
+
* smoke.cjs layer 6) needs to report which endpoint the wire actually
|
|
1199
|
+
* resolved to, so it must read that value from the one place that decides
|
|
1200
|
+
* it rather than duplicating the canon literal as a second source of truth.
|
|
1201
|
+
*
|
|
1202
|
+
* @returns {string}
|
|
1203
|
+
*/
|
|
1204
|
+
function getBrainUrl() {
|
|
1205
|
+
return BRAIN_URL;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Enrich local graph with causal edges from Brain's teaching graph.
|
|
1210
|
+
*
|
|
1211
|
+
* Queries the Brain Neo4j for causal framework chains relevant to the
|
|
1212
|
+
* given problem type or section keywords. Returns structured causal data
|
|
1213
|
+
* suitable for writing to local SQLite graph as CAUSES/ROOT_CAUSE_OF edges.
|
|
1214
|
+
*
|
|
1215
|
+
* @param {string} problemType - Room problem type (e.g., 'market-validation')
|
|
1216
|
+
* @param {string[]} sectionKeywords - Keywords from room sections for context
|
|
1217
|
+
* @param {object} [options] - Optional config
|
|
1218
|
+
* @param {number} [options.maxChainDepth=3] - Maximum causal chain depth
|
|
1219
|
+
* @param {number} [options.minConfidence=0.5] - Minimum confidence threshold
|
|
1220
|
+
* @returns {Promise<{ causes: Array, rootCauses: Array } | null>}
|
|
1221
|
+
* causes: [{ from, to, mechanism, confidence, framework }]
|
|
1222
|
+
* rootCauses: [{ from, to, chainLength, intermediateCauses, confidence }]
|
|
1223
|
+
*/
|
|
1224
|
+
async function enrichCausalEdges(problemType, sectionKeywords, options = {}) {
|
|
1225
|
+
if (!isAvailable()) return null;
|
|
1226
|
+
|
|
1227
|
+
// SEC-01 defence-in-depth: coerce + bound numeric interpolants so a hostile
|
|
1228
|
+
// non-number (e.g. an object with .toString() side-effects) cannot reach
|
|
1229
|
+
// the Cypher string.
|
|
1230
|
+
const maxDepth = Math.max(1, Math.min(10, Number(options.maxChainDepth) || 3));
|
|
1231
|
+
const minConf = Math.max(0, Math.min(1, Number(options.minConfidence) || 0.5));
|
|
1232
|
+
const keywordFilter = sectionKeywords && sectionKeywords.length > 0
|
|
1233
|
+
? sectionKeywords.map(k => `"${sanitizeCypherInput(k)}"`).join(', ')
|
|
1234
|
+
: '';
|
|
1235
|
+
|
|
1236
|
+
// Query 1: Direct causal relationships from framework chains
|
|
1237
|
+
const causesCypher = `
|
|
1238
|
+
MATCH (f1:Framework)-[r:ADDRESSES_PROBLEM_TYPE]->(pt:ProblemType)
|
|
1239
|
+
WHERE pt.name CONTAINS "${sanitizeCypherInput(problemType || '')}"
|
|
1240
|
+
WITH f1
|
|
1241
|
+
MATCH (f1)-[co:CO_OCCURS]->(f2:Framework)
|
|
1242
|
+
WHERE co.weight >= ${minConf}
|
|
1243
|
+
RETURN f1.name AS cause_framework,
|
|
1244
|
+
f2.name AS effect_framework,
|
|
1245
|
+
co.weight AS confidence,
|
|
1246
|
+
f1.description AS mechanism
|
|
1247
|
+
LIMIT 20
|
|
1248
|
+
`;
|
|
1249
|
+
|
|
1250
|
+
// Query 2: Root cause chains (multi-hop framework dependencies)
|
|
1251
|
+
const rootCauseCypher = `
|
|
1252
|
+
MATCH path = (root:Framework)-[:CO_OCCURS*1..${maxDepth}]->(leaf:Framework)
|
|
1253
|
+
WHERE root <> leaf
|
|
1254
|
+
${keywordFilter ? `AND ANY(k IN [${keywordFilter}] WHERE root.name CONTAINS k OR root.description CONTAINS k)` : ''}
|
|
1255
|
+
WITH root, leaf, path, length(path) AS depth
|
|
1256
|
+
WHERE depth >= 2
|
|
1257
|
+
RETURN root.name AS root_cause,
|
|
1258
|
+
leaf.name AS symptom,
|
|
1259
|
+
depth AS chain_length,
|
|
1260
|
+
[n IN nodes(path) | n.name] AS chain_nodes
|
|
1261
|
+
LIMIT 10
|
|
1262
|
+
`;
|
|
1263
|
+
|
|
1264
|
+
try {
|
|
1265
|
+
const [causesResult, rootCausesResult] = await Promise.all([
|
|
1266
|
+
query(causesCypher),
|
|
1267
|
+
query(rootCauseCypher),
|
|
1268
|
+
]);
|
|
1269
|
+
|
|
1270
|
+
const causes = [];
|
|
1271
|
+
const rootCauses = [];
|
|
1272
|
+
|
|
1273
|
+
// Parse causes
|
|
1274
|
+
if (causesResult && Array.isArray(causesResult.records)) {
|
|
1275
|
+
for (const rec of causesResult.records) {
|
|
1276
|
+
causes.push({
|
|
1277
|
+
from: rec.cause_framework || rec[0],
|
|
1278
|
+
to: rec.effect_framework || rec[1],
|
|
1279
|
+
mechanism: rec.mechanism || rec[3] || '',
|
|
1280
|
+
confidence: parseFloat(rec.confidence || rec[2] || 0),
|
|
1281
|
+
framework: rec.cause_framework || rec[0] || '',
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// Parse root causes
|
|
1287
|
+
if (rootCausesResult && Array.isArray(rootCausesResult.records)) {
|
|
1288
|
+
for (const rec of rootCausesResult.records) {
|
|
1289
|
+
rootCauses.push({
|
|
1290
|
+
from: rec.root_cause || rec[0],
|
|
1291
|
+
to: rec.symptom || rec[1],
|
|
1292
|
+
chainLength: parseInt(rec.chain_length || rec[2] || 1, 10),
|
|
1293
|
+
intermediateCauses: rec.chain_nodes || rec[3] || [],
|
|
1294
|
+
confidence: 1.0 / (parseInt(rec.chain_length || rec[2] || 1, 10) + 1),
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return { causes, rootCauses };
|
|
1300
|
+
} catch (err) {
|
|
1301
|
+
// Brain query failed -- return null for graceful degradation
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Hat-aware framework recommendation.
|
|
1308
|
+
*
|
|
1309
|
+
* Reads persistent hat states and adjusts Brain framework queries:
|
|
1310
|
+
* - Black Hat concerns boost risk-related frameworks (Risk Matrix, SWOT threats)
|
|
1311
|
+
* - Yellow Hat opportunities boost HSI scoring and opportunity frameworks
|
|
1312
|
+
* - Blue Hat methodology notes avoid repeating ineffective frameworks
|
|
1313
|
+
*
|
|
1314
|
+
* @param {string} roomDir - Absolute path to room directory
|
|
1315
|
+
* @param {string} problemType - Room problem type
|
|
1316
|
+
* @param {object} [options] - Optional config
|
|
1317
|
+
* @param {number} [options.topK=5] - Number of frameworks to return
|
|
1318
|
+
* @returns {Promise<{ frameworks: Array, hat_influence: object } | null>}
|
|
1319
|
+
*/
|
|
1320
|
+
async function hatAwareRecommend(roomDir, problemType, options = {}) {
|
|
1321
|
+
if (!isAvailable()) return null;
|
|
1322
|
+
|
|
1323
|
+
// Lazy-require to avoid circular dependency at module load time
|
|
1324
|
+
const { loadAllHatStates } = require('./hat-persistence.cjs');
|
|
1325
|
+
const hatStates = loadAllHatStates(roomDir);
|
|
1326
|
+
// SEC-01 defence-in-depth: bound topK numeric interpolation.
|
|
1327
|
+
const topK = Math.max(1, Math.min(100, Number(options.topK) || 5));
|
|
1328
|
+
|
|
1329
|
+
const hatInfluence = {
|
|
1330
|
+
risk_boost: false,
|
|
1331
|
+
opportunity_boost: false,
|
|
1332
|
+
avoid_frameworks: [],
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
// Black Hat: if concerns exist, boost risk-related frameworks
|
|
1336
|
+
const blackConcerns = hatStates.black.top_concerns || [];
|
|
1337
|
+
const riskBoost = blackConcerns.length > 0;
|
|
1338
|
+
hatInfluence.risk_boost = riskBoost;
|
|
1339
|
+
|
|
1340
|
+
// Yellow Hat: if opportunities exist, boost HSI/opportunity frameworks
|
|
1341
|
+
const yellowOpps = hatStates.yellow.top_opportunities || [];
|
|
1342
|
+
const oppBoost = yellowOpps.length > 0;
|
|
1343
|
+
hatInfluence.opportunity_boost = oppBoost;
|
|
1344
|
+
|
|
1345
|
+
// Blue Hat: methodology notes may flag ineffective frameworks to avoid
|
|
1346
|
+
const blueNotes = hatStates.blue.methodology_notes || [];
|
|
1347
|
+
|
|
1348
|
+
// Part 8 raw-field egress guard (Phase 239 / BRAIN-02, threats T2, T4, T5).
|
|
1349
|
+
// Classifies the RAW problemType and each RAW blueNotes entry BEFORE
|
|
1350
|
+
// sanitizeCypherInput and BEFORE any Cypher interpolation below -- the raw
|
|
1351
|
+
// field is the only place the content signal is still intact. MEASURED:
|
|
1352
|
+
// classifying the ASSEMBLED cypher string instead would let the template's
|
|
1353
|
+
// own vocabulary word "Framework" launder an embedded canary from ambiguous
|
|
1354
|
+
// to allow, and sanitizeCypherInput strips the char the Part-8 PII pattern
|
|
1355
|
+
// keys on. Fail-closed: any missing or non-allow verdict skips the Brain leg.
|
|
1356
|
+
for (const rawField of [problemType].concat(blueNotes)) {
|
|
1357
|
+
let hatEgressVerdict = null;
|
|
1358
|
+
try {
|
|
1359
|
+
const hatEgressGuard = require('./part8-egress-guard.cjs');
|
|
1360
|
+
hatEgressVerdict = hatEgressGuard.classify(
|
|
1361
|
+
{ question: String(rawField || '') },
|
|
1362
|
+
{ toolName: 'brain_ask' }
|
|
1363
|
+
);
|
|
1364
|
+
} catch (_e) {
|
|
1365
|
+
hatEgressVerdict = null;
|
|
1366
|
+
}
|
|
1367
|
+
if (!hatEgressVerdict || hatEgressVerdict.verdict !== 'allow') {
|
|
1368
|
+
_logEventBestEffort(options.db, 'brain_egress_blocked', {
|
|
1369
|
+
egress_class: (hatEgressVerdict && hatEgressVerdict.class) || 'unknown',
|
|
1370
|
+
verdict: (hatEgressVerdict && hatEgressVerdict.verdict) || 'unverified',
|
|
1371
|
+
count: 1,
|
|
1372
|
+
created_by: 'system',
|
|
1373
|
+
source_path: 'system:brain-hat-recommend',
|
|
1374
|
+
});
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const avoidPatterns = blueNotes
|
|
1380
|
+
.filter(n => /ineffective|didn't work|not useful|skip|avoid/i.test(n))
|
|
1381
|
+
.map(n => {
|
|
1382
|
+
// Extract framework name from notes like "SWOT was ineffective for this stage"
|
|
1383
|
+
const match = n.match(/^(\w[\w\s]+?)\s+(?:was|is|were|proved)\s/i);
|
|
1384
|
+
return match ? match[1].trim() : null;
|
|
1385
|
+
})
|
|
1386
|
+
.filter(Boolean);
|
|
1387
|
+
hatInfluence.avoid_frameworks = avoidPatterns;
|
|
1388
|
+
|
|
1389
|
+
// Build Cypher query with hat-influenced scoring
|
|
1390
|
+
const safeProblemType = sanitizeCypherInput(problemType || '');
|
|
1391
|
+
const avoidClause = avoidPatterns.length > 0
|
|
1392
|
+
? `AND NOT ANY(avoid IN [${avoidPatterns.map(a => `"${sanitizeCypherInput(a)}"`).join(', ')}] WHERE f.name CONTAINS avoid)`
|
|
1393
|
+
: '';
|
|
1394
|
+
|
|
1395
|
+
// Query: frameworks for problem type, with hat-influenced ordering
|
|
1396
|
+
const cypher = `
|
|
1397
|
+
MATCH (f:Framework)-[:ADDRESSES_PROBLEM_TYPE]->(pt:ProblemType)
|
|
1398
|
+
WHERE pt.name CONTAINS "${safeProblemType}"
|
|
1399
|
+
${avoidClause}
|
|
1400
|
+
WITH f
|
|
1401
|
+
OPTIONAL MATCH (f)-[co:CO_OCCURS]->(f2:Framework)
|
|
1402
|
+
WITH f, count(co) AS connections
|
|
1403
|
+
RETURN f.name AS name,
|
|
1404
|
+
f.description AS description,
|
|
1405
|
+
connections,
|
|
1406
|
+
CASE
|
|
1407
|
+
WHEN ${riskBoost ? 'true' : 'false'} AND (f.name CONTAINS 'Risk' OR f.name CONTAINS 'SWOT' OR f.name CONTAINS 'Failure') THEN connections + 10
|
|
1408
|
+
WHEN ${oppBoost ? 'true' : 'false'} AND (f.name CONTAINS 'HSI' OR f.name CONTAINS 'Opportunity' OR f.name CONTAINS 'Innovation') THEN connections + 10
|
|
1409
|
+
ELSE connections
|
|
1410
|
+
END AS hat_score
|
|
1411
|
+
ORDER BY hat_score DESC
|
|
1412
|
+
LIMIT ${topK}
|
|
1413
|
+
`;
|
|
1414
|
+
|
|
1415
|
+
try {
|
|
1416
|
+
const result = await query(cypher);
|
|
1417
|
+
const frameworks = [];
|
|
1418
|
+
|
|
1419
|
+
if (result && Array.isArray(result.records)) {
|
|
1420
|
+
for (const rec of result.records) {
|
|
1421
|
+
frameworks.push({
|
|
1422
|
+
name: rec.name || rec[0],
|
|
1423
|
+
description: rec.description || rec[1],
|
|
1424
|
+
connections: parseInt(rec.connections || rec[2] || 0, 10),
|
|
1425
|
+
hat_score: parseInt(rec.hat_score || rec[3] || 0, 10),
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
return {
|
|
1431
|
+
frameworks,
|
|
1432
|
+
hat_influence: hatInfluence,
|
|
1433
|
+
black_concerns: blackConcerns.slice(0, 3),
|
|
1434
|
+
yellow_opportunities: yellowOpps.slice(0, 3),
|
|
1435
|
+
blue_avoid: avoidPatterns,
|
|
1436
|
+
};
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Suggest validation steps for a banked opportunity using Brain framework chains.
|
|
1444
|
+
*
|
|
1445
|
+
* Queries Brain Neo4j for frameworks that ADDRESSES_PROBLEM_TYPE matching the
|
|
1446
|
+
* opportunity's domain/problem, then follows FEEDS_INTO chains to build a
|
|
1447
|
+
* suggested validation sequence.
|
|
1448
|
+
*
|
|
1449
|
+
* @param {Object} opportunity - Opportunity object with at minimum: problem, domain, knight_position
|
|
1450
|
+
* @param {Object} [options] - Optional config
|
|
1451
|
+
* @param {number} [options.maxSteps=5] - Maximum validation steps to return
|
|
1452
|
+
* @param {number} [options.chainDepth=3] - Maximum FEEDS_INTO chain depth
|
|
1453
|
+
* @returns {Promise<{ steps: Array<{framework: string, reason: string, order: number}>, chain_source: string } | null>}
|
|
1454
|
+
* Returns null if Brain unavailable (Tier 0 graceful degradation)
|
|
1455
|
+
*/
|
|
1456
|
+
async function suggestValidationSteps(opportunity, options = {}) {
|
|
1457
|
+
if (!isAvailable()) return null;
|
|
1458
|
+
if (!opportunity || !opportunity.problem) return null;
|
|
1459
|
+
|
|
1460
|
+
// Part 8 raw-field egress guard (Phase 239 / BRAIN-02, threats T2, T4, T5).
|
|
1461
|
+
// Classifies the RAW opportunity.domain and the RAW opportunity.problem
|
|
1462
|
+
// BEFORE sanitizeCypherInput and BEFORE any Cypher interpolation below --
|
|
1463
|
+
// strictly upstream of sanitizeCypherInput, which is the entire point.
|
|
1464
|
+
// MEASURED: sanitizeCypherInput strips the '@' the Part-8 email pattern
|
|
1465
|
+
// keys on (jane@startup.com -> janestartup.com), flipping a block verdict
|
|
1466
|
+
// into an allow. Classifying the ASSEMBLED cypher string instead would also
|
|
1467
|
+
// let the template's own vocabulary word "Framework" launder an embedded
|
|
1468
|
+
// canary from ambiguous to allow. Fail-closed: any missing or non-allow
|
|
1469
|
+
// verdict skips the Brain leg entirely.
|
|
1470
|
+
for (const rawField of [opportunity.domain, opportunity.problem]) {
|
|
1471
|
+
let validationEgressVerdict = null;
|
|
1472
|
+
try {
|
|
1473
|
+
const validationEgressGuard = require('./part8-egress-guard.cjs');
|
|
1474
|
+
validationEgressVerdict = validationEgressGuard.classify(
|
|
1475
|
+
{ question: String(rawField || '') },
|
|
1476
|
+
{ toolName: 'brain_ask' }
|
|
1477
|
+
);
|
|
1478
|
+
} catch (_e) {
|
|
1479
|
+
validationEgressVerdict = null;
|
|
1480
|
+
}
|
|
1481
|
+
if (!validationEgressVerdict || validationEgressVerdict.verdict !== 'allow') {
|
|
1482
|
+
_logEventBestEffort(options.db, 'brain_egress_blocked', {
|
|
1483
|
+
egress_class: (validationEgressVerdict && validationEgressVerdict.class) || 'unknown',
|
|
1484
|
+
verdict: (validationEgressVerdict && validationEgressVerdict.verdict) || 'unverified',
|
|
1485
|
+
count: 1,
|
|
1486
|
+
created_by: 'system',
|
|
1487
|
+
source_path: 'system:brain-validation-steps',
|
|
1488
|
+
});
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
const maxSteps = options.maxSteps || 5;
|
|
1494
|
+
const chainDepth = options.chainDepth || 3;
|
|
1495
|
+
const safeProblem = sanitizeCypherInput(opportunity.problem || '').substring(0, 200);
|
|
1496
|
+
const safeDomain = sanitizeCypherInput(opportunity.domain || '').substring(0, 100);
|
|
1497
|
+
const safeKnight = opportunity.knight_position || 'uncertainty';
|
|
1498
|
+
|
|
1499
|
+
// Query 1: Find frameworks that address this problem type / domain
|
|
1500
|
+
const matchCypher = `
|
|
1501
|
+
MATCH (f:Framework)-[:ADDRESSES_PROBLEM_TYPE]->(pt:ProblemType)
|
|
1502
|
+
WHERE pt.name CONTAINS "${safeDomain}"
|
|
1503
|
+
OR pt.description CONTAINS "${safeDomain}"
|
|
1504
|
+
RETURN f.name AS name, f.description AS description
|
|
1505
|
+
LIMIT 10
|
|
1506
|
+
`;
|
|
1507
|
+
|
|
1508
|
+
// Query 2: Follow FEEDS_INTO chains from matched frameworks
|
|
1509
|
+
const chainCypher = `
|
|
1510
|
+
MATCH (f:Framework)-[:ADDRESSES_PROBLEM_TYPE]->(pt:ProblemType)
|
|
1511
|
+
WHERE pt.name CONTAINS "${safeDomain}"
|
|
1512
|
+
OR pt.description CONTAINS "${safeDomain}"
|
|
1513
|
+
WITH f LIMIT 3
|
|
1514
|
+
MATCH path = (f)-[:FEEDS_INTO*1..${chainDepth}]->(next:Framework)
|
|
1515
|
+
RETURN f.name AS start_framework,
|
|
1516
|
+
next.name AS next_framework,
|
|
1517
|
+
next.description AS next_description,
|
|
1518
|
+
length(path) AS depth
|
|
1519
|
+
ORDER BY depth ASC
|
|
1520
|
+
LIMIT ${maxSteps * 2}
|
|
1521
|
+
`;
|
|
1522
|
+
|
|
1523
|
+
try {
|
|
1524
|
+
const [matchResult, chainResult] = await Promise.all([
|
|
1525
|
+
query(matchCypher),
|
|
1526
|
+
query(chainCypher),
|
|
1527
|
+
]);
|
|
1528
|
+
|
|
1529
|
+
const steps = [];
|
|
1530
|
+
const seen = new Set();
|
|
1531
|
+
|
|
1532
|
+
// First: add the entry-point frameworks
|
|
1533
|
+
if (matchResult && Array.isArray(matchResult.records)) {
|
|
1534
|
+
for (const rec of matchResult.records) {
|
|
1535
|
+
const name = rec.name || rec[0];
|
|
1536
|
+
if (name && !seen.has(name) && steps.length < maxSteps) {
|
|
1537
|
+
seen.add(name);
|
|
1538
|
+
steps.push({
|
|
1539
|
+
framework: name,
|
|
1540
|
+
reason: safeKnight === 'uncertainty'
|
|
1541
|
+
? `Explore this ${safeDomain} uncertainty with ${name}`
|
|
1542
|
+
: `Validate this ${safeDomain} risk using ${name}`,
|
|
1543
|
+
order: steps.length + 1,
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// Then: add FEEDS_INTO chain steps
|
|
1550
|
+
if (chainResult && Array.isArray(chainResult.records)) {
|
|
1551
|
+
for (const rec of chainResult.records) {
|
|
1552
|
+
const name = rec.next_framework || rec[1];
|
|
1553
|
+
const desc = rec.next_description || rec[2] || '';
|
|
1554
|
+
if (name && !seen.has(name) && steps.length < maxSteps) {
|
|
1555
|
+
seen.add(name);
|
|
1556
|
+
steps.push({
|
|
1557
|
+
framework: name,
|
|
1558
|
+
reason: desc ? `Then apply ${name}: ${desc.substring(0, 120)}` : `Then apply ${name} (follows from chain)`,
|
|
1559
|
+
order: steps.length + 1,
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (steps.length === 0) return null;
|
|
1566
|
+
|
|
1567
|
+
return {
|
|
1568
|
+
steps,
|
|
1569
|
+
chain_source: 'brain_feeds_into',
|
|
1570
|
+
};
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
// Brain query failed -- graceful degradation
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Write Cypher to Neo4j via Brain (write operations).
|
|
1579
|
+
* Used by sync-rooms-brain for creating Room/RoomGroup nodes and edges.
|
|
1580
|
+
* Returns null if Brain is unavailable -- never throws.
|
|
1581
|
+
*
|
|
1582
|
+
* NOTE (Finding I sibling, v1.10.9 hotfix 2026-04-15): same param-name
|
|
1583
|
+
* mismatch as brain_query had. Brain MCP brain_write expects `cypher`,
|
|
1584
|
+
* not `query`. This function had the mirror bug since inception but
|
|
1585
|
+
* never fired in production because sync-rooms-brain is rarely invoked
|
|
1586
|
+
* against the live Brain. Caught by the plan-checker audit for Phase 85.
|
|
1587
|
+
*
|
|
1588
|
+
* @param {string} cypher - Cypher write query
|
|
1589
|
+
* @returns {Promise<object|null>}
|
|
1590
|
+
*/
|
|
1591
|
+
async function write(cypher) {
|
|
1592
|
+
return callTool('brain_write', { cypher: cypher });
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// ============================================================================
|
|
1596
|
+
// Loop-contract read wrappers (Phase 247-02, CONTRACT-01).
|
|
1597
|
+
// ============================================================================
|
|
1598
|
+
//
|
|
1599
|
+
// Thin pass-through wrappers over callTool for the five loop-contract tools
|
|
1600
|
+
// that had NO client wrapper before this phase (data/brain-surface-contract.json
|
|
1601
|
+
// is the source of truth for the tool name + arg-key shape each wrapper below
|
|
1602
|
+
// must emit; tests/test-247-contract-client.cjs derives its expectations from
|
|
1603
|
+
// that file, not from this comment). brain_stats is the sixth loop-contract
|
|
1604
|
+
// tool and already has a wrapper: stats() above.
|
|
1605
|
+
//
|
|
1606
|
+
// Every wrapper is a straight callTool(name, args) call with NO result
|
|
1607
|
+
// reshaping -- the tier_denied / invalid_key sentinels and the transport-null
|
|
1608
|
+
// pass through unchanged, exactly like write() and stats() already do.
|
|
1609
|
+
// Part 7 (extend, never a fourth brain skill): these live in this file.
|
|
1610
|
+
//
|
|
1611
|
+
// loopSearch() is named to avoid colliding with the existing search() export
|
|
1612
|
+
// above, which wraps the DIFFERENT brain_search tool (Pinecone semantic
|
|
1613
|
+
// search with quota-fallback handling). The loop-contract `search` tool is a
|
|
1614
|
+
// distinct, simpler read tool -- same English word, different Brain tool,
|
|
1615
|
+
// different client function, deliberately renamed so callers cannot conflate
|
|
1616
|
+
// them.
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Loop-contract `normalize_framework_name` tool.
|
|
1620
|
+
* @param {string} raw
|
|
1621
|
+
* @returns {Promise<object|null>}
|
|
1622
|
+
*/
|
|
1623
|
+
async function normalizeFrameworkName(raw) {
|
|
1624
|
+
return callTool('normalize_framework_name', { raw: raw });
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Loop-contract `search` tool (NOT brain_search -- see module-level note above).
|
|
1629
|
+
* @param {string} queryText
|
|
1630
|
+
* @param {number} [topK]
|
|
1631
|
+
* @returns {Promise<object|null>}
|
|
1632
|
+
*/
|
|
1633
|
+
async function loopSearch(queryText, topK) {
|
|
1634
|
+
return callTool('search', { query: queryText, topK: topK });
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// ----------------------------------------------------------------------------
|
|
1638
|
+
// Phase 249-01 (ENRICH-01) -- capture-on-miss for the two readiness-shaped
|
|
1639
|
+
// loop wrappers below. NEITHER wrapper reshapes its return value (247-02's
|
|
1640
|
+
// "zero result reshaping so sentinels propagate unchanged" is preserved
|
|
1641
|
+
// here); capture happens in a try/catch side branch AFTER resolution, and
|
|
1642
|
+
// the raw callTool result is returned unchanged in every case, including
|
|
1643
|
+
// when the capture branch itself fails. opts is OPTIONAL and backward
|
|
1644
|
+
// compatible: no opts / no opts.roomDir means zero capture, zero throw,
|
|
1645
|
+
// identical behavior to the pre-249 wrapper.
|
|
1646
|
+
//
|
|
1647
|
+
// Sentinel discipline: a result carrying .error (tier_denied, invalid_key)
|
|
1648
|
+
// or a null (transport failure) is NEVER a capture trigger -- failure
|
|
1649
|
+
// visibility is Phase 250's refusal territory, not an enrichment miss
|
|
1650
|
+
// (research "Where the readiness probe should run").
|
|
1651
|
+
//
|
|
1652
|
+
// Each successful capture logs a scalar enrichment_queue_captured
|
|
1653
|
+
// memory_event (framework handle + score only), mirroring the existing
|
|
1654
|
+
// brain_packet_rejected precedent's _logEventBestEffort mechanism above --
|
|
1655
|
+
// best-effort, silently skipped when opts.db is absent.
|
|
1656
|
+
// ----------------------------------------------------------------------------
|
|
1657
|
+
|
|
1658
|
+
function _isCapturableResult(result) {
|
|
1659
|
+
return result !== null && typeof result === 'object' && !result.error;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function _maybeCaptureEnrichmentMiss(frameworkName, result, opts) {
|
|
1663
|
+
try {
|
|
1664
|
+
if (!opts || typeof opts !== 'object') return;
|
|
1665
|
+
if (typeof opts.roomDir !== 'string' || opts.roomDir.length === 0) return;
|
|
1666
|
+
if (!_isCapturableResult(result)) return;
|
|
1667
|
+
// eslint-disable-next-line global-require
|
|
1668
|
+
const enrichmentQueue = require('./enrichment-queue.cjs');
|
|
1669
|
+
const captureResult = enrichmentQueue.captureReadinessMiss(
|
|
1670
|
+
opts.roomDir, frameworkName, result, opts.contextClass || {}
|
|
1671
|
+
);
|
|
1672
|
+
if (captureResult && captureResult.queued) {
|
|
1673
|
+
_logEventBestEffort(opts.db, 'enrichment_queue_captured', {
|
|
1674
|
+
framework: frameworkName,
|
|
1675
|
+
// Phase 339 (2026-09-03, FLIP-03): recognizes Theo's orchestration_readiness
|
|
1676
|
+
// payload shape (result.score, an integer 0-4 whose practical ceiling is 3,
|
|
1677
|
+
// /home/jsagi/Theo/src/mcp/content/orchestration-readiness.ts:494) alongside
|
|
1678
|
+
// the incumbent's result.readiness_score, in the SAME expression, each
|
|
1679
|
+
// guarded by typeof === 'number' (D-04) and never on key presence. The
|
|
1680
|
+
// incumbent's field is checked first so today's log bytes stay identical
|
|
1681
|
+
// when both are somehow present. Deliberately does NOT rescale, does NOT
|
|
1682
|
+
// default a missing score to 0, and does NOT read coverage -- coverage
|
|
1683
|
+
// semantics live in the capture path in lib/core/enrichment-queue.cjs
|
|
1684
|
+
// (plan 339-05's territory). null is still the final fallback so an
|
|
1685
|
+
// unrecognized shape is honestly null rather than a coerced zero.
|
|
1686
|
+
readiness_score: (typeof result.readiness_score === 'number')
|
|
1687
|
+
? result.readiness_score
|
|
1688
|
+
: ((typeof result.score === 'number') ? result.score : null),
|
|
1689
|
+
source_path: 'system:enrichment-queue',
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
} catch (_e) {
|
|
1693
|
+
// Best-effort capture side branch: NEVER surfaces to the wrapper caller.
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* Loop-contract `discover_structure` tool.
|
|
1699
|
+
*
|
|
1700
|
+
* @param {string} frameworkName
|
|
1701
|
+
* @param {object} [opts] - { roomDir?, contextClass?, db? }. Phase 249-01:
|
|
1702
|
+
* when opts.roomDir is set and the resolved payload carries
|
|
1703
|
+
* grounded === false, best-effort captures an enrichment-queue entry.
|
|
1704
|
+
* Absent opts / opts.roomDir: zero capture, zero throw, unchanged
|
|
1705
|
+
* backward-compatible behavior.
|
|
1706
|
+
* @returns {Promise<object|null>}
|
|
1707
|
+
*/
|
|
1708
|
+
async function discoverStructure(frameworkName, opts) {
|
|
1709
|
+
const result = await callTool('discover_structure', { framework_name: frameworkName });
|
|
1710
|
+
_maybeCaptureEnrichmentMiss(frameworkName, result, opts);
|
|
1711
|
+
return result;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* Loop-contract `orchestration_readiness` tool.
|
|
1716
|
+
*
|
|
1717
|
+
* @param {string} frameworkName
|
|
1718
|
+
* @param {object} [opts] - { roomDir?, contextClass?, db? }. Phase 249-01:
|
|
1719
|
+
* when opts.roomDir is set and the resolved payload carries
|
|
1720
|
+
* readiness_score <= 2, best-effort captures an enrichment-queue entry.
|
|
1721
|
+
* Absent opts / opts.roomDir: zero capture, zero throw, unchanged
|
|
1722
|
+
* backward-compatible behavior.
|
|
1723
|
+
* @returns {Promise<object|null>}
|
|
1724
|
+
*/
|
|
1725
|
+
async function orchestrationReadiness(frameworkName, opts) {
|
|
1726
|
+
const result = await callTool('orchestration_readiness', { framework_name: frameworkName });
|
|
1727
|
+
_maybeCaptureEnrichmentMiss(frameworkName, result, opts);
|
|
1728
|
+
return result;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Loop-contract `feeds_into_chains` tool.
|
|
1733
|
+
* @param {string[]} seeds
|
|
1734
|
+
* @param {number} [maxHops]
|
|
1735
|
+
* @returns {Promise<object|null>}
|
|
1736
|
+
*/
|
|
1737
|
+
async function feedsIntoChains(seeds, maxHops) {
|
|
1738
|
+
return callTool('feeds_into_chains', { seeds: seeds, max_hops: maxHops });
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// Quick 260819-c8j (WS-C1): the enum-only Part 8 shape gate for
|
|
1742
|
+
// recommendChain(), below. `problemTypeOf(tuple)` (lib/core/insight-
|
|
1743
|
+
// sensors.cjs) returns `tuple.problem_type` VERBATIM off the /mos:diagnose
|
|
1744
|
+
// tuple, so the `brain_framework_chain:<pt>` companion string is NOT
|
|
1745
|
+
// structurally guaranteed to be a Brain ProblemType enum -- it could carry
|
|
1746
|
+
// anything the classifier wrote. A handle-shaped token (letter-initial,
|
|
1747
|
+
// letters/digits/space/hyphen only, 1..49 chars) cannot carry prose, a
|
|
1748
|
+
// sentence, a URL, an email, or a payload, so this regex is the enum-only
|
|
1749
|
+
// enforcement at the wire: a non-handle input is refused locally with ZERO
|
|
1750
|
+
// network rather than forwarded and hoped-safe.
|
|
1751
|
+
const PROBLEM_TYPE_HANDLE_RE = /^[A-Za-z][A-Za-z0-9 -]{0,48}$/;
|
|
1752
|
+
|
|
1753
|
+
// Projects the LOCAL sensor slugs (`problemTypeOf`'s output shape) onto the
|
|
1754
|
+
// Brain's canonical ProblemType node names. This is a PROJECTION, not an
|
|
1755
|
+
// allow-list: an unmapped token still passes THROUGH _normalizeBrainProblemType
|
|
1756
|
+
// unchanged, because data/brain-census.generated.json records 26 live
|
|
1757
|
+
// ProblemType nodes on the Brain and this map names only the 3 the local
|
|
1758
|
+
// sensors are known to emit today -- a local allow-list would silently block
|
|
1759
|
+
// the other 23 valid types the Brain already knows how to answer.
|
|
1760
|
+
//
|
|
1761
|
+
// Deliberately NOT the same map as this file's sibling,
|
|
1762
|
+
// lib/brain/chain-recommender.cjs's PROBLEM_TYPE_ALIASES (:71 there): that
|
|
1763
|
+
// map projects onto the LOCAL router codes UDP/IDP/WDP (a different target,
|
|
1764
|
+
// consumed by problem-type-router.cjs), not onto Brain ProblemType node
|
|
1765
|
+
// names. The two are not shared on purpose, so a reader cannot conflate a
|
|
1766
|
+
// local router code with a Brain node name. This stays true of BOTH tables
|
|
1767
|
+
// below.
|
|
1768
|
+
//
|
|
1769
|
+
// Phase 339 (2026-09-03): TWO tables, not one, plus an origin-keyed
|
|
1770
|
+
// selector. Theo matches WHERE toLower(m.id) = toLower($problemType) AND
|
|
1771
|
+
// NOTHING MORE (/home/jsagi/Theo/src/mcp/content/recommend-chain.ts:320-321,
|
|
1772
|
+
// the matching rule stated at :65-69): no trimming, no hyphen folding, no
|
|
1773
|
+
// suffix stripping, no fuzzy fallback. The incumbent's target value
|
|
1774
|
+
// 'Undefined Problem' matches nothing on Theo's :DomainConcept nodes, whose
|
|
1775
|
+
// ids are the exact strings 'UnDefined' / 'IllDefined' / 'WellDefined' /
|
|
1776
|
+
// 'Wicked' / 'Trinity' / 'Compass' (recommend-chain.ts:27-47). A single
|
|
1777
|
+
// shared table can never be correct for both origins at once -- the map
|
|
1778
|
+
// ITSELF is what breaks the post-flip match, so the map itself is what this
|
|
1779
|
+
// phase fixes, before the flip ships, not with it.
|
|
1780
|
+
//
|
|
1781
|
+
// This mechanism deliberately does NOT key on a response, does not retry on
|
|
1782
|
+
// PROBLEM_TYPE_NOT_FOUND using available_problem_types, and does not sniff
|
|
1783
|
+
// the server. A runtime retry would cost a round trip on every miss, would
|
|
1784
|
+
// paper over a vocabulary bug at runtime instead of fixing it, and would
|
|
1785
|
+
// break the property this selector exists to preserve: a
|
|
1786
|
+
// MINDRIAN_BRAIN_URL revert moves vocabulary and URL together, with no
|
|
1787
|
+
// second edit anywhere.
|
|
1788
|
+
//
|
|
1789
|
+
// Canon Part 8: both tables carry problem-type handles only, closed
|
|
1790
|
+
// vocabulary, never room content.
|
|
1791
|
+
const BRAIN_PROBLEM_TYPE_ALIASES_INCUMBENT = Object.freeze({
|
|
1792
|
+
'undefined': 'Undefined Problem',
|
|
1793
|
+
udp: 'Undefined Problem',
|
|
1794
|
+
'ill-defined': 'Ill-Defined Problem',
|
|
1795
|
+
ill_defined: 'Ill-Defined Problem',
|
|
1796
|
+
idp: 'Ill-Defined Problem',
|
|
1797
|
+
'well-defined': 'Well-Defined Problem',
|
|
1798
|
+
well_defined: 'Well-Defined Problem',
|
|
1799
|
+
wdp: 'Well-Defined Problem',
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
const BRAIN_PROBLEM_TYPE_ALIASES_THEO = Object.freeze({
|
|
1803
|
+
'undefined': 'UnDefined',
|
|
1804
|
+
udp: 'UnDefined',
|
|
1805
|
+
'ill-defined': 'IllDefined',
|
|
1806
|
+
ill_defined: 'IllDefined',
|
|
1807
|
+
idp: 'IllDefined',
|
|
1808
|
+
'well-defined': 'WellDefined',
|
|
1809
|
+
well_defined: 'WellDefined',
|
|
1810
|
+
wdp: 'WellDefined',
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
// The set of Brain origins whose problem-type vocabulary is Theo's, not the
|
|
1814
|
+
// incumbent's. An ARRAY, not a bare string literal, so a Theo staging
|
|
1815
|
+
// origin is a one-line addition rather than a re-architecture. Exported so
|
|
1816
|
+
// lib/core/doctor/class-m-brain-smoke.cjs can key its per-origin node floor
|
|
1817
|
+
// on this SAME set (plan 339-12), rather than minting a second copy of the
|
|
1818
|
+
// origin list.
|
|
1819
|
+
const THEO_ORIGINS = Object.freeze(['https://theo-mcp.onrender.com']);
|
|
1820
|
+
|
|
1821
|
+
// Selects the alias table by the resolved origin (BRAIN_URL, the same
|
|
1822
|
+
// module-scope const getBrainUrl() reads). Anything not in THEO_ORIGINS
|
|
1823
|
+
// gets the incumbent vocabulary -- the correct default while line 24 names
|
|
1824
|
+
// the incumbent, and the conservative default afterwards for any origin
|
|
1825
|
+
// this set does not yet name.
|
|
1826
|
+
function _brainProblemTypeAliases() {
|
|
1827
|
+
return THEO_ORIGINS.indexOf(BRAIN_URL) !== -1
|
|
1828
|
+
? BRAIN_PROBLEM_TYPE_ALIASES_THEO
|
|
1829
|
+
: BRAIN_PROBLEM_TYPE_ALIASES_INCUMBENT;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* _normalizeBrainProblemType(raw) -> canonical string | null
|
|
1834
|
+
* null when raw is not a string, is empty after trim, or fails the
|
|
1835
|
+
* PROBLEM_TYPE_HANDLE_RE shape gate above. A known local slug (matched
|
|
1836
|
+
* case-insensitively on the trimmed token) projects to its Brain canonical
|
|
1837
|
+
* name; anything else well-shaped passes through unchanged for the Brain to
|
|
1838
|
+
* rule on honestly.
|
|
1839
|
+
* @param {*} raw
|
|
1840
|
+
* @returns {string|null}
|
|
1841
|
+
*/
|
|
1842
|
+
function _normalizeBrainProblemType(raw) {
|
|
1843
|
+
if (typeof raw !== 'string') return null;
|
|
1844
|
+
const trimmed = raw.trim();
|
|
1845
|
+
if (trimmed.length === 0) return null;
|
|
1846
|
+
if (!PROBLEM_TYPE_HANDLE_RE.test(trimmed)) return null;
|
|
1847
|
+
const lc = trimmed.toLowerCase();
|
|
1848
|
+
const table = _brainProblemTypeAliases();
|
|
1849
|
+
if (Object.prototype.hasOwnProperty.call(table, lc)) {
|
|
1850
|
+
return table[lc];
|
|
1851
|
+
}
|
|
1852
|
+
return trimmed;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* Loop-contract `recommend_chain` tool. A straight sibling of
|
|
1857
|
+
* feedsIntoChains() above: ZERO result reshaping, so sentinels
|
|
1858
|
+
* (tier_denied/invalid_key) and the transport-null pass through untouched
|
|
1859
|
+
* (247-02 convention) -- match its behavior byte-for-byte except for the
|
|
1860
|
+
* pre-wire shape gate this tool needs and the sibling above does not.
|
|
1861
|
+
*
|
|
1862
|
+
* Live output shape (verified live 2026-08-19 AND now contractually pinned):
|
|
1863
|
+
* { tool, backend, grounded, problem_type, seed_candidates,
|
|
1864
|
+
* chain: [{ step, framework, pagerank, edge_confidence, commands }],
|
|
1865
|
+
* note, execution_hint }
|
|
1866
|
+
* The Brain team froze recommend_chain as a v1 ADDITIVE-ONLY contract in
|
|
1867
|
+
* their own repo (ProblemsWorthSolving-Brain, docs/RECOMMEND-CHAIN-
|
|
1868
|
+
* CONTRACT.md, commit 721d8e1): under v1 these keys may gain siblings but
|
|
1869
|
+
* will not be renamed or removed, and an unknown problem type is GUARANTEED
|
|
1870
|
+
* to come back as { grounded: false, chain: [] } rather than an error or a
|
|
1871
|
+
* throw. This wrapper does not depend on any of that shape -- it is a pure
|
|
1872
|
+
* pass-through -- so a v2 or an out-of-contract change degrades at the
|
|
1873
|
+
* consumer (lib/brain/chain-recommender.cjs), never here.
|
|
1874
|
+
*
|
|
1875
|
+
* @param {string} problemType
|
|
1876
|
+
* @param {number} [maxSteps]
|
|
1877
|
+
* @returns {Promise<object|null>}
|
|
1878
|
+
*/
|
|
1879
|
+
async function recommendChain(problemType, maxSteps) {
|
|
1880
|
+
const normalized = _normalizeBrainProblemType(problemType);
|
|
1881
|
+
if (normalized === null) {
|
|
1882
|
+
// Shape gate failed: refuse locally, zero network, zero key consumption,
|
|
1883
|
+
// zero auto-register attempt. T-c8j-01 mitigation.
|
|
1884
|
+
return { error: 'invalid_problem_type', tool: 'recommend_chain' };
|
|
1885
|
+
}
|
|
1886
|
+
const steps = Number.isInteger(maxSteps) && maxSteps >= 1 && maxSteps <= 6 ? maxSteps : 6;
|
|
1887
|
+
return callTool('recommend_chain', { problem_type: normalized, max_steps: steps });
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// Phase 252-01 (SWEEP-01, guard sweep) -- getTier0Chain() / getFrameworkChain()
|
|
1891
|
+
// DELETED here. They served hardcoded, non-graph-grounded persona chains
|
|
1892
|
+
// with `source: 'tier0'` -- the counterfeit core the 250-01 doctrine kill
|
|
1893
|
+
// list (site #11) marked but deferred flipping. Live grep at 252 execution
|
|
1894
|
+
// time confirmed zero CJS consumers outside this file; their only consumer
|
|
1895
|
+
// was the conversation-mode SKILL instruction (skills/conversation-mode/
|
|
1896
|
+
// SKILL.md), rewritten in this same task to follow the refusal rail instead
|
|
1897
|
+
// of calling either function. The 247-02 do-not-widen note on the null
|
|
1898
|
+
// branch (`callTool()`'s transport-failure contract) is untouched -- this
|
|
1899
|
+
// deletion removes a local fallback CHAIN, not the null contract.
|
|
1900
|
+
|
|
1901
|
+
// ============================================================================
|
|
1902
|
+
// Phase 110-03: Brain Context Packet wire enforcement (Canon Part 8 + Part 9).
|
|
1903
|
+
// ============================================================================
|
|
1904
|
+
//
|
|
1905
|
+
// sendPacket(packet, opts) is the SOLE typed-packet wire path. It runs:
|
|
1906
|
+
// (1) D-08 layer-3 origin allowlist (the belt to the schema's suspenders --
|
|
1907
|
+
// a caller who bypassed ajv still gets caught here).
|
|
1908
|
+
// (2) Unknown-job guard against the closed D-02 vocabulary (12 jobs).
|
|
1909
|
+
// (3) ajv in-validation (reject hard: throw + log brain_packet_rejected).
|
|
1910
|
+
// (4) POST tools/call name:'brain_packet' via the existing callTool transport
|
|
1911
|
+
// (degrade soft when the tool is absent: { advice:null, reason:
|
|
1912
|
+
// 'brain_packet_tool_absent' }; on transport error: { advice:null,
|
|
1913
|
+
// reason: 'brain_unreachable' }). Never throws on Brain-side problems.
|
|
1914
|
+
// (5) ajv out-validation (reject hard, degrade soft: log
|
|
1915
|
+
// brain_response_rejected + return { advice:null, reason:
|
|
1916
|
+
// 'response_schema_invalid' }). Never throws, never partial-ingests.
|
|
1917
|
+
// (6) Returns the validated out payload on success.
|
|
1918
|
+
//
|
|
1919
|
+
// ajv@8.18.0 is transitive via @modelcontextprotocol/sdk -- do NOT add it to
|
|
1920
|
+
// package.json (CLAUDE.md "What NOT to Use"). strict:false at runtime; the
|
|
1921
|
+
// strict:true build gate lives in scripts/build-brain-packet-schema.cjs. The
|
|
1922
|
+
// schema is draft 2020-12, so Ajv2020 is required (ajv/dist/2020).
|
|
1923
|
+
//
|
|
1924
|
+
// SHIPPED_JOBS is the D-02 closed vocabulary (locked in 110-CONTEXT D-02).
|
|
1925
|
+
|
|
1926
|
+
const Ajv2020 = require('ajv/dist/2020').default || require('ajv/dist/2020');
|
|
1927
|
+
|
|
1928
|
+
const _BRAIN_PACKET_SCHEMA_PATH = process.env.MINDRIAN_BRAIN_PACKET_SCHEMA
|
|
1929
|
+
|| path.join(__dirname, '..', '..', 'data', 'brain-packet-schema.json');
|
|
1930
|
+
|
|
1931
|
+
const SHIPPED_JOBS = new Set([
|
|
1932
|
+
'select_methodology',
|
|
1933
|
+
'suggest_next_move',
|
|
1934
|
+
'detect_contradiction',
|
|
1935
|
+
'summarize_neighborhood',
|
|
1936
|
+
'classify_room_budding',
|
|
1937
|
+
'rank_assumptions',
|
|
1938
|
+
'generate_feynman_explanation',
|
|
1939
|
+
'strengthen_minto',
|
|
1940
|
+
'prepare_investor_brief',
|
|
1941
|
+
'opportunity_react',
|
|
1942
|
+
'opportunity_reflect',
|
|
1943
|
+
'opportunity_rank',
|
|
1944
|
+
]);
|
|
1945
|
+
|
|
1946
|
+
let _ajv = null;
|
|
1947
|
+
let _schemaRaw = null;
|
|
1948
|
+
const _jobValidators = new Map(); // job -> { in: fn, out: fn }
|
|
1949
|
+
|
|
1950
|
+
/**
|
|
1951
|
+
* Lazily compile the brain-packet schema. Reads data/brain-packet-schema.json
|
|
1952
|
+
* (or process.env.MINDRIAN_BRAIN_PACKET_SCHEMA, the test seam set up by 110-01)
|
|
1953
|
+
* once at first use; subsequent calls reuse the in-memory Ajv instance.
|
|
1954
|
+
*
|
|
1955
|
+
* NOTE on the schema's pointer shape: the 110-01 schema's per-job $defs are
|
|
1956
|
+
* { in: {...}, out: {...} } -- NOT { properties: { in, out } }. So the JSON
|
|
1957
|
+
* pointer for a job half is #/$defs/<job>/<half>, not the planner's interfaces
|
|
1958
|
+
* sketch shape #/$defs/<job>/properties/<half>. Compiled wrappers carry the
|
|
1959
|
+
* root's $defs inline because ajv 8.x cannot resolve a deep JSON pointer into
|
|
1960
|
+
* a schema indexed only by its absolute $id (a known ajv@8 quirk -- the build
|
|
1961
|
+
* script in scripts/build-brain-packet-schema.cjs validates the root by
|
|
1962
|
+
* compiling it directly with the same Ajv2020 class).
|
|
1963
|
+
*/
|
|
1964
|
+
function _ensureSchema() {
|
|
1965
|
+
if (_ajv) return;
|
|
1966
|
+
_schemaRaw = JSON.parse(fs.readFileSync(_BRAIN_PACKET_SCHEMA_PATH, 'utf8'));
|
|
1967
|
+
_ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
1968
|
+
// Pre-add the root so cross-refs (FocusNode, Origin, BankedOpportunities,
|
|
1969
|
+
// BrainResponse, etc.) resolve when sub-schemas reference them. Wrapper
|
|
1970
|
+
// schemas below carry $defs inline as a defensive duplicate.
|
|
1971
|
+
_ajv.addSchema(_schemaRaw);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* Return the memoized in/out validator for a given (job, half). Half is
|
|
1976
|
+
* 'in' or 'out'. Compiles on first use.
|
|
1977
|
+
*
|
|
1978
|
+
* @param {string} job - D-02 jobname (must be in SHIPPED_JOBS).
|
|
1979
|
+
* @param {'in'|'out'} half
|
|
1980
|
+
* @returns {Function} a compiled ajv validator function.
|
|
1981
|
+
*/
|
|
1982
|
+
function _validatorFor(job, half) {
|
|
1983
|
+
_ensureSchema();
|
|
1984
|
+
let pair = _jobValidators.get(job);
|
|
1985
|
+
if (!pair) { pair = {}; _jobValidators.set(job, pair); }
|
|
1986
|
+
if (!pair[half]) {
|
|
1987
|
+
pair[half] = _ajv.compile({
|
|
1988
|
+
$id: 'urn:mindrian:brain-packet:' + job + ':' + half,
|
|
1989
|
+
$ref: '#/$defs/' + job + '/' + half,
|
|
1990
|
+
$defs: _schemaRaw.$defs,
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
return pair[half];
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Reset the lazy schema state. Test hook only -- NOT part of the public API.
|
|
1998
|
+
* Used by 110-05 (the per-job validation suite) and 110-04 (the pre-commit
|
|
1999
|
+
* tripwire) to swap the schema seam (MINDRIAN_BRAIN_PACKET_SCHEMA) and re-run.
|
|
2000
|
+
*/
|
|
2001
|
+
function _resetSchema() {
|
|
2002
|
+
_ajv = null;
|
|
2003
|
+
_schemaRaw = null;
|
|
2004
|
+
_jobValidators.clear();
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* Best-effort memory_event log. Skips silently if opts.db is absent OR if the
|
|
2009
|
+
* navigation re-export throws (Brain telemetry should never break a Brain call).
|
|
2010
|
+
*
|
|
2011
|
+
* @param {object} db - SQLite db handle from openRoomDb (or undefined).
|
|
2012
|
+
* @param {string} eventType - one of 'brain_packet_rejected' /
|
|
2013
|
+
* 'brain_response_rejected' / 'brain_legacy_path_used'
|
|
2014
|
+
* (Phase 110-02 added these to EVENT_TYPES).
|
|
2015
|
+
* @param {object} payload - { job, errors, source_path, ... } scalars only.
|
|
2016
|
+
*/
|
|
2017
|
+
function _logEventBestEffort(db, eventType, payload) {
|
|
2018
|
+
if (!db) return;
|
|
2019
|
+
try {
|
|
2020
|
+
// Lazy-require to avoid a circular load between brain-client.cjs and
|
|
2021
|
+
// navigation.cjs (navigation.cjs does NOT require brain-client.cjs today,
|
|
2022
|
+
// but this stays robust if a future closure of the loop ever happens).
|
|
2023
|
+
require('./navigation.cjs').logMemoryEvent(db, eventType, payload || {});
|
|
2024
|
+
} catch (_e) { /* best-effort */ }
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* Detect a "Brain doesn't recognize this tool" error in the callTool result.
|
|
2029
|
+
* Per the callTool JSDoc around line ~329, the result on a server-side error
|
|
2030
|
+
* is shaped { text: 'Error: ...' } (Cypher errors pass through the same way).
|
|
2031
|
+
* A missing brain_packet tool will show up as -32602 / 'unknown tool' / 'No
|
|
2032
|
+
* such tool' / a 404-ish marker. Be liberal: D-04 says "degrade gracefully
|
|
2033
|
+
* when the Brain doesn't recognize the contract."
|
|
2034
|
+
*
|
|
2035
|
+
* @param {*} result
|
|
2036
|
+
* @returns {boolean}
|
|
2037
|
+
*/
|
|
2038
|
+
function _looksLikeUnknownToolError(result) {
|
|
2039
|
+
if (!result) return false;
|
|
2040
|
+
let t = '';
|
|
2041
|
+
if (typeof result === 'string') t = result;
|
|
2042
|
+
else if (typeof result === 'object') t = result.text || result.error || JSON.stringify(result);
|
|
2043
|
+
const s = String(t).toLowerCase();
|
|
2044
|
+
return s.includes('unknown tool')
|
|
2045
|
+
|| s.includes('no such tool')
|
|
2046
|
+
|| s.includes('method not found')
|
|
2047
|
+
|| s.includes('-32602')
|
|
2048
|
+
|| (s.includes('brain_packet') && (s.includes('not') || s.includes('unknown')))
|
|
2049
|
+
|| s.includes('tool not found');
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/**
|
|
2053
|
+
* Unwrap the callTool result to the Brain response object the schema wants to
|
|
2054
|
+
* validate. callTool returns:
|
|
2055
|
+
* - an array (the tools/call content array on a successful brain_query-style
|
|
2056
|
+
* parsed result),
|
|
2057
|
+
* - or a parsed object (when content[0] is text and parses as JSON),
|
|
2058
|
+
* - or { text: '...' } (text content that did not parse as JSON, or an error
|
|
2059
|
+
* string),
|
|
2060
|
+
* - or null (unreachable / no API key -- handled before this is called).
|
|
2061
|
+
*
|
|
2062
|
+
* For brain_packet specifically, the Brain side (when implemented) will return
|
|
2063
|
+
* a BrainResponse-shaped JSON object: { job_id, suggestions: [...] }. Older
|
|
2064
|
+
* test transports inject [{ type:'text', text: JSON.stringify(...) }] (the raw
|
|
2065
|
+
* MCP content array shape). Both shapes are unwrapped to the response object.
|
|
2066
|
+
*
|
|
2067
|
+
* @param {*} result
|
|
2068
|
+
* @returns {object}
|
|
2069
|
+
*/
|
|
2070
|
+
function _parseBrainResult(result) {
|
|
2071
|
+
if (Array.isArray(result)) {
|
|
2072
|
+
for (const item of result) {
|
|
2073
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
2074
|
+
try { return JSON.parse(item.text); } catch (_) { /* fall through */ }
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return { suggestions: [] };
|
|
2078
|
+
}
|
|
2079
|
+
if (result && typeof result === 'object') {
|
|
2080
|
+
// text-only error/string shape -- try to parse, else return { suggestions: [] }
|
|
2081
|
+
if (typeof result.text === 'string' && !result.job_id && !result.suggestions) {
|
|
2082
|
+
try { return JSON.parse(result.text); } catch (_) { return { suggestions: [] }; }
|
|
2083
|
+
}
|
|
2084
|
+
return result;
|
|
2085
|
+
}
|
|
2086
|
+
if (typeof result === 'string') {
|
|
2087
|
+
try { return JSON.parse(result); } catch (_) { return { suggestions: [] }; }
|
|
2088
|
+
}
|
|
2089
|
+
return { suggestions: [] };
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// ----------------------------------------------------------------------------
|
|
2093
|
+
// _warnLegacyOnce -- the D-10 dual-path deprecation guard.
|
|
2094
|
+
//
|
|
2095
|
+
// As of v1.13.0-beta.3 there is NO legacy free-form Brain *job* call site --
|
|
2096
|
+
// new job-style work goes through sendPacket(). The guard ships now as a
|
|
2097
|
+
// forward-looking contract: if a free-form job helper is ever added before
|
|
2098
|
+
// v1.14.0, it MUST call _warnLegacyOnce() first; in v1.14.0 both the helper
|
|
2099
|
+
// and this guard are deleted.
|
|
2100
|
+
//
|
|
2101
|
+
// query() / write() / search() / schema() / callTool() / ask() / stats() are
|
|
2102
|
+
// NOT "legacy" -- raw-Cypher methodology lookups carry only generic handles
|
|
2103
|
+
// (framework names, phase identifiers, problem-type enums) and are Part-8
|
|
2104
|
+
// clean by construction; they are PERMANENT.
|
|
2105
|
+
//
|
|
2106
|
+
// Module-level flag idiom mirrors checkFilePermissions._warned (the existing
|
|
2107
|
+
// once-per-process warning pattern in this file).
|
|
2108
|
+
// ----------------------------------------------------------------------------
|
|
2109
|
+
|
|
2110
|
+
let _legacyPathWarned = false;
|
|
2111
|
+
|
|
2112
|
+
function _warnLegacyOnce(db) {
|
|
2113
|
+
if (_legacyPathWarned) return;
|
|
2114
|
+
_legacyPathWarned = true;
|
|
2115
|
+
// eslint-disable-next-line no-console
|
|
2116
|
+
console.warn('[mindrian-os] legacy free-form Brain job call detected. '
|
|
2117
|
+
+ 'Migrate to brain-client.sendPacket() -- the legacy job path is removed in v1.14.0.');
|
|
2118
|
+
_logEventBestEffort(db, 'brain_legacy_path_used', {
|
|
2119
|
+
source_path: 'system:brain-legacy',
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/**
|
|
2124
|
+
* Validate-then-route a Brain Context Packet. The ONLY door for typed Brain
|
|
2125
|
+
* job calls.
|
|
2126
|
+
*
|
|
2127
|
+
* Per CONTEXT D-07 "reject hard, degrade soft":
|
|
2128
|
+
* - Bad in-packet (a programmer error in OUR code) -> throw.
|
|
2129
|
+
* - Bad origin (D-08 layer 3) -> throw.
|
|
2130
|
+
* - Unknown job -> throw.
|
|
2131
|
+
* - Brain unreachable -> { advice: null, reason: 'brain_unreachable' } (no throw).
|
|
2132
|
+
* - brain_packet tool absent on the Brain -> { advice: null, reason:
|
|
2133
|
+
* 'brain_packet_tool_absent' } (no throw, no log -- D-04 graceful degrade
|
|
2134
|
+
* is NOT a bad-response situation; a half-trusted response IS one).
|
|
2135
|
+
* - Bad out-response -> { advice: null, reason: 'response_schema_invalid' }
|
|
2136
|
+
* + log brain_response_rejected. NEVER throws, NEVER partial-ingests.
|
|
2137
|
+
*
|
|
2138
|
+
* @param {object} packet - MUST come from lib/core/navigation.cjs::buildBrainPacket
|
|
2139
|
+
* (D-01 chokepoint; D-08 layer 2 enforces lexically;
|
|
2140
|
+
* D-08 layer 3 is the origin check below).
|
|
2141
|
+
* @param {object} [opts] - { db?, roomDir?, __transport? }
|
|
2142
|
+
* db -> the SQLite handle for the memory_event log
|
|
2143
|
+
* roomDir -> reserved for future use; not read today
|
|
2144
|
+
* __transport -> optional test seam: a function
|
|
2145
|
+
* (toolName, args) => Promise<result> that
|
|
2146
|
+
* replaces callTool (lets tests inject a
|
|
2147
|
+
* fake without require.cache surgery)
|
|
2148
|
+
* @returns {Promise<object>} the validated Brain out on success; a no-advice
|
|
2149
|
+
* sentinel on a soft-degrade.
|
|
2150
|
+
* @throws on a bad in-packet, a bad origin (D-08 layer 3), or an unknown job.
|
|
2151
|
+
*/
|
|
2152
|
+
// PARKED (2026-07-30, Phase 239, BRAIN-03): ZERO production sendPacket(
|
|
2153
|
+
// consumers. Census across lib/, scripts/, bin/, pipelines/: the only
|
|
2154
|
+
// definition is this one; non-definition references are this file's own
|
|
2155
|
+
// export/comments, tests/test-brain-packet-validation-per-job.cjs, and the
|
|
2156
|
+
// D-08 layer-2 guard (scripts/check-schema-aliases.cjs --check-sendpacket).
|
|
2157
|
+
// This reconciles two prior contradictory claims: navigation/packet.cjs:105
|
|
2158
|
+
// ("zero production consumers today") was TRUE; test-150-brain-egress.cjs:12
|
|
2159
|
+
// ("FIRST real sendPacket consumer") was FALSE and is corrected in this
|
|
2160
|
+
// change. RULING: PARKED, not wired -- wiring it to a real job is net-new
|
|
2161
|
+
// feature work, forbidden inside this remediation-only milestone (RESEARCH.md
|
|
2162
|
+
// A3, a navigator-equivalent ruling, cheap to overturn). CONSEQUENCE: the
|
|
2163
|
+
// PB8-10 belt below (step 3.5) is correct code on an unreached path -- do NOT
|
|
2164
|
+
// count it as live Part 8 coverage; the live in-process coverage is sibling
|
|
2165
|
+
// plan 239-05's raw-field guard in hatAwareRecommend()/suggestValidationSteps().
|
|
2166
|
+
// RE-OPEN CONDITION: the first real caller. Caught by the D-08 layer-2
|
|
2167
|
+
// pre-commit guard (requires a preceding buildBrainPacket() call) and by
|
|
2168
|
+
// tests/test-239-sendpacket-parked.cjs LEG 1's census. See also the matching
|
|
2169
|
+
// ADR amendment in docs/architecture/SUBSTRATE-CONTRACT.md (Phase 239-06).
|
|
2170
|
+
async function sendPacket(packet, opts) {
|
|
2171
|
+
const o = opts || {};
|
|
2172
|
+
|
|
2173
|
+
// (1) D-08 layer 3 -- the belt to the schema's suspenders. Runs FIRST so a
|
|
2174
|
+
// caller who bypassed ajv still gets caught.
|
|
2175
|
+
if (!packet || typeof packet.origin !== 'string') {
|
|
2176
|
+
throw new Error('brain packet missing origin (D-08): packets must come from buildBrainPacket');
|
|
2177
|
+
}
|
|
2178
|
+
if (packet.origin === 'test_fixture' && process.env.MINDRIAN_TEST_MODE !== '1') {
|
|
2179
|
+
throw new Error('brain packet origin "test_fixture" only valid when MINDRIAN_TEST_MODE=1');
|
|
2180
|
+
}
|
|
2181
|
+
if (packet.origin !== 'navigation_api' && packet.origin !== 'test_fixture') {
|
|
2182
|
+
throw new Error('brain packet origin "' + packet.origin
|
|
2183
|
+
+ '" not in the closed allowlist (D-08): navigation_api | test_fixture');
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// (2) Unknown-job guard.
|
|
2187
|
+
if (!SHIPPED_JOBS.has(packet.job)) {
|
|
2188
|
+
throw new Error('brain packet: unknown job "' + packet.job
|
|
2189
|
+
+ '" (not in the D-02 closed vocabulary)');
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// (3) in-validation -- reject hard.
|
|
2193
|
+
const inFn = _validatorFor(packet.job, 'in');
|
|
2194
|
+
if (!inFn(packet)) {
|
|
2195
|
+
// Pitfall 1: snapshot errors immediately -- validate.errors is overwritten
|
|
2196
|
+
// on every subsequent validate() call.
|
|
2197
|
+
const errsSnapshot = (inFn.errors || []).slice();
|
|
2198
|
+
const errStr = errsSnapshot
|
|
2199
|
+
.map(function (e) { return (e.instancePath || '(root)') + ' ' + (e.message || ''); })
|
|
2200
|
+
.join('; ');
|
|
2201
|
+
_logEventBestEffort(o.db, 'brain_packet_rejected', {
|
|
2202
|
+
job: packet.job,
|
|
2203
|
+
errors: errsSnapshot.length,
|
|
2204
|
+
source_path: 'system:brain-packet',
|
|
2205
|
+
});
|
|
2206
|
+
throw new Error('brain packet rejected for job "' + packet.job + '": ' + errStr);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// (3.5) PB8-10 -- the in-sendPacket defense-in-depth belt. sendPacket is the
|
|
2210
|
+
// SOLE typed-packet wire path; this is the last LOCAL check before the
|
|
2211
|
+
// packet goes on the wire. It COMPLEMENTS the required PreToolUse hook
|
|
2212
|
+
// (D-02, the primary), never replaces it. Runs the pure LOCAL classifier
|
|
2213
|
+
// (zero network, zero judge wire, D-01):
|
|
2214
|
+
// - 'block' verdict -> refuse the send, returning the same soft-degrade
|
|
2215
|
+
// sentinel the unreachable path uses; best-effort log brain_egress_blocked.
|
|
2216
|
+
// - 'ambiguous' verdict -> belt posture: the hook is the gate, so do NOT
|
|
2217
|
+
// double-prompt; best-effort log brain_egress_ambiguous and allow through.
|
|
2218
|
+
// The whole belt is wrapped so a belt-INTERNAL error degrades to the
|
|
2219
|
+
// existing behavior (the hook remains the primary). It NEVER throws and
|
|
2220
|
+
// NEVER opens a Brain wire to judge.
|
|
2221
|
+
try {
|
|
2222
|
+
const _guard = require('./part8-egress-guard.cjs');
|
|
2223
|
+
const _verdict = _guard.classify(packet, { toolName: 'brain_packet' });
|
|
2224
|
+
if (_verdict && _verdict.verdict === 'block') {
|
|
2225
|
+
_logEventBestEffort(o.db, 'brain_egress_blocked', {
|
|
2226
|
+
egress_class: _verdict.class || 'content_set',
|
|
2227
|
+
verdict: 'block',
|
|
2228
|
+
count: 1,
|
|
2229
|
+
created_by: 'system',
|
|
2230
|
+
source_path: 'system:brain-packet',
|
|
2231
|
+
});
|
|
2232
|
+
return { advice: null, reason: 'egress_blocked' };
|
|
2233
|
+
}
|
|
2234
|
+
if (_verdict && _verdict.verdict === 'ambiguous') {
|
|
2235
|
+
_logEventBestEffort(o.db, 'brain_egress_ambiguous', {
|
|
2236
|
+
egress_class: _verdict.class || 'unknown',
|
|
2237
|
+
verdict: 'ambiguous',
|
|
2238
|
+
count: 1,
|
|
2239
|
+
created_by: 'system',
|
|
2240
|
+
source_path: 'system:brain-packet',
|
|
2241
|
+
});
|
|
2242
|
+
// belt posture: allow the hook to have been the gate; do not double-prompt.
|
|
2243
|
+
}
|
|
2244
|
+
} catch (_e) {
|
|
2245
|
+
// belt-internal error degrades to existing behavior (the hook is the primary, D-02).
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// (4) Build the wire envelope + POST. Reuse callTool's transport (it does
|
|
2249
|
+
// tools/call over Streamable HTTP + SSE-parse). Tests can inject a fake
|
|
2250
|
+
// via opts.__transport.
|
|
2251
|
+
const transport = (typeof o.__transport === 'function') ? o.__transport : callTool;
|
|
2252
|
+
let result;
|
|
2253
|
+
try {
|
|
2254
|
+
result = await transport('brain_packet', { packet: packet });
|
|
2255
|
+
} catch (_e) {
|
|
2256
|
+
// Network / transport error: treat like the Brain being unreachable.
|
|
2257
|
+
// No log -- it is not a leak, and not a contract violation either.
|
|
2258
|
+
return { advice: null, reason: 'brain_unreachable' };
|
|
2259
|
+
}
|
|
2260
|
+
if (result == null) {
|
|
2261
|
+
// callTool returns null when there is no API key or when the HTTP layer
|
|
2262
|
+
// returned a non-ok status -- functionally the same as "Brain unreachable".
|
|
2263
|
+
return { advice: null, reason: 'brain_unreachable' };
|
|
2264
|
+
}
|
|
2265
|
+
if (_looksLikeUnknownToolError(result)) {
|
|
2266
|
+
// The live Brain has no brain_packet tool yet (D-04 generalized).
|
|
2267
|
+
// Degrade soft, NO brain_response_rejected log -- this is NOT a bad
|
|
2268
|
+
// response; it is the absence of a contract handler.
|
|
2269
|
+
return { advice: null, reason: 'brain_packet_tool_absent' };
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// (5) out-validation -- reject hard but degrade soft.
|
|
2273
|
+
const parsed = _parseBrainResult(result);
|
|
2274
|
+
const outFn = _validatorFor(packet.job, 'out');
|
|
2275
|
+
if (!outFn(parsed)) {
|
|
2276
|
+
const errsSnapshot = (outFn.errors || []).slice();
|
|
2277
|
+
_logEventBestEffort(o.db, 'brain_response_rejected', {
|
|
2278
|
+
job: packet.job,
|
|
2279
|
+
errors: errsSnapshot.length,
|
|
2280
|
+
source_path: 'system:brain-packet',
|
|
2281
|
+
});
|
|
2282
|
+
// NEVER throw on a bad response; NEVER partial-ingest.
|
|
2283
|
+
return { advice: null, reason: 'response_schema_invalid' };
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// (6) Success -- the caller gets a known-good shape (typically then calls
|
|
2287
|
+
// navigation.storeBrainSuggestions).
|
|
2288
|
+
return parsed;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
module.exports = {
|
|
2292
|
+
isAvailable,
|
|
2293
|
+
ensureAvailable,
|
|
2294
|
+
getAutoRegisterFailureReason,
|
|
2295
|
+
getApiKey,
|
|
2296
|
+
callTool,
|
|
2297
|
+
query,
|
|
2298
|
+
write,
|
|
2299
|
+
search,
|
|
2300
|
+
smartSearch,
|
|
2301
|
+
ask,
|
|
2302
|
+
askOp,
|
|
2303
|
+
schema,
|
|
2304
|
+
stats,
|
|
2305
|
+
getBrainUrl,
|
|
2306
|
+
// Phase 339 (FLIP-02): the set of Brain origins whose problem-type
|
|
2307
|
+
// vocabulary is Theo's. Exported so class-m-brain-smoke.cjs's per-origin
|
|
2308
|
+
// node floor (plan 339-12) keys on this SAME set rather than minting a
|
|
2309
|
+
// second copy.
|
|
2310
|
+
THEO_ORIGINS,
|
|
2311
|
+
enrichCausalEdges,
|
|
2312
|
+
hatAwareRecommend,
|
|
2313
|
+
suggestValidationSteps,
|
|
2314
|
+
// getFrameworkChain: DELETED (Phase 252-01, SWEEP-01) -- the counterfeit
|
|
2315
|
+
// Tier-0 hardcoded-chain fallback. Zero CJS consumers at deletion time.
|
|
2316
|
+
// Phase 247-02 (CONTRACT-01): loop-contract read wrappers. brain_stats is
|
|
2317
|
+
// the sixth loop tool and is already exported above as stats().
|
|
2318
|
+
normalizeFrameworkName,
|
|
2319
|
+
loopSearch,
|
|
2320
|
+
discoverStructure,
|
|
2321
|
+
orchestrationReadiness,
|
|
2322
|
+
feedsIntoChains,
|
|
2323
|
+
// Quick 260819-c8j (WS-C1): recommend_chain is the seventh loop-contract
|
|
2324
|
+
// read wrapper, a sibling of feedsIntoChains above.
|
|
2325
|
+
recommendChain,
|
|
2326
|
+
// Phase 110-03 (Brain Context Packet Contract wire-level enforcement):
|
|
2327
|
+
// sendPacket is the SOLE typed-packet wire path; _warnLegacyOnce is the
|
|
2328
|
+
// forward-looking D-10 deprecation guard with no current call site.
|
|
2329
|
+
sendPacket,
|
|
2330
|
+
_warnLegacyOnce,
|
|
2331
|
+
// SEC-01/SEC-02 + CASCADE-06 test surface: not part of the public API.
|
|
2332
|
+
// See lib/memory/security-trifecta.test.cjs + brain-cache-lru.test.cjs.
|
|
2333
|
+
// Helpers are small and pure. sessionCache + _ensureSession + _hashKey +
|
|
2334
|
+
// SESSION_TTL_MS are exposed for Phase 87-07 cache-behavior tests.
|
|
2335
|
+
// Phase 110-03: ajv middleware test seam (_resetSchema clears the lazy
|
|
2336
|
+
// ajv state so a test can swap MINDRIAN_BRAIN_PACKET_SCHEMA and re-run;
|
|
2337
|
+
// _setLegacyWarned resets the once-per-session deprecation flag;
|
|
2338
|
+
// _validatorFor / _parseBrainResult / _looksLikeUnknownToolError are
|
|
2339
|
+
// the helpers Plan 110-05 covers in its per-job round-trip suite).
|
|
2340
|
+
_test: {
|
|
2341
|
+
sanitizeCypherInput,
|
|
2342
|
+
checkFilePermissions,
|
|
2343
|
+
sessionCache,
|
|
2344
|
+
SESSION_TTL_MS,
|
|
2345
|
+
_hashKey,
|
|
2346
|
+
_ensureSession,
|
|
2347
|
+
_resetSchema,
|
|
2348
|
+
_validatorFor,
|
|
2349
|
+
_parseBrainResult,
|
|
2350
|
+
_looksLikeUnknownToolError,
|
|
2351
|
+
_ensureSchema,
|
|
2352
|
+
SHIPPED_JOBS,
|
|
2353
|
+
_setLegacyWarned: function (v) { _legacyPathWarned = !!v; },
|
|
2354
|
+
// Quick 260903-eit: reset seam for query()'s unrecognized-shape
|
|
2355
|
+
// warn-once flag, mirroring _setLegacyWarned above.
|
|
2356
|
+
_setQueryShapeWarned: function (v) { _queryShapeWarned = !!v; },
|
|
2357
|
+
// Phase 250-04 test surface: silent registration internals.
|
|
2358
|
+
_tryAutoRegister,
|
|
2359
|
+
_installTokenPath,
|
|
2360
|
+
// Phase 259 (TRUST-01) test surface: the pure 429 wait-schedule helpers,
|
|
2361
|
+
// so the exact D-01/D-02 schedule is unit-tested with zero sleeps.
|
|
2362
|
+
_parseRetryAfterMs,
|
|
2363
|
+
_rateLimitWaitMs,
|
|
2364
|
+
},
|
|
2365
|
+
};
|