@mindrian_os/install 1.13.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +9 -0
- package/CHANGELOG.md +3333 -0
- package/LICENSE +123 -0
- package/README.md +673 -0
- package/agents/brain-query.md +80 -0
- package/agents/framework-runner.md +237 -0
- package/agents/grading.md +188 -0
- package/agents/investor.md +128 -0
- package/agents/larry-extended.md +135 -0
- package/agents/opportunity-scanner.md +91 -0
- package/agents/persona-analyst.md +132 -0
- package/agents/research.md +89 -0
- package/agents/reverse-salient-agent.md +27 -0
- package/bin/cli.js +142 -0
- package/bin/mindrian-mcp-server.cjs +182 -0
- package/bin/mindrian-tools.cjs +765 -0
- package/commands/act.md +439 -0
- package/commands/admin.md +404 -0
- package/commands/analyze-needs.md +42 -0
- package/commands/analyze-systems.md +39 -0
- package/commands/analyze-timing.md +42 -0
- package/commands/auto-explore.md +64 -0
- package/commands/beautiful-question.md +40 -0
- package/commands/brain-derive.md +78 -0
- package/commands/build-knowledge.md +42 -0
- package/commands/build-thesis.md +46 -0
- package/commands/causal.md +234 -0
- package/commands/challenge-assumptions.md +33 -0
- package/commands/compare-ventures.md +83 -0
- package/commands/dashboard.md +110 -0
- package/commands/deep-grade.md +82 -0
- package/commands/diagnose.md +58 -0
- package/commands/diagnostics.md +151 -0
- package/commands/doctor.md +151 -0
- package/commands/dominant-designs.md +40 -0
- package/commands/explain-decision.md +87 -0
- package/commands/explore-domains.md +42 -0
- package/commands/explore-futures.md +40 -0
- package/commands/explore-trends.md +42 -0
- package/commands/export.md +103 -0
- package/commands/file-meeting.md +724 -0
- package/commands/find-analogies.md +188 -0
- package/commands/find-bottlenecks.md +62 -0
- package/commands/find-connections.md +76 -0
- package/commands/funding.md +81 -0
- package/commands/grade.md +203 -0
- package/commands/graph.md +128 -0
- package/commands/hat-briefing.md +125 -0
- package/commands/heal.md +196 -0
- package/commands/help.md +399 -0
- package/commands/hmi-status.md +172 -0
- package/commands/jtbd.md +241 -0
- package/commands/leadership.md +73 -0
- package/commands/lean-canvas.md +40 -0
- package/commands/macro-trends.md +40 -0
- package/commands/map-unknowns.md +40 -0
- package/commands/memory.md +173 -0
- package/commands/models.md +175 -0
- package/commands/mos-reason.md +285 -0
- package/commands/mullins.md +120 -0
- package/commands/new-project.md +481 -0
- package/commands/onboard.md +434 -0
- package/commands/operator.md +149 -0
- package/commands/opportunities.md +144 -0
- package/commands/organize.md +497 -0
- package/commands/persona.md +198 -0
- package/commands/pipeline.md +112 -0
- package/commands/present.md +91 -0
- package/commands/publish.md +201 -0
- package/commands/query.md +124 -0
- package/commands/radar.md +72 -0
- package/commands/reanalyze.md +91 -0
- package/commands/research.md +196 -0
- package/commands/room.md +352 -0
- package/commands/rooms.md +598 -0
- package/commands/root-cause.md +40 -0
- package/commands/rs-experts.md +85 -0
- package/commands/rs-explain.md +100 -0
- package/commands/rs-fetch.md +94 -0
- package/commands/rs-thesis.md +85 -0
- package/commands/scenario-plan.md +40 -0
- package/commands/scheduled-tasks.md +285 -0
- package/commands/score-innovation.md +43 -0
- package/commands/scout.md +239 -0
- package/commands/setup.md +618 -0
- package/commands/snapshot.md +147 -0
- package/commands/speakers.md +84 -0
- package/commands/splash.md +28 -0
- package/commands/status.md +75 -0
- package/commands/structure-argument.md +42 -0
- package/commands/suggest-next.md +80 -0
- package/commands/systems-thinking.md +40 -0
- package/commands/think-hats.md +42 -0
- package/commands/update.md +181 -0
- package/commands/user-needs.md +40 -0
- package/commands/validate.md +40 -0
- package/commands/value-proposition.md +61 -0
- package/commands/vault.md +180 -0
- package/commands/visualize.md +52 -0
- package/commands/whitespace.md +507 -0
- package/commands/wiki.md +69 -0
- package/hooks/hooks.json +381 -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 +1043 -0
- package/lib/agents/reverse-salient-agent.cjs +679 -0
- package/lib/agents/tension-hook-agent.cjs +544 -0
- package/lib/brain/ROOM.md +44 -0
- package/lib/brain/chain-recommender.cjs +301 -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.cjs +287 -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_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/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/brain-client.cjs +865 -0
- package/lib/core/brain-derivation-prompts.cjs +326 -0
- package/lib/core/brain-derivation-queue.cjs +431 -0
- package/lib/core/brain-derivation.cjs +580 -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 +188 -0
- package/lib/core/bridge-writer.cjs +477 -0
- package/lib/core/chat-context-builder.cjs +253 -0
- package/lib/core/cross-room-aggregator.cjs +762 -0
- package/lib/core/daily-briefing.cjs +438 -0
- package/lib/core/decision-capture.cjs +618 -0
- package/lib/core/deep-links.cjs +82 -0
- package/lib/core/dispatch-optimizer.cjs +354 -0
- package/lib/core/dual-path-detector.cjs +84 -0
- package/lib/core/dual-path-detector.test.cjs +334 -0
- package/lib/core/exports-log.cjs +79 -0
- package/lib/core/feynman-minto-invariants.cjs +605 -0
- package/lib/core/folder-memory-async.cjs +338 -0
- package/lib/core/folder-memory-shared.cjs +890 -0
- package/lib/core/folder-memory.cjs +416 -0
- package/lib/core/framework-chain-composer.cjs +411 -0
- package/lib/core/frontmatter-schemas.cjs +330 -0
- package/lib/core/git-ops.cjs +141 -0
- package/lib/core/graph-ops.cjs +258 -0
- package/lib/core/hat-persistence.cjs +362 -0
- package/lib/core/index.cjs +60 -0
- package/lib/core/integration-registry.cjs +232 -0
- package/lib/core/intelligence-cascade.cjs +661 -0
- package/lib/core/lazygraph-ops.cjs +1057 -0
- package/lib/core/lru-cache.cjs +139 -0
- package/lib/core/mcp-profiles.cjs +182 -0
- package/lib/core/meeting-ops.cjs +54 -0
- package/lib/core/memory-ops.cjs +600 -0
- package/lib/core/migrations/ROOM.md +33 -0
- package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
- package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
- package/lib/core/model-profiles.cjs +246 -0
- package/lib/core/mullins-scaffold.cjs +160 -0
- package/lib/core/nav-dial.cjs +316 -0
- package/lib/core/navigation/ROOM.md +15 -0
- package/lib/core/navigation/explanation.cjs +43 -0
- package/lib/core/navigation/focus.cjs +135 -0
- package/lib/core/navigation/ingestion.cjs +82 -0
- package/lib/core/navigation/insights.cjs +350 -0
- package/lib/core/navigation/memory-events.cjs +118 -0
- package/lib/core/navigation/neighborhood.cjs +78 -0
- package/lib/core/navigation/packet.cjs +182 -0
- package/lib/core/navigation/room-home.cjs +127 -0
- package/lib/core/navigation/transitions.cjs +82 -0
- package/lib/core/navigation-engine-shared.cjs +242 -0
- package/lib/core/navigation-engine.cjs +664 -0
- package/lib/core/navigation.cjs +60 -0
- package/lib/core/nl-graph-queries.cjs +164 -0
- package/lib/core/offer-presenter.cjs +406 -0
- package/lib/core/opportunity-extractor.cjs +183 -0
- package/lib/core/opportunity-ops.cjs +1371 -0
- package/lib/core/persona-ops.cjs +537 -0
- package/lib/core/persona-taxonomy.cjs +190 -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/reasoning-ops.cjs +639 -0
- package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
- package/lib/core/room-classifier-strict-mode.cjs +229 -0
- package/lib/core/room-db.cjs +127 -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-type-detector.cjs +386 -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 +343 -0
- package/lib/core/rs-commercial-assessor.cjs +280 -0
- package/lib/core/rs-differential-scorer.cjs +376 -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-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-mind-map.cjs +656 -0
- package/lib/core/rs-neo4j-writer.cjs +388 -0
- package/lib/core/rs-nl-to-query.cjs +425 -0
- package/lib/core/rs-pinecone-bridge.cjs +303 -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 +443 -0
- package/lib/core/rs-thesis-generator.cjs +188 -0
- package/lib/core/rs_cache.py +479 -0
- package/lib/core/rs_corpus.py +468 -0
- package/lib/core/rs_hybrid.py +586 -0
- package/lib/core/rs_math.py +287 -0
- package/lib/core/rs_rooms.py +193 -0
- package/lib/core/scheduled-scanner.cjs +463 -0
- package/lib/core/scratchpad-ops.cjs +201 -0
- package/lib/core/section-8-trace-schema.cjs +138 -0
- package/lib/core/section-registry.cjs +111 -0
- package/lib/core/session-state.cjs +144 -0
- package/lib/core/shallow-doc-parser.cjs +174 -0
- package/lib/core/shallow-doc-parser.test.cjs +226 -0
- package/lib/core/skill-activation-router.cjs +284 -0
- package/lib/core/state-ops.cjs +46 -0
- package/lib/core/statusline-cache.cjs +266 -0
- package/lib/core/token-estimator.cjs +348 -0
- package/lib/core/user-archetype.cjs +239 -0
- package/lib/core/user-md-ops.cjs +524 -0
- package/lib/core/visual-ops.cjs +624 -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 +604 -0
- package/lib/hmi/cross-room-memory.cjs +575 -0
- package/lib/hmi/decoy-tier.cjs +395 -0
- package/lib/hmi/jtbd-classifier.cjs +219 -0
- package/lib/hmi/jtbd-state.cjs +199 -0
- package/lib/hmi/jtbd-taxonomy.json +392 -0
- package/lib/hmi/selector-dispatcher.cjs +546 -0
- package/lib/hmi/selector-telemetry.cjs +263 -0
- package/lib/hmi/shape-f0-renderer.cjs +139 -0
- package/lib/hmi/shape-f1-fallback.cjs +80 -0
- package/lib/hmi/shape-f1-renderer.cjs +138 -0
- package/lib/hmi/shape-f2-renderer.cjs +132 -0
- package/lib/hmi/shape-f3-renderer.cjs +66 -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 +312 -0
- package/lib/hmi/shape-f6-renderer.cjs +144 -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/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/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
- package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
- package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
- package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
- package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
- package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
- package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
- package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -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/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-router.cjs +418 -0
- package/lib/mcp/capability-registry.cjs +62 -0
- package/lib/mcp/larry-context.cjs +46 -0
- package/lib/mcp/larry-server-instructions.md +114 -0
- package/lib/mcp/pipeline-state.cjs +275 -0
- package/lib/mcp/prompts.cjs +302 -0
- package/lib/mcp/resources.cjs +227 -0
- package/lib/mcp/session-catchup.cjs +327 -0
- package/lib/mcp/surface-detect.cjs +75 -0
- package/lib/mcp/tool-router.cjs +1034 -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/brain-cache-lru.test.cjs +259 -0
- package/lib/memory/brain-client-query-shape.test.cjs +160 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
- package/lib/memory/brain-derivation-queue.test.cjs +539 -0
- package/lib/memory/brain-derivation.test.cjs +634 -0
- package/lib/memory/brain-derive-command.test.cjs +534 -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/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/cross-room-aggregator.test.cjs +909 -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/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/feynman-minto-guardian.test.cjs +736 -0
- package/lib/memory/feynman-minto-invariants.test.cjs +511 -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/folder-memory-quadruple.test.cjs +548 -0
- package/lib/memory/folder-memory.test.cjs +503 -0
- package/lib/memory/framework-chain-composer.test.cjs +515 -0
- package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
- package/lib/memory/heal-command.test.cjs +604 -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/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-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 +722 -0
- package/lib/memory/navigation-invariants.test.cjs +483 -0
- package/lib/memory/offer-presenter.test.cjs +554 -0
- package/lib/memory/on-stop-snapshot.test.cjs +404 -0
- package/lib/memory/pending-tension-store.cjs +373 -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/problem-type-router.test.cjs +656 -0
- package/lib/memory/query-efficiency-telemetry.test.cjs +370 -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/room-classifier-strict-mode.test.cjs +417 -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 +1247 -0
- package/lib/memory/security-trifecta.test.cjs +312 -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-minto-banner.test.cjs +373 -0
- package/lib/memory/skill-activation-router.test.cjs +419 -0
- package/lib/memory/stamp-artifact-write.test.cjs +304 -0
- package/lib/memory/statusline-active-room.test.cjs +315 -0
- package/lib/memory/statusline-minto-segment.test.cjs +292 -0
- package/lib/memory/sync-async-entry-points.test.cjs +204 -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 +603 -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 +277 -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 +690 -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/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 +59 -0
- package/lib/render/render-v2.cjs +486 -0
- package/lib/render/render-v2.test.cjs +267 -0
- package/lib/render/render.cjs +65 -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/surface-detect.cjs +85 -0
- package/lib/update-bootstrap.sh.template +145 -0
- package/lib/vault/frontmatter-schema.cjs +297 -0
- package/lib/vault/room-scanner.cjs +352 -0
- package/lib/vault/wikilink-builder.cjs +231 -0
- package/lib/vault/wikilink-builder.test.cjs +182 -0
- package/lib/wiki/graph-links.cjs +281 -0
- package/lib/wiki/page-renderer.cjs +229 -0
- package/lib/wiki/wiki-chat.cjs +81 -0
- package/lib/wiki/wiki-layout.cjs +1459 -0
- package/lib/wiki/wiki-search.cjs +142 -0
- package/lib/wiki/wiki-server.cjs +678 -0
- package/lib/wiki/wiki-watcher.cjs +105 -0
- package/lib/workflow/ROOM.md +47 -0
- package/lib/workflow/command-resolver.cjs +155 -0
- package/lib/workflow/command-resolver.test.cjs +235 -0
- package/package.json +44 -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 +56 -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 +37 -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 +37 -0
- package/references/brain/causal-directives.md +91 -0
- package/references/brain/causal-enrichment.cypher +165 -0
- package/references/brain/command-triggers-schema.md +226 -0
- package/references/brain/graph-architecture.md +317 -0
- package/references/brain/query-patterns.md +460 -0
- package/references/brain/room-hierarchy-schema.md +218 -0
- package/references/brain/schema.md +76 -0
- package/references/capability-radar/capabilities-index.md +241 -0
- package/references/capability-radar/changelog-cache.md +81 -0
- package/references/causal/causal-schema.md +103 -0
- package/references/design/email-template-standard.md +155 -0
- package/references/design/graph-visualization-standard.md +178 -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/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.md +237 -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/index.md +97 -0
- package/references/methodology/leadership.md +200 -0
- package/references/methodology/lean-canvas.md +116 -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/sapphire-encoding.md +355 -0
- package/references/methodology/scenario-plan.md +178 -0
- package/references/methodology/score-innovation.md +154 -0
- package/references/methodology/structure-argument.md +158 -0
- package/references/methodology/systems-thinking.md +159 -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/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/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/symbol-system.md +151 -0
- package/skills/MOSDeckEngine/SKILL.md +325 -0
- package/skills/brain-connector/SKILL.md +114 -0
- package/skills/context-engine/SKILL.md +147 -0
- package/skills/conversation-mode/SKILL.md +102 -0
- package/skills/larry-personality/SKILL.md +219 -0
- package/skills/larry-personality/framework-chains.md +92 -0
- package/skills/larry-personality/mode-engine.md +185 -0
- package/skills/mullins-scaffold/SKILL.md +61 -0
- package/skills/mullins-scaffold/scaffold.json +146 -0
- package/skills/pws-methodology/SKILL.md +49 -0
- package/skills/room-passive/SKILL.md +165 -0
- package/skills/room-proactive/SKILL.md +250 -0
- package/skills/ui-system/SKILL.md +277 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* Phase 94-09 -- /mos:explain-decision Action Footer fixture tests
|
|
6
|
+
* =================================================================
|
|
7
|
+
* Per QA handoff Section 3 FIX-8: scripts/explain-decision-command.cjs
|
|
8
|
+
* renderTrace() output ends with `---` and no Next: action footer. The
|
|
9
|
+
* 4-zone Action Footer rule in skills/ui-system/SKILL.md (Section 1
|
|
10
|
+
* Zone 4: "NEVER omitted") is violated for /mos:explain-decision.
|
|
11
|
+
*
|
|
12
|
+
* Plan 94-09 appends an Action Footer at the END of the rendered output,
|
|
13
|
+
* AFTER the existing closing `---` separator. The footer offers 3
|
|
14
|
+
* canonical /mos:* commands (status / act / suggest-next) per the QA
|
|
15
|
+
* handoff Section 3 FIX-8 verbatim suggestion.
|
|
16
|
+
*
|
|
17
|
+
* Canon Part 7 (Reuse Before Build): extends scripts/explain-decision-
|
|
18
|
+
* command.cjs renderTrace() output by appending a footer. The 9 trace
|
|
19
|
+
* fields covered by lib/memory/explain-decision-command.test.cjs Tests
|
|
20
|
+
* 1-12 are byte-identical before the footer; the new footer is purely
|
|
21
|
+
* additive at the tail.
|
|
22
|
+
*
|
|
23
|
+
* Canon Part 3 (Tri-Context Decision Gate): the footer offers verbs
|
|
24
|
+
* from the canonical 10-verb vocabulary (status -> Synthesize, act ->
|
|
25
|
+
* Run Methodology, suggest-next -> Free-Text / Spawn Sub-Agent). No
|
|
26
|
+
* new verbs introduced.
|
|
27
|
+
*
|
|
28
|
+
* Test map:
|
|
29
|
+
* T1 footer presence: last 5 lines of stdout contain literal 'Next:'
|
|
30
|
+
* T2 footer content: footer contains at least 2 /mos: command refs
|
|
31
|
+
* T3 non-regression: existing renderTrace prefix output (header +
|
|
32
|
+
* 9 trace fields + closing `---`) remains intact when the footer
|
|
33
|
+
* is appended. Verified by checking that all existing markers
|
|
34
|
+
* (Tier Mode, BRAIN.md signal, Five-Signal Triangulation, Chosen
|
|
35
|
+
* rationale, the closing `---`) are still present and in order.
|
|
36
|
+
* T4 graceful empty path: when the trace is empty (no decisions
|
|
37
|
+
* recorded yet), the footer STILL renders. Stdout still contains
|
|
38
|
+
* 'Next:' substring.
|
|
39
|
+
*
|
|
40
|
+
* Registered in lib/memory/run-feynman-tests.cjs.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
'use strict';
|
|
44
|
+
|
|
45
|
+
const assert = require('node:assert/strict');
|
|
46
|
+
const fs = require('node:fs');
|
|
47
|
+
const path = require('node:path');
|
|
48
|
+
const os = require('node:os');
|
|
49
|
+
const { spawnSync } = require('node:child_process');
|
|
50
|
+
|
|
51
|
+
const REPO = path.resolve(__dirname, '..', '..');
|
|
52
|
+
const SCRIPT_PATH = path.join(REPO, 'scripts/explain-decision-command.cjs');
|
|
53
|
+
|
|
54
|
+
// ---------- Test scaffolding ----------
|
|
55
|
+
|
|
56
|
+
let passed = 0;
|
|
57
|
+
let failed = 0;
|
|
58
|
+
function run(name, fn) {
|
|
59
|
+
try {
|
|
60
|
+
fn();
|
|
61
|
+
process.stdout.write('ok ' + name + '\n');
|
|
62
|
+
passed += 1;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
process.stderr.write('FAIL ' + name + '\n');
|
|
65
|
+
process.stderr.write(String(err && err.stack ? err.stack : err) + '\n');
|
|
66
|
+
failed += 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------- Fixture builders ----------
|
|
71
|
+
|
|
72
|
+
const TMPDIRS = [];
|
|
73
|
+
|
|
74
|
+
process.on('exit', () => {
|
|
75
|
+
for (const d of TMPDIRS) {
|
|
76
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (_) {}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function makeRoomsFixture(label) {
|
|
81
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '94-09-' + label + '-'));
|
|
82
|
+
TMPDIRS.push(tmp);
|
|
83
|
+
const root = path.join(tmp, 'MindrianRooms');
|
|
84
|
+
fs.mkdirSync(root, { recursive: true });
|
|
85
|
+
const roomDir = path.join(root, 'fixture-room');
|
|
86
|
+
fs.mkdirSync(roomDir, { recursive: true });
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
path.join(roomDir, 'STATE.md'),
|
|
89
|
+
'---\nproject: fixture room\n---\n\n# Fixture Room\n'
|
|
90
|
+
);
|
|
91
|
+
const regDir = path.join(root, '.rooms');
|
|
92
|
+
fs.mkdirSync(regDir, { recursive: true });
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
path.join(regDir, 'registry.json'),
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
version: 1,
|
|
97
|
+
active: 'fixture-room',
|
|
98
|
+
rooms: {
|
|
99
|
+
'fixture-room': { path: 'fixture-room' },
|
|
100
|
+
},
|
|
101
|
+
}, null, 2)
|
|
102
|
+
);
|
|
103
|
+
return { tmp: tmp, root: root, roomDir: roomDir };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function writeTraceFile(roomDir, sessionId, traces) {
|
|
107
|
+
const dir = path.join(roomDir, '.mindrian', 'decision-traces');
|
|
108
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
+
const fp = path.join(dir, sessionId + '.json');
|
|
110
|
+
fs.writeFileSync(
|
|
111
|
+
fp,
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{ version: 1, session_id: sessionId, traces: traces },
|
|
114
|
+
null,
|
|
115
|
+
2
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
return fp;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function makeTrace(turn, overrides) {
|
|
122
|
+
const base = {
|
|
123
|
+
turn: turn,
|
|
124
|
+
at: '2026-04-29T12:00:0' + (turn % 10) + '.000Z',
|
|
125
|
+
elapsed_ms: 12,
|
|
126
|
+
brain_md_version: 1,
|
|
127
|
+
brain_md_staleness: 'fresh',
|
|
128
|
+
brain_md_stale_reason: null,
|
|
129
|
+
brain_md_weight_applied: 1.0,
|
|
130
|
+
brain_md_recommended_confidence: 0.85,
|
|
131
|
+
brain_md_recommended_marker_rendered: true,
|
|
132
|
+
brain_md_tier_mode: 'mode_a',
|
|
133
|
+
brain_md_sections_consumed: ['pattern_matches', 'wicked_indicators'],
|
|
134
|
+
icm_scope: { section: 'market-analysis', scope_path: 'fixture-room/market-analysis' },
|
|
135
|
+
sql_signals: { edge_counts: 12, contradictions: 1, convergences: 3 },
|
|
136
|
+
minto_reasoning: {
|
|
137
|
+
governing_thought: 'Customers will pay for clarity',
|
|
138
|
+
reasoning_health_score: 0.7,
|
|
139
|
+
is_stale: false,
|
|
140
|
+
},
|
|
141
|
+
intent_persona: {
|
|
142
|
+
larry_persona: 'Researcher',
|
|
143
|
+
brain_persona: 'Implicit',
|
|
144
|
+
detection_confidence: 0.6,
|
|
145
|
+
},
|
|
146
|
+
chosen_rationale: 'Mode A: BRAIN.md fresh; pattern_matches confidence above 0.7 floor.',
|
|
147
|
+
};
|
|
148
|
+
return Object.assign({}, base, overrides || {});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runCommand(roomDir, args, extraEnv) {
|
|
152
|
+
const env = Object.assign({}, process.env, {
|
|
153
|
+
MINDRIAN_ROOMS_HOME: path.dirname(roomDir),
|
|
154
|
+
MINDRIAN_ROOMS_ROOT: path.dirname(roomDir),
|
|
155
|
+
}, extraEnv || {});
|
|
156
|
+
return spawnSync(process.execPath, [SCRIPT_PATH].concat(args || []), {
|
|
157
|
+
env: env,
|
|
158
|
+
encoding: 'utf8',
|
|
159
|
+
timeout: 5000,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =========================================================
|
|
164
|
+
// Tests T1-T4 (Plan 94-09 Action Footer)
|
|
165
|
+
// =========================================================
|
|
166
|
+
|
|
167
|
+
run('T1 footer presence: last 5 lines of stdout contain literal Next:', () => {
|
|
168
|
+
const fix = makeRoomsFixture('t1');
|
|
169
|
+
writeTraceFile(fix.roomDir, 'sid-t1', [makeTrace(1)]);
|
|
170
|
+
const res = runCommand(fix.roomDir, [], { CLAUDE_SESSION_ID: 'sid-t1' });
|
|
171
|
+
assert.equal(res.status, 0, 'exit 0; stderr: ' + (res.stderr || ''));
|
|
172
|
+
const out = res.stdout || '';
|
|
173
|
+
// Trim trailing whitespace then split into lines.
|
|
174
|
+
const lines = out.replace(/\s+$/, '').split('\n');
|
|
175
|
+
const last5 = lines.slice(-5).join('\n');
|
|
176
|
+
assert.equal(
|
|
177
|
+
last5.indexOf('Next:') !== -1,
|
|
178
|
+
true,
|
|
179
|
+
'expected "Next:" in last 5 lines; got last 5 lines:\n' + last5 + '\n\nFull output:\n' + out
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
run('T2 footer content: at least 2 /mos: command refs in footer', () => {
|
|
184
|
+
const fix = makeRoomsFixture('t2');
|
|
185
|
+
writeTraceFile(fix.roomDir, 'sid-t2', [makeTrace(1)]);
|
|
186
|
+
const res = runCommand(fix.roomDir, [], { CLAUDE_SESSION_ID: 'sid-t2' });
|
|
187
|
+
assert.equal(res.status, 0, 'exit 0; stderr: ' + (res.stderr || ''));
|
|
188
|
+
const out = res.stdout || '';
|
|
189
|
+
// Locate the Next: marker and count /mos: occurrences AFTER it. The
|
|
190
|
+
// pre-94-09 output may already contain /mos: in advisory paths or
|
|
191
|
+
// in the chosen_rationale body. We isolate the footer by anchoring
|
|
192
|
+
// on the literal 'Next:' substring.
|
|
193
|
+
const idx = out.indexOf('Next:');
|
|
194
|
+
assert.equal(
|
|
195
|
+
idx !== -1,
|
|
196
|
+
true,
|
|
197
|
+
'expected "Next:" anchor; got:\n' + out
|
|
198
|
+
);
|
|
199
|
+
const footer = out.slice(idx);
|
|
200
|
+
const matches = footer.match(/\/mos:/g) || [];
|
|
201
|
+
assert.equal(
|
|
202
|
+
matches.length >= 2,
|
|
203
|
+
true,
|
|
204
|
+
'expected at least 2 /mos: commands in footer; found '
|
|
205
|
+
+ matches.length + ' in:\n' + footer
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
run('T3 non-regression: existing renderTrace markers preserved before footer', () => {
|
|
210
|
+
const fix = makeRoomsFixture('t3');
|
|
211
|
+
writeTraceFile(fix.roomDir, 'sid-t3', [makeTrace(1)]);
|
|
212
|
+
const res = runCommand(fix.roomDir, [], { CLAUDE_SESSION_ID: 'sid-t3' });
|
|
213
|
+
assert.equal(res.status, 0, 'exit 0; stderr: ' + (res.stderr || ''));
|
|
214
|
+
const out = res.stdout || '';
|
|
215
|
+
// The 9 trace-field markers from lib/memory/explain-decision-command.test.cjs
|
|
216
|
+
// Tests 1-4 must all still appear in the output.
|
|
217
|
+
const requiredMarkers = [
|
|
218
|
+
'Navigation Engine Decision Trace', // header
|
|
219
|
+
'Turn 1', // structural turn
|
|
220
|
+
'Tier Mode', // brain_md_tier_mode label
|
|
221
|
+
'mode_a', // tier_mode value
|
|
222
|
+
'BRAIN.md signal', // section header
|
|
223
|
+
'version:',
|
|
224
|
+
'staleness:',
|
|
225
|
+
'stale_reason:',
|
|
226
|
+
'weight_applied:',
|
|
227
|
+
'sections_consumed:',
|
|
228
|
+
'RECOMMENDED marker',
|
|
229
|
+
'rendered:',
|
|
230
|
+
'highest_confidence:',
|
|
231
|
+
'Five-Signal Triangulation',
|
|
232
|
+
'ICM scope',
|
|
233
|
+
'SQL signals',
|
|
234
|
+
'Feynman-MINTO',
|
|
235
|
+
'BRAIN patterns',
|
|
236
|
+
'Intent + Persona',
|
|
237
|
+
'Chosen rationale', // chosen_rationale section
|
|
238
|
+
'pattern_matches', // brain_md_sections_consumed value
|
|
239
|
+
'market-analysis', // icm_scope.section value
|
|
240
|
+
'Customers will pay', // minto_reasoning.governing_thought
|
|
241
|
+
'Researcher', // intent_persona.larry_persona
|
|
242
|
+
'Mode A: BRAIN.md fresh', // chosen_rationale verbatim prefix
|
|
243
|
+
];
|
|
244
|
+
for (const m of requiredMarkers) {
|
|
245
|
+
assert.equal(
|
|
246
|
+
out.indexOf(m) !== -1,
|
|
247
|
+
true,
|
|
248
|
+
'pre-94-09 marker missing after footer append: "' + m + '"'
|
|
249
|
+
+ '\nFull output:\n' + out
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
// The closing `---` separator MUST appear BEFORE the Next: footer.
|
|
253
|
+
// Pre-94-09 output ended with `---\n`. Plan 94-09 appends the footer
|
|
254
|
+
// AFTER that `---`, so the order is: ...content... `---` ... `Next:`.
|
|
255
|
+
const sepIdx = out.lastIndexOf('\n---');
|
|
256
|
+
const nextIdx = out.indexOf('Next:');
|
|
257
|
+
assert.equal(
|
|
258
|
+
sepIdx !== -1,
|
|
259
|
+
true,
|
|
260
|
+
'closing `---` separator missing; got:\n' + out
|
|
261
|
+
);
|
|
262
|
+
assert.equal(
|
|
263
|
+
nextIdx !== -1,
|
|
264
|
+
true,
|
|
265
|
+
'"Next:" footer missing; got:\n' + out
|
|
266
|
+
);
|
|
267
|
+
assert.equal(
|
|
268
|
+
sepIdx < nextIdx,
|
|
269
|
+
true,
|
|
270
|
+
'closing `---` must precede the Next: footer; sepIdx=' + sepIdx
|
|
271
|
+
+ ' nextIdx=' + nextIdx + '\nFull output:\n' + out
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
run('T4 graceful empty path: footer renders when no decisions yet', () => {
|
|
276
|
+
// Empty traces array. Pre-94-09 the script prints the
|
|
277
|
+
// "No decisions recorded for this session." advisory and exits 0
|
|
278
|
+
// with no footer. Plan 94-09 appends the footer to the empty path
|
|
279
|
+
// too, so the user always gets a navigable next step.
|
|
280
|
+
const fix = makeRoomsFixture('t4');
|
|
281
|
+
writeTraceFile(fix.roomDir, 'empty-sid', []);
|
|
282
|
+
const res = runCommand(fix.roomDir, [], { CLAUDE_SESSION_ID: 'empty-sid' });
|
|
283
|
+
assert.equal(res.status, 0, 'exit 0; stderr: ' + (res.stderr || ''));
|
|
284
|
+
const out = res.stdout || '';
|
|
285
|
+
// Empty-path advisory still rendered.
|
|
286
|
+
assert.equal(
|
|
287
|
+
out.indexOf('No decisions recorded') !== -1,
|
|
288
|
+
true,
|
|
289
|
+
'empty-path advisory missing; got:\n' + out
|
|
290
|
+
);
|
|
291
|
+
// Footer rendered too.
|
|
292
|
+
assert.equal(
|
|
293
|
+
out.indexOf('Next:') !== -1,
|
|
294
|
+
true,
|
|
295
|
+
'expected "Next:" footer on empty-path output; got:\n' + out
|
|
296
|
+
);
|
|
297
|
+
// At least 2 /mos: command refs after the Next: anchor.
|
|
298
|
+
const idx = out.indexOf('Next:');
|
|
299
|
+
const footer = out.slice(idx);
|
|
300
|
+
const matches = footer.match(/\/mos:/g) || [];
|
|
301
|
+
assert.equal(
|
|
302
|
+
matches.length >= 2,
|
|
303
|
+
true,
|
|
304
|
+
'expected at least 2 /mos: commands in empty-path footer; found '
|
|
305
|
+
+ matches.length + ' in:\n' + footer
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// ---------- Reporter ----------
|
|
310
|
+
|
|
311
|
+
process.stdout.write('\n');
|
|
312
|
+
process.stdout.write('explain-decision-footer: ' + passed + '/' + (passed + failed) + ' passed\n');
|
|
313
|
+
if (failed > 0) {
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
process.exit(0);
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
+
*
|
|
4
|
+
* Phase 117-01 Wave 1 -- explored-materials-store substrate.
|
|
5
|
+
*
|
|
6
|
+
* JSONL append-only state store at ~/.mindrian/explored-materials/<roomSlug>.jsonl.
|
|
7
|
+
* Per CLAUDE.md workspace guard: writes OUTSIDE plugin repo (os.homedir()).
|
|
8
|
+
* Per Canon Part 8: USER_CONTENT_KEY_DENYLIST guards against telemetry leakage.
|
|
9
|
+
*
|
|
10
|
+
* Graph-native HARD RULE (memory feedback_reverse_salient_agent_graph_native.md):
|
|
11
|
+
* 1. NEVER require room-db.cjs (Phase 109 D-06 chokepoint).
|
|
12
|
+
* 2. NEVER require brain-client (Canon Part 8 boundary).
|
|
13
|
+
* 3. NEVER call console.log or process.stdout.write (dual-surface telemetry contract).
|
|
14
|
+
*
|
|
15
|
+
* Pattern: append-only writer + LWW replay (mirrors lib/memory/pending-tension-store.cjs
|
|
16
|
+
* Phase 116-01 verbatim; field substitutions per RESEARCH Section 3 sibling code-clone).
|
|
17
|
+
*
|
|
18
|
+
* DELTA vs Phase 116-01 pending-tension-store.cjs: same 8-export skeleton; field
|
|
19
|
+
* renames (tension_id -> material_id; tension_type -> mtime_seconds; source_node_ids
|
|
20
|
+
* -> relative_file_path). State machine vocabulary differs (queued | in_flight |
|
|
21
|
+
* completed | failed instead of queued | surfaced | resolved | dropped). 2 new
|
|
22
|
+
* exports added (markFailed, sweepStaleInFlight) to handle the in_flight
|
|
23
|
+
* stale-sweep that 116 does not have because tensions have no background-spawn
|
|
24
|
+
* lifecycle.
|
|
25
|
+
*
|
|
26
|
+
* Pure CJS, node built-ins only, zero new runtime dependencies.
|
|
27
|
+
*/
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const os = require('node:os');
|
|
33
|
+
const crypto = require('node:crypto');
|
|
34
|
+
|
|
35
|
+
// ---------- Constants ----------
|
|
36
|
+
|
|
37
|
+
const EXPLORED_MATERIALS_DIR = path.join(os.homedir(), '.mindrian', 'explored-materials');
|
|
38
|
+
|
|
39
|
+
const VALID_STATES = Object.freeze(new Set(['queued', 'in_flight', 'completed', 'failed']));
|
|
40
|
+
|
|
41
|
+
const VALID_RESPONSES = Object.freeze(new Set(['EXPLORE', 'SKIP', 'LATER', 'FREE_TEXT']));
|
|
42
|
+
|
|
43
|
+
const VALID_SUPPRESS_REASONS = Object.freeze(new Set([
|
|
44
|
+
'tier_0',
|
|
45
|
+
'just_talk',
|
|
46
|
+
'dispatcher_load_failed',
|
|
47
|
+
'pickShape_unavailable',
|
|
48
|
+
'all_pipelines_empty',
|
|
49
|
+
'brain_baseline_unavailable',
|
|
50
|
+
'rate_limited',
|
|
51
|
+
'daily_cap_exceeded',
|
|
52
|
+
'room_dir_not_writable',
|
|
53
|
+
'ledger_replay_failed',
|
|
54
|
+
'hybrid_mode_insufficient_corpus',
|
|
55
|
+
'sanitizer_blocked',
|
|
56
|
+
'invalid_args',
|
|
57
|
+
]));
|
|
58
|
+
|
|
59
|
+
// User-content key denylist (Canon Part 8 -- artifact bodies, titles, transcripts
|
|
60
|
+
// live in the graph, never in this JSONL). Rejection is fail-fast at
|
|
61
|
+
// appendMaterial time so a caller bug never leaks user content to disk.
|
|
62
|
+
// NOTE: relative_file_path IS allowed in the ledger (LOCAL-only state file
|
|
63
|
+
// outside the plugin repo per workspace guard), but MUST be hashed to
|
|
64
|
+
// file_path_sha256 BEFORE any memory_event payload write or telemetry mirror.
|
|
65
|
+
const USER_CONTENT_KEY_DENYLIST = Object.freeze(new Set([
|
|
66
|
+
'body_text',
|
|
67
|
+
'source_title',
|
|
68
|
+
'target_title',
|
|
69
|
+
'artifact_body',
|
|
70
|
+
'note_content',
|
|
71
|
+
'transcript_content',
|
|
72
|
+
'meeting_summary',
|
|
73
|
+
'cv_content',
|
|
74
|
+
'file_content',
|
|
75
|
+
]));
|
|
76
|
+
|
|
77
|
+
const MATERIAL_ID_LEN = 32;
|
|
78
|
+
const STALE_IN_FLIGHT_MS_DEFAULT = 300000; // 5 minutes per RESEARCH scenario 7
|
|
79
|
+
|
|
80
|
+
// ---------- Material id (deterministic, second-precision) ----------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compute the deterministic material id for a (roomDir, relativeFilePath, mtimeMs)
|
|
84
|
+
* tuple. Per RESEARCH Section 4.5: mtime is FLOORED to seconds for cross-fs
|
|
85
|
+
* portability (FAT32, NFS, etc. lack ms-precision; risk T1).
|
|
86
|
+
*
|
|
87
|
+
* @param {string} roomDir absolute path to room (with .room-root)
|
|
88
|
+
* @param {string} relativeFilePath path relative to roomDir
|
|
89
|
+
* @param {number} mtimeMs file mtime in ms-epoch
|
|
90
|
+
* @returns {string} 32-char hex sha256 prefix
|
|
91
|
+
*/
|
|
92
|
+
function computeMaterialId(roomDir, relativeFilePath, mtimeMs) {
|
|
93
|
+
const seconds = Math.floor(Number(mtimeMs) / 1000);
|
|
94
|
+
const basis = String(roomDir) + '|' + String(relativeFilePath) + '|' + String(seconds);
|
|
95
|
+
return crypto.createHash('sha256').update(basis).digest('hex').slice(0, MATERIAL_ID_LEN);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------- JSONL path resolution (workspace-guard-clean) ----------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the JSONL path for a given room slug. Always returns a path under
|
|
102
|
+
* os.homedir() per CLAUDE.md workspace guard (OUTSIDE the plugin repo).
|
|
103
|
+
*
|
|
104
|
+
* @param {string} roomSlug
|
|
105
|
+
* @returns {string} absolute path
|
|
106
|
+
*/
|
|
107
|
+
function jsonlPath(roomSlug) {
|
|
108
|
+
const safeSlug = encodeURIComponent(String(roomSlug || 'default-room'));
|
|
109
|
+
return path.join(EXPLORED_MATERIALS_DIR, safeSlug + '.jsonl');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------- Internal helpers ----------
|
|
113
|
+
|
|
114
|
+
function isPlainObject(v) {
|
|
115
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isHex32(v) {
|
|
119
|
+
return typeof v === 'string' && /^[0-9a-f]{32}$/.test(v);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function violatesUserContentBoundary(entry) {
|
|
123
|
+
for (const k of Object.keys(entry)) {
|
|
124
|
+
if (USER_CONTENT_KEY_DENYLIST.has(k)) return k;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------- Validation ----------
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validate the shape of an entry. Returns { ok: true } on success, or
|
|
133
|
+
* { ok: false, reason, offending_key? } on rejection.
|
|
134
|
+
*
|
|
135
|
+
* Canon Part 8 enforcement: any USER_CONTENT_KEY_DENYLIST key in the entry
|
|
136
|
+
* triggers a canon_part_8_violation rejection (fail-fast before disk write).
|
|
137
|
+
*
|
|
138
|
+
* @param {object} entry
|
|
139
|
+
* @returns {{ok: true} | {ok: false, reason: string, offending_key?: string}}
|
|
140
|
+
*/
|
|
141
|
+
function validateEntryShape(entry) {
|
|
142
|
+
if (!isPlainObject(entry)) {
|
|
143
|
+
return { ok: false, reason: 'entry_not_object' };
|
|
144
|
+
}
|
|
145
|
+
// Canon Part 8 boundary -- check FIRST so a payload with denylist keys is
|
|
146
|
+
// rejected even if other shape checks would also fail.
|
|
147
|
+
const violator = violatesUserContentBoundary(entry);
|
|
148
|
+
if (violator) {
|
|
149
|
+
return { ok: false, reason: 'canon_part_8_violation', offending_key: violator };
|
|
150
|
+
}
|
|
151
|
+
if (!isHex32(entry.material_id)) {
|
|
152
|
+
return { ok: false, reason: 'invalid_material_id' };
|
|
153
|
+
}
|
|
154
|
+
if (!VALID_STATES.has(entry.state)) {
|
|
155
|
+
return { ok: false, reason: 'invalid_state' };
|
|
156
|
+
}
|
|
157
|
+
if (!Number.isInteger(entry.mtime_seconds) || entry.mtime_seconds < 0) {
|
|
158
|
+
return { ok: false, reason: 'invalid_mtime_seconds' };
|
|
159
|
+
}
|
|
160
|
+
if (!Number.isInteger(entry.fired_at) || entry.fired_at < 0) {
|
|
161
|
+
return { ok: false, reason: 'invalid_fired_at' };
|
|
162
|
+
}
|
|
163
|
+
return { ok: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------- Public API ----------
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Append a material entry to the room's JSONL file. Creates the directory if
|
|
170
|
+
* missing. Validates shape + Canon Part 8 boundary; rejects user-content
|
|
171
|
+
* fields.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} roomSlug
|
|
174
|
+
* @param {object} entry conforming to RESEARCH Section 4.5 schema
|
|
175
|
+
* @returns {{ok: true, path: string} | {ok: false, reason: string, offending_key?: string}}
|
|
176
|
+
*/
|
|
177
|
+
function appendMaterial(roomSlug, entry) {
|
|
178
|
+
const validation = validateEntryShape(entry);
|
|
179
|
+
if (!validation.ok) return validation;
|
|
180
|
+
try {
|
|
181
|
+
fs.mkdirSync(EXPLORED_MATERIALS_DIR, { recursive: true });
|
|
182
|
+
const file = jsonlPath(roomSlug);
|
|
183
|
+
fs.appendFileSync(file, JSON.stringify(entry) + '\n');
|
|
184
|
+
return { ok: true, path: file };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
const msg = (err && err.message) ? String(err.message) : 'append_failed';
|
|
187
|
+
return { ok: false, reason: msg.slice(0, 80) };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Read all materials for a room, applying LWW replay (last entry per
|
|
193
|
+
* material_id wins). Returns [] when the file is missing. Tolerates corrupt
|
|
194
|
+
* lines by skipping them silently.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} roomSlug
|
|
197
|
+
* @returns {Array<object>}
|
|
198
|
+
*/
|
|
199
|
+
function readMaterials(roomSlug) {
|
|
200
|
+
const file = jsonlPath(roomSlug);
|
|
201
|
+
if (!fs.existsSync(file)) return [];
|
|
202
|
+
let raw;
|
|
203
|
+
try {
|
|
204
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
205
|
+
} catch (_e) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
209
|
+
const byId = new Map();
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
let entry;
|
|
212
|
+
try {
|
|
213
|
+
entry = JSON.parse(line);
|
|
214
|
+
} catch (_e) {
|
|
215
|
+
continue; // corrupt line tolerated per RESEARCH Section 4.5 LWW replay tolerance
|
|
216
|
+
}
|
|
217
|
+
if (!isPlainObject(entry) || !isHex32(entry.material_id)) continue;
|
|
218
|
+
byId.set(entry.material_id, entry);
|
|
219
|
+
}
|
|
220
|
+
return Array.from(byId.values());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Find the latest entry for a single material_id. Returns null when missing.
|
|
225
|
+
* Sorts by fired_at descending so the most-recent entry wins regardless of
|
|
226
|
+
* append order.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} roomSlug
|
|
229
|
+
* @param {string} materialId 32-char hex
|
|
230
|
+
* @returns {object|null}
|
|
231
|
+
*/
|
|
232
|
+
function findLatest(roomSlug, materialId) {
|
|
233
|
+
if (!isHex32(materialId)) return null;
|
|
234
|
+
const file = jsonlPath(roomSlug);
|
|
235
|
+
if (!fs.existsSync(file)) return null;
|
|
236
|
+
let raw;
|
|
237
|
+
try {
|
|
238
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
239
|
+
} catch (_e) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
243
|
+
let latest = null;
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
let entry;
|
|
246
|
+
try {
|
|
247
|
+
entry = JSON.parse(line);
|
|
248
|
+
} catch (_e) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (!isPlainObject(entry)) continue;
|
|
252
|
+
if (entry.material_id !== materialId) continue;
|
|
253
|
+
if (latest === null) {
|
|
254
|
+
latest = entry;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const a = Number.isInteger(entry.fired_at) ? entry.fired_at : 0;
|
|
258
|
+
const b = Number.isInteger(latest.fired_at) ? latest.fired_at : 0;
|
|
259
|
+
if (a >= b) latest = entry;
|
|
260
|
+
}
|
|
261
|
+
return latest;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Mark a material as completed. Appends a transition entry where state is
|
|
266
|
+
* 'completed' and finding_count is set from fields.
|
|
267
|
+
*
|
|
268
|
+
* @param {string} roomSlug
|
|
269
|
+
* @param {string} materialId
|
|
270
|
+
* @param {object} fields { finding_count: number }
|
|
271
|
+
* @returns {{ok: true} | {ok: false, reason: string}}
|
|
272
|
+
*/
|
|
273
|
+
function markCompleted(roomSlug, materialId, fields) {
|
|
274
|
+
const prev = findLatest(roomSlug, materialId);
|
|
275
|
+
if (!prev) return { ok: false, reason: 'material_not_found' };
|
|
276
|
+
const fc = (fields && Number.isInteger(fields.finding_count)) ? fields.finding_count : null;
|
|
277
|
+
const next = Object.assign({}, prev, {
|
|
278
|
+
state: 'completed',
|
|
279
|
+
finding_count: fc,
|
|
280
|
+
in_flight_since: null,
|
|
281
|
+
});
|
|
282
|
+
const r = appendMaterial(roomSlug, next);
|
|
283
|
+
if (!r.ok) return r;
|
|
284
|
+
return { ok: true };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Mark a material as failed with a suppression reason. If the material is
|
|
289
|
+
* not yet in the ledger, append a fresh 'failed' entry (suppression on the
|
|
290
|
+
* very first detection event).
|
|
291
|
+
*
|
|
292
|
+
* @param {string} roomSlug
|
|
293
|
+
* @param {string} materialId
|
|
294
|
+
* @param {string} suppressReason one of VALID_SUPPRESS_REASONS
|
|
295
|
+
* @returns {{ok: true} | {ok: false, reason: string}}
|
|
296
|
+
*/
|
|
297
|
+
function markFailed(roomSlug, materialId, suppressReason) {
|
|
298
|
+
if (!VALID_SUPPRESS_REASONS.has(suppressReason)) {
|
|
299
|
+
return { ok: false, reason: 'invalid_suppress_reason' };
|
|
300
|
+
}
|
|
301
|
+
if (!isHex32(materialId)) {
|
|
302
|
+
return { ok: false, reason: 'invalid_material_id' };
|
|
303
|
+
}
|
|
304
|
+
const prev = findLatest(roomSlug, materialId);
|
|
305
|
+
let next;
|
|
306
|
+
if (prev) {
|
|
307
|
+
next = Object.assign({}, prev, {
|
|
308
|
+
state: 'failed',
|
|
309
|
+
suppress_reason: suppressReason,
|
|
310
|
+
in_flight_since: null,
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
// Suppression on first event -- caller has not yet appended a queued entry.
|
|
314
|
+
next = {
|
|
315
|
+
material_id: materialId,
|
|
316
|
+
file_path_sha256: null,
|
|
317
|
+
relative_file_path: null,
|
|
318
|
+
mtime_seconds: 0,
|
|
319
|
+
fired_at: Date.now(),
|
|
320
|
+
state: 'failed',
|
|
321
|
+
finding_count: null,
|
|
322
|
+
surfaced: false,
|
|
323
|
+
user_response: null,
|
|
324
|
+
responded_at: null,
|
|
325
|
+
suppress_reason: suppressReason,
|
|
326
|
+
in_flight_since: null,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const r = appendMaterial(roomSlug, next);
|
|
330
|
+
if (!r.ok) return r;
|
|
331
|
+
return { ok: true };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Sweep stale in_flight entries. Per RESEARCH scenario 7 (5min stale
|
|
336
|
+
* recovery): an entry with state==='in_flight' AND
|
|
337
|
+
* (now - in_flight_since) > staleMs is transitioned to 'failed' with
|
|
338
|
+
* suppress_reason='rate_limited' (benign reason; the spawn child likely
|
|
339
|
+
* crashed or timed out, and the next detection of the same material_id
|
|
340
|
+
* will be free to fire fresh).
|
|
341
|
+
*
|
|
342
|
+
* Idempotent: re-running on the same JSONL is a no-op when no entries are
|
|
343
|
+
* stale (terminal states 'completed' and 'failed' are excluded from the scan).
|
|
344
|
+
*
|
|
345
|
+
* Sync. Never throws. On any error returns { ok: true, swept: 0 } so the
|
|
346
|
+
* SessionStart hook can never be derailed by a sweep failure.
|
|
347
|
+
*
|
|
348
|
+
* @param {string} roomSlug
|
|
349
|
+
* @param {object} opts { staleMs: number }
|
|
350
|
+
* @returns {{ok: true, swept: number}}
|
|
351
|
+
*/
|
|
352
|
+
function sweepStaleInFlight(roomSlug, opts) {
|
|
353
|
+
try {
|
|
354
|
+
const staleMs = (opts && Number.isInteger(opts.staleMs) && opts.staleMs > 0)
|
|
355
|
+
? opts.staleMs
|
|
356
|
+
: STALE_IN_FLIGHT_MS_DEFAULT;
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
const entries = readMaterials(roomSlug);
|
|
359
|
+
let swept = 0;
|
|
360
|
+
for (const e of entries) {
|
|
361
|
+
if (!isPlainObject(e)) continue;
|
|
362
|
+
if (e.state !== 'in_flight') continue;
|
|
363
|
+
if (!Number.isInteger(e.in_flight_since)) continue;
|
|
364
|
+
if ((now - e.in_flight_since) <= staleMs) continue;
|
|
365
|
+
const r = markFailed(roomSlug, e.material_id, 'rate_limited');
|
|
366
|
+
if (r && r.ok) swept += 1;
|
|
367
|
+
}
|
|
368
|
+
return { ok: true, swept: swept };
|
|
369
|
+
} catch (_e) {
|
|
370
|
+
return { ok: true, swept: 0 };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------- Module exports ----------
|
|
375
|
+
|
|
376
|
+
module.exports = {
|
|
377
|
+
computeMaterialId,
|
|
378
|
+
jsonlPath,
|
|
379
|
+
validateEntryShape,
|
|
380
|
+
appendMaterial,
|
|
381
|
+
readMaterials,
|
|
382
|
+
findLatest,
|
|
383
|
+
markCompleted,
|
|
384
|
+
markFailed,
|
|
385
|
+
sweepStaleInFlight,
|
|
386
|
+
EXPLORED_MATERIALS_DIR,
|
|
387
|
+
MATERIAL_ID_LEN,
|
|
388
|
+
VALID_STATES,
|
|
389
|
+
VALID_RESPONSES,
|
|
390
|
+
VALID_SUPPRESS_REASONS,
|
|
391
|
+
USER_CONTENT_KEY_DENYLIST,
|
|
392
|
+
};
|