@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,1057 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindrianOS Plugin -- LazyGraph Operations
|
|
3
|
+
* SQLite-backed per-project queryable knowledge graph via node:sqlite DatabaseSync.
|
|
4
|
+
* Graph stored in room/.mindrian/room.db (WAL mode for concurrent reads).
|
|
5
|
+
*
|
|
6
|
+
* Exports: openGraph, closeGraph, initSchema, indexArtifact,
|
|
7
|
+
* rebuildGraph, queryGraph, graphStats, embedArtifact,
|
|
8
|
+
* + 12 edge-creator functions + EDGE_TYPES constant
|
|
9
|
+
*
|
|
10
|
+
* All functions remain async for backward compatibility with callers
|
|
11
|
+
* that use await. Awaiting a synchronous return resolves immediately.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
20
|
+
const { discoverSections } = require('./section-registry.cjs');
|
|
21
|
+
|
|
22
|
+
// --- Schema ---
|
|
23
|
+
|
|
24
|
+
/** All relationship (edge) types in the LazyGraph */
|
|
25
|
+
const EDGE_TYPES = ['INFORMS', 'CONTRADICTS', 'CONVERGES', 'ENABLES', 'INVALIDATES', 'BELONGS_TO', 'REASONING_INFORMS', 'HSI_CONNECTION', 'REVERSE_SALIENT', 'ANALOGOUS_TO', 'STRUCTURALLY_ISOMORPHIC', 'RESOLVES_VIA', 'CAUSES', 'ROOT_CAUSE_OF', 'CASCADES_TO', 'EXTRACTED_FROM', 'WHITESPACE_DETECTED', 'WHITESPACE_NEAR', 'DISCOVERY_CYCLE_SOURCE', 'DISCOVERED', 'DERIVED_FROM', 'AUTHORED_BY', 'AFFILIATED_WITH'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create nodes and edges tables with indexes. Idempotent.
|
|
29
|
+
* @param {import('node:sqlite').DatabaseSync} db - node:sqlite DatabaseSync instance
|
|
30
|
+
*/
|
|
31
|
+
function initSchema(db) {
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
type TEXT NOT NULL,
|
|
36
|
+
properties TEXT DEFAULT '{}'
|
|
37
|
+
);
|
|
38
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
39
|
+
source TEXT NOT NULL,
|
|
40
|
+
target TEXT NOT NULL,
|
|
41
|
+
type TEXT NOT NULL,
|
|
42
|
+
properties TEXT DEFAULT '{}',
|
|
43
|
+
PRIMARY KEY (source, target, type),
|
|
44
|
+
FOREIGN KEY (source) REFERENCES nodes(id),
|
|
45
|
+
FOREIGN KEY (target) REFERENCES nodes(id)
|
|
46
|
+
);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(source, type);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(target, type);
|
|
53
|
+
CREATE TABLE IF NOT EXISTS stakeholders (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
type TEXT NOT NULL,
|
|
56
|
+
name TEXT NOT NULL,
|
|
57
|
+
canonical_ref TEXT,
|
|
58
|
+
notes TEXT,
|
|
59
|
+
metadata TEXT DEFAULT '{}',
|
|
60
|
+
created_at TEXT NOT NULL,
|
|
61
|
+
updated_at TEXT NOT NULL
|
|
62
|
+
);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_stakeholders_type ON stakeholders(type);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_stakeholders_canonical ON stakeholders(canonical_ref);
|
|
65
|
+
-- rs_discoveries: contract bridge between the RS SQLite mirror (writer) and
|
|
66
|
+
-- the RS NL/SQL readers. rs-sqlite-mirror.cjs writes RSDiscovery records
|
|
67
|
+
-- into the nodes table with the payload in a JSON properties bag
|
|
68
|
+
-- (keys: classification, breakthrough_score, dominant_dimension, thesis,
|
|
69
|
+
-- bridge_concept, diff, lsa, bert, ..., created_at, room_id, domain). The
|
|
70
|
+
-- readers (rs-nl-to-query.cjs SQL templates and scripts/rs-thesis-command.cjs)
|
|
71
|
+
-- query SELECT * FROM rs_discoveries WHERE room_slug = ? and
|
|
72
|
+
-- SELECT id, thesis, rs_type, breakthrough_score, room_slug, created_at
|
|
73
|
+
-- FROM rs_discoveries WHERE id = ? -- there was no such table, so queryGraph
|
|
74
|
+
-- swallowed the "no such table" error as []. This VIEW closes that gap: it
|
|
75
|
+
-- json_extract's the payload keys into the columns the queries expect and
|
|
76
|
+
-- renames the writer's room_id JSON key to the reader's room_slug column
|
|
77
|
+
-- and classification to rs_type. node:sqlite's bundled SQLite ships the
|
|
78
|
+
-- JSON1 extension so json_extract works. NOTE: room_id is mapped to
|
|
79
|
+
-- room_slug verbatim -- if ctx.room_id passed by the RS engine is ever
|
|
80
|
+
-- not a room slug, that is a separate data-lineage fix in
|
|
81
|
+
-- rs-discovery-engine.cjs / rs-sqlite-mirror.cjs; this view only aligns the
|
|
82
|
+
-- column NAMES the queries already use. CREATE VIEW IF NOT EXISTS makes it
|
|
83
|
+
-- idempotent -- existing room.db files pick it up on the next openGraph.
|
|
84
|
+
CREATE VIEW IF NOT EXISTS rs_discoveries AS
|
|
85
|
+
SELECT
|
|
86
|
+
id,
|
|
87
|
+
json_extract(properties, '$.thesis') AS thesis,
|
|
88
|
+
json_extract(properties, '$.classification') AS rs_type,
|
|
89
|
+
json_extract(properties, '$.breakthrough_score') AS breakthrough_score,
|
|
90
|
+
json_extract(properties, '$.room_id') AS room_slug,
|
|
91
|
+
json_extract(properties, '$.created_at') AS created_at,
|
|
92
|
+
json_extract(properties, '$.bridge_concept') AS bridge_concept,
|
|
93
|
+
json_extract(properties, '$.dominant_dimension') AS dominant_dimension,
|
|
94
|
+
json_extract(properties, '$.diff') AS diff,
|
|
95
|
+
json_extract(properties, '$.lsa') AS lsa,
|
|
96
|
+
json_extract(properties, '$.bert') AS bert,
|
|
97
|
+
json_extract(properties, '$.domain') AS domain,
|
|
98
|
+
properties AS properties_json
|
|
99
|
+
FROM nodes
|
|
100
|
+
WHERE type = 'RSDiscovery';
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Stakeholder helpers (Phase 84-05, SCOPE-NB-03 / SCOPE-NB-04) ---
|
|
105
|
+
|
|
106
|
+
/** Allowed stakeholder.type values per Phase 84-05 spec. */
|
|
107
|
+
const STAKEHOLDER_TYPES = ['person', 'org', 'coalition', 'role'];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate a stakeholder type string. Throws TypeError on mismatch.
|
|
111
|
+
* @param {string} type
|
|
112
|
+
*/
|
|
113
|
+
function validateStakeholderType(type) {
|
|
114
|
+
if (!STAKEHOLDER_TYPES.includes(type)) {
|
|
115
|
+
throw new TypeError(`Invalid stakeholder type: ${type}. Must be one of ${STAKEHOLDER_TYPES.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create a new Stakeholder row.
|
|
121
|
+
* Generates a UUID, stamps created_at and updated_at, inserts the row.
|
|
122
|
+
* @param {import('node:sqlite').DatabaseSync} db
|
|
123
|
+
* @param {object} fields
|
|
124
|
+
* @param {string} fields.type - person | org | coalition | role
|
|
125
|
+
* @param {string} fields.name
|
|
126
|
+
* @param {string} [fields.canonical_ref]
|
|
127
|
+
* @param {string} [fields.notes]
|
|
128
|
+
* @param {object|string} [fields.metadata]
|
|
129
|
+
* @returns {Promise<object|null>} The new row or null on failure.
|
|
130
|
+
*/
|
|
131
|
+
async function createStakeholder(db, fields) {
|
|
132
|
+
try {
|
|
133
|
+
const { type, name } = fields || {};
|
|
134
|
+
validateStakeholderType(type);
|
|
135
|
+
if (!name || typeof name !== 'string') {
|
|
136
|
+
throw new TypeError('Stakeholder name is required');
|
|
137
|
+
}
|
|
138
|
+
const id = crypto.randomUUID();
|
|
139
|
+
const now = new Date().toISOString();
|
|
140
|
+
const canonical_ref = fields.canonical_ref || null;
|
|
141
|
+
const notes = fields.notes || null;
|
|
142
|
+
const metadata = typeof fields.metadata === 'string'
|
|
143
|
+
? fields.metadata
|
|
144
|
+
: JSON.stringify(fields.metadata || {});
|
|
145
|
+
db.prepare(
|
|
146
|
+
'INSERT INTO stakeholders (id, type, name, canonical_ref, notes, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
|
147
|
+
).run(id, type, name, canonical_ref, notes, metadata, now, now);
|
|
148
|
+
return Promise.resolve({ id, type, name, canonical_ref, notes, metadata, created_at: now, updated_at: now });
|
|
149
|
+
} catch (e) {
|
|
150
|
+
if (e instanceof TypeError) throw e;
|
|
151
|
+
return Promise.resolve(null);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get a Stakeholder row by id.
|
|
157
|
+
* @param {import('node:sqlite').DatabaseSync} db
|
|
158
|
+
* @param {string} id
|
|
159
|
+
* @returns {Promise<object|null>}
|
|
160
|
+
*/
|
|
161
|
+
async function getStakeholder(db, id) {
|
|
162
|
+
try {
|
|
163
|
+
const row = db.prepare('SELECT * FROM stakeholders WHERE id = ?').get(id);
|
|
164
|
+
return Promise.resolve(row || null);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return Promise.resolve(null);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Upsert a Stakeholder by canonical_ref. Updates in place if present, else inserts.
|
|
172
|
+
* Updates updated_at on every call.
|
|
173
|
+
* @param {import('node:sqlite').DatabaseSync} db
|
|
174
|
+
* @param {string} canonical_ref
|
|
175
|
+
* @param {object} fields
|
|
176
|
+
* @returns {Promise<object|null>}
|
|
177
|
+
*/
|
|
178
|
+
async function upsertStakeholder(db, canonical_ref, fields) {
|
|
179
|
+
try {
|
|
180
|
+
if (!canonical_ref) {
|
|
181
|
+
throw new TypeError('canonical_ref is required for upsertStakeholder');
|
|
182
|
+
}
|
|
183
|
+
const existing = db.prepare('SELECT * FROM stakeholders WHERE canonical_ref = ?').get(canonical_ref);
|
|
184
|
+
const now = new Date().toISOString();
|
|
185
|
+
if (existing) {
|
|
186
|
+
const type = fields.type || existing.type;
|
|
187
|
+
validateStakeholderType(type);
|
|
188
|
+
const name = fields.name || existing.name;
|
|
189
|
+
const notes = fields.notes !== undefined ? fields.notes : existing.notes;
|
|
190
|
+
const metadata = fields.metadata !== undefined
|
|
191
|
+
? (typeof fields.metadata === 'string' ? fields.metadata : JSON.stringify(fields.metadata))
|
|
192
|
+
: existing.metadata;
|
|
193
|
+
db.prepare(
|
|
194
|
+
'UPDATE stakeholders SET type = ?, name = ?, notes = ?, metadata = ?, updated_at = ? WHERE id = ?'
|
|
195
|
+
).run(type, name, notes, metadata, now, existing.id);
|
|
196
|
+
return Promise.resolve({ ...existing, type, name, notes, metadata, updated_at: now });
|
|
197
|
+
}
|
|
198
|
+
return createStakeholder(db, { ...fields, canonical_ref });
|
|
199
|
+
} catch (e) {
|
|
200
|
+
if (e instanceof TypeError) throw e;
|
|
201
|
+
return Promise.resolve(null);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Find stakeholders connected to a claim artifact via INFORMS edges.
|
|
207
|
+
* Walks both directions (stakeholder INFORMS claim, claim INFORMS stakeholder).
|
|
208
|
+
* @param {import('node:sqlite').DatabaseSync} db
|
|
209
|
+
* @param {string} claim_artifact_id
|
|
210
|
+
* @returns {Promise<Array<object>>}
|
|
211
|
+
*/
|
|
212
|
+
async function findStakeholdersByClaim(db, claim_artifact_id) {
|
|
213
|
+
try {
|
|
214
|
+
const rows = db.prepare(
|
|
215
|
+
`SELECT s.* FROM stakeholders s
|
|
216
|
+
WHERE s.id IN (
|
|
217
|
+
SELECT source FROM edges WHERE target = ? AND type = 'INFORMS'
|
|
218
|
+
UNION
|
|
219
|
+
SELECT target FROM edges WHERE source = ? AND type = 'INFORMS'
|
|
220
|
+
)
|
|
221
|
+
LIMIT 20`
|
|
222
|
+
).all(claim_artifact_id, claim_artifact_id);
|
|
223
|
+
return Promise.resolve(rows || []);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
return Promise.resolve([]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Helpers ---
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get artifact ID from file path relative to room dir.
|
|
233
|
+
* @param {string} filePath - Absolute path to .md file
|
|
234
|
+
* @param {string} roomDir - Absolute path to room directory
|
|
235
|
+
* @returns {string} e.g. "problem-definition/market-trends"
|
|
236
|
+
*/
|
|
237
|
+
function getArtifactId(filePath, roomDir) {
|
|
238
|
+
const rel = path.relative(path.resolve(roomDir), path.resolve(filePath));
|
|
239
|
+
return rel.replace(/\.md$/, '').replace(/\\/g, '/');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Extract title from first # heading in file content.
|
|
244
|
+
* @param {string} content - File content
|
|
245
|
+
* @param {string} filePath - Fallback basename
|
|
246
|
+
* @returns {string}
|
|
247
|
+
*/
|
|
248
|
+
function extractTitle(content, filePath) {
|
|
249
|
+
const match = content.match(/^# (.+)$/m);
|
|
250
|
+
return match ? match[1].trim() : path.basename(filePath, '.md');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Extract a frontmatter field value.
|
|
255
|
+
* @param {string} content - File content
|
|
256
|
+
* @param {string} field - Field name
|
|
257
|
+
* @returns {string}
|
|
258
|
+
*/
|
|
259
|
+
function extractFrontmatter(content, field) {
|
|
260
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
261
|
+
if (!fmMatch) return '';
|
|
262
|
+
const line = fmMatch[1].split('\n').find(l => l.startsWith(field + ':'));
|
|
263
|
+
if (!line) return '';
|
|
264
|
+
return line.slice(field.length + 1).trim().replace(/^["']|["']$/g, '');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Compute MD5 content hash (first 8 hex chars).
|
|
269
|
+
* @param {string} content
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
function computeHash(content) {
|
|
273
|
+
return crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// --- Contradiction detection terms ---
|
|
277
|
+
const CONTRADICT_TERMS = ['however', 'contradicts', 'unlike', 'disagrees', 'conflicts', 'contrary', 'opposes'];
|
|
278
|
+
|
|
279
|
+
// --- Core Functions ---
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Open (or create) a SQLite database at {roomDir}/.mindrian/room.db.
|
|
283
|
+
* Enables WAL mode and foreign keys. Initializes schema.
|
|
284
|
+
* @param {string} roomDir - Path to room directory
|
|
285
|
+
* @returns {Promise<{db: import('node:sqlite').DatabaseSync, conn: import('node:sqlite').DatabaseSync}>}
|
|
286
|
+
*/
|
|
287
|
+
async function openGraph(roomDir) {
|
|
288
|
+
const resolved = path.resolve(roomDir);
|
|
289
|
+
const dbDir = path.join(resolved, '.mindrian');
|
|
290
|
+
const dbPath = path.join(dbDir, 'room.db');
|
|
291
|
+
|
|
292
|
+
// Ensure directories exist
|
|
293
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
294
|
+
|
|
295
|
+
const db = new DatabaseSync(dbPath);
|
|
296
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
297
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
298
|
+
|
|
299
|
+
initSchema(db);
|
|
300
|
+
|
|
301
|
+
// conn === db in SQLite (single object handles both)
|
|
302
|
+
return Promise.resolve({ db, conn: db });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Close SQLite database.
|
|
307
|
+
* @param {import('node:sqlite').DatabaseSync} db - node:sqlite DatabaseSync instance
|
|
308
|
+
*/
|
|
309
|
+
async function closeGraph(db) {
|
|
310
|
+
try {
|
|
311
|
+
db.close();
|
|
312
|
+
} catch (e) {
|
|
313
|
+
// Handle already-closed or invalid db gracefully
|
|
314
|
+
}
|
|
315
|
+
return Promise.resolve();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Index a single .md artifact into the graph.
|
|
320
|
+
* Creates Artifact node, Section node, BELONGS_TO edge.
|
|
321
|
+
* Scans for [[wikilinks]] to create INFORMS edges.
|
|
322
|
+
* Scans for contradiction terms near wikilinks to create CONTRADICTS edges.
|
|
323
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
324
|
+
* @param {string} roomDir - Absolute path to room directory
|
|
325
|
+
* @param {string} filePath - Absolute path to .md file
|
|
326
|
+
* @returns {Promise<{id: string, section: string, title: string, contentHash: string}>}
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* Internal: run the INSERT body of indexArtifact WITHOUT opening a transaction.
|
|
330
|
+
* The caller is responsible for transaction semantics. Used by:
|
|
331
|
+
* - indexArtifact() -- wraps this in BEGIN/COMMIT/ROLLBACK
|
|
332
|
+
* - rebuildGraph() -- calls this inside its own outer BEGIN/COMMIT so the
|
|
333
|
+
* whole rebuild is atomic (no nested transactions, which SQLite forbids
|
|
334
|
+
* without SAVEPOINT).
|
|
335
|
+
* @param {import('node:sqlite').DatabaseSync} conn
|
|
336
|
+
* @param {string} roomDir
|
|
337
|
+
* @param {string} filePath
|
|
338
|
+
* @returns {{id: string, section: string, title: string, contentHash: string}}
|
|
339
|
+
*/
|
|
340
|
+
function _indexArtifactBody(conn, roomDir, filePath) {
|
|
341
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
342
|
+
const id = getArtifactId(filePath, roomDir);
|
|
343
|
+
const section = id.split('/')[0];
|
|
344
|
+
const title = extractTitle(content, filePath);
|
|
345
|
+
const methodology = extractFrontmatter(content, 'methodology');
|
|
346
|
+
const created = extractFrontmatter(content, 'date');
|
|
347
|
+
const contentHash = computeHash(content);
|
|
348
|
+
const enables = extractFrontmatter(content, 'enables');
|
|
349
|
+
const invalidates = extractFrontmatter(content, 'invalidates');
|
|
350
|
+
const wikilinks = content.match(/\[\[([^\]]+)\]\]/g) || [];
|
|
351
|
+
const artifactProps = JSON.stringify({ title, section, methodology, created, content_hash: contentHash });
|
|
352
|
+
const sectionLabel = section.replace(/-/g, ' ').toUpperCase();
|
|
353
|
+
const sectionProps = JSON.stringify({ name: section, label: sectionLabel });
|
|
354
|
+
|
|
355
|
+
// Upsert Artifact node
|
|
356
|
+
conn.prepare(
|
|
357
|
+
'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
|
|
358
|
+
).run(id, 'Artifact', artifactProps);
|
|
359
|
+
|
|
360
|
+
// Upsert Section node
|
|
361
|
+
conn.prepare(
|
|
362
|
+
'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET properties = excluded.properties'
|
|
363
|
+
).run(section, 'Section', sectionProps);
|
|
364
|
+
|
|
365
|
+
// Upsert BELONGS_TO edge
|
|
366
|
+
conn.prepare(
|
|
367
|
+
'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
|
|
368
|
+
).run(id, section, 'BELONGS_TO');
|
|
369
|
+
|
|
370
|
+
// Scan for [[wikilinks]] to create INFORMS edges
|
|
371
|
+
for (const link of wikilinks) {
|
|
372
|
+
const target = link.replace(/\[\[|\]\]/g, '').toLowerCase().replace(/\s/g, '-');
|
|
373
|
+
|
|
374
|
+
const linkIdx = content.indexOf(link);
|
|
375
|
+
const contextStart = Math.max(0, linkIdx - 200);
|
|
376
|
+
const contextEnd = Math.min(content.length, linkIdx + 200);
|
|
377
|
+
const nearbyText = content.slice(contextStart, contextEnd).toLowerCase();
|
|
378
|
+
|
|
379
|
+
const isContradiction = CONTRADICT_TERMS.some(term => nearbyText.includes(term));
|
|
380
|
+
|
|
381
|
+
const targetArtifacts = conn.prepare(
|
|
382
|
+
"SELECT id FROM nodes WHERE type = 'Artifact' AND json_extract(properties, '$.section') = ?"
|
|
383
|
+
).all(target);
|
|
384
|
+
|
|
385
|
+
for (const row of targetArtifacts) {
|
|
386
|
+
const targetId = row.id;
|
|
387
|
+
if (targetId === id) continue;
|
|
388
|
+
|
|
389
|
+
if (isContradiction) {
|
|
390
|
+
const contradictProps = JSON.stringify({ confidence: 'medium' });
|
|
391
|
+
conn.prepare(
|
|
392
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
393
|
+
).run(id, targetId, 'CONTRADICTS', contradictProps);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
conn.prepare(
|
|
397
|
+
'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
|
|
398
|
+
).run(id, targetId, 'INFORMS');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (enables) {
|
|
403
|
+
const targetRows = conn.prepare("SELECT id FROM nodes WHERE id = ? AND type = 'Artifact'").all(enables);
|
|
404
|
+
for (const row of targetRows) {
|
|
405
|
+
conn.prepare(
|
|
406
|
+
'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
|
|
407
|
+
).run(id, row.id, 'ENABLES');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (invalidates) {
|
|
412
|
+
const targetRows = conn.prepare("SELECT id FROM nodes WHERE id = ? AND type = 'Artifact'").all(invalidates);
|
|
413
|
+
for (const row of targetRows) {
|
|
414
|
+
conn.prepare(
|
|
415
|
+
'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
|
|
416
|
+
).run(id, row.id, 'INVALIDATES');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { id, section, title, contentHash };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function indexArtifact(conn, roomDir, filePath) {
|
|
424
|
+
// --- Transaction: all INSERTs or none (Plan 87-06 / CASCADE-04) ---
|
|
425
|
+
// node:sqlite DatabaseSync does NOT expose a transaction(fn) higher-order
|
|
426
|
+
// helper (that is a better-sqlite3 API). We use explicit BEGIN/COMMIT with
|
|
427
|
+
// ROLLBACK on any throw. On successful completion, SQLite commits the
|
|
428
|
+
// block; on any error, we roll back the partial writes and re-throw so
|
|
429
|
+
// the caller sees the original error.
|
|
430
|
+
//
|
|
431
|
+
// Why this matters (CASCADE-04): a pre-patch throw between two INSERTs
|
|
432
|
+
// could leave dangling nodes without their edges, or vice versa. The
|
|
433
|
+
// BEGIN/COMMIT wrapper converts the whole indexArtifact body into a
|
|
434
|
+
// single atomic unit. The post-condition is either "all writes survive"
|
|
435
|
+
// or "no writes survive" -- there is no middle state.
|
|
436
|
+
//
|
|
437
|
+
// Pure computation (file read, hash, wikilink scan) runs inside the txn
|
|
438
|
+
// but is idempotent and side-effect free. Moving it outside would require
|
|
439
|
+
// duplicating it in _indexArtifactBody and the rebuildGraph call site;
|
|
440
|
+
// keeping it inside keeps indexArtifact a thin BEGIN/COMMIT shell.
|
|
441
|
+
conn.prepare('BEGIN').run();
|
|
442
|
+
let result;
|
|
443
|
+
try {
|
|
444
|
+
result = _indexArtifactBody(conn, roomDir, filePath);
|
|
445
|
+
conn.prepare('COMMIT').run();
|
|
446
|
+
} catch (err) {
|
|
447
|
+
// ROLLBACK undoes every INSERT issued since BEGIN. We swallow any error
|
|
448
|
+
// from ROLLBACK itself (rare: can happen if the connection is already
|
|
449
|
+
// aborted) and always re-throw the ORIGINAL error so the caller sees
|
|
450
|
+
// the failure reason, not a ROLLBACK noise error.
|
|
451
|
+
try { conn.prepare('ROLLBACK').run(); } catch (_rbErr) { /* ignore */ }
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return Promise.resolve(result);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Rebuild the entire graph from all room artifacts.
|
|
460
|
+
* Clears existing data, walks all sections, indexes every .md file.
|
|
461
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
462
|
+
* @param {string} roomDir - Absolute path to room directory
|
|
463
|
+
* @returns {Promise<{success: boolean, artifacts: number, sections: number}>}
|
|
464
|
+
*/
|
|
465
|
+
async function rebuildGraph(conn, roomDir) {
|
|
466
|
+
const resolved = path.resolve(roomDir);
|
|
467
|
+
|
|
468
|
+
// Wrap entire rebuild in a transaction for atomicity.
|
|
469
|
+
// If anything throws mid-rebuild, the DB rolls back to pre-rebuild state.
|
|
470
|
+
//
|
|
471
|
+
// NOTE (Plan 87-06): use explicit BEGIN/COMMIT/ROLLBACK because
|
|
472
|
+
// node:sqlite DatabaseSync does NOT expose a transaction(fn) higher-order
|
|
473
|
+
// helper (that is a better-sqlite3 API). Calling the inner
|
|
474
|
+
// `_indexArtifactBody` (not `indexArtifact`) avoids a nested BEGIN that
|
|
475
|
+
// SQLite would reject ("cannot start a transaction within a transaction"
|
|
476
|
+
// without SAVEPOINT).
|
|
477
|
+
const discovery = discoverSections(resolved);
|
|
478
|
+
const sectionNames = discovery.all;
|
|
479
|
+
let artifactCount = 0;
|
|
480
|
+
|
|
481
|
+
conn.prepare('BEGIN').run();
|
|
482
|
+
try {
|
|
483
|
+
// Clear all existing data (edges first for FK compliance)
|
|
484
|
+
conn.exec('DELETE FROM edges; DELETE FROM nodes;');
|
|
485
|
+
|
|
486
|
+
for (const sectionName of sectionNames) {
|
|
487
|
+
const sectionDir = path.join(resolved, sectionName);
|
|
488
|
+
let files;
|
|
489
|
+
try {
|
|
490
|
+
files = fs.readdirSync(sectionDir).filter(f =>
|
|
491
|
+
f.endsWith('.md') && f !== 'STATE.md' && f !== 'ROOM.md'
|
|
492
|
+
);
|
|
493
|
+
} catch (e) {
|
|
494
|
+
continue; // skip this section
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
for (const file of files) {
|
|
498
|
+
const filePath = path.join(sectionDir, file);
|
|
499
|
+
_indexArtifactBody(conn, resolved, filePath); // inside outer BEGIN, no nested txn
|
|
500
|
+
artifactCount++;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
conn.prepare('COMMIT').run();
|
|
504
|
+
} catch (err) {
|
|
505
|
+
try { conn.prepare('ROLLBACK').run(); } catch (_rbErr) { /* ignore */ }
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return Promise.resolve({ success: true, artifacts: artifactCount, sections: sectionNames.length });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Execute a SQL query and return all result rows.
|
|
514
|
+
* Gracefully returns empty array on error.
|
|
515
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
516
|
+
* @param {string} sql - SQL query string
|
|
517
|
+
* @param {Array<*>|Object} [params] - bound parameters; array for anonymous (?) placeholders, object for named ($x)
|
|
518
|
+
* @returns {Promise<Array<object>>}
|
|
519
|
+
*/
|
|
520
|
+
async function queryGraph(conn, sql, params) {
|
|
521
|
+
try {
|
|
522
|
+
const stmt = conn.prepare(sql);
|
|
523
|
+
if (params == null) {
|
|
524
|
+
return Promise.resolve(stmt.all());
|
|
525
|
+
}
|
|
526
|
+
if (Array.isArray(params)) {
|
|
527
|
+
return Promise.resolve(params.length ? stmt.all(...params) : stmt.all());
|
|
528
|
+
}
|
|
529
|
+
return Promise.resolve(stmt.all(params));
|
|
530
|
+
} catch (e) {
|
|
531
|
+
return Promise.resolve([]);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Get graph statistics: node counts, edge counts, totals.
|
|
537
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
538
|
+
* @returns {Promise<{nodes: object, edges: object, total: {nodes: number, edges: number}}>}
|
|
539
|
+
*/
|
|
540
|
+
async function graphStats(conn) {
|
|
541
|
+
// Count nodes by type
|
|
542
|
+
// Count ALL node types dynamically (not hardcoded)
|
|
543
|
+
const nodeCounts = {};
|
|
544
|
+
const nodeRows = conn.prepare('SELECT type, COUNT(*) AS cnt FROM nodes GROUP BY type').all();
|
|
545
|
+
for (const row of nodeRows) {
|
|
546
|
+
nodeCounts[row.type] = row.cnt;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Count edges by type
|
|
550
|
+
const edges = {};
|
|
551
|
+
for (const edgeType of EDGE_TYPES) { edges[edgeType] = 0; }
|
|
552
|
+
const edgeRows = conn.prepare('SELECT type, COUNT(*) AS cnt FROM edges GROUP BY type').all();
|
|
553
|
+
for (const row of edgeRows) {
|
|
554
|
+
if (row.type in edges) {
|
|
555
|
+
edges[row.type] = row.cnt;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const totalNodes = Object.values(nodeCounts).reduce((sum, n) => sum + n, 0);
|
|
560
|
+
const totalEdges = Object.values(edges).reduce((sum, n) => sum + n, 0);
|
|
561
|
+
|
|
562
|
+
return Promise.resolve({
|
|
563
|
+
nodes: nodeCounts,
|
|
564
|
+
edges,
|
|
565
|
+
total: { nodes: totalNodes, edges: totalEdges },
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Tier 2 Pinecone semantic layer stub.
|
|
571
|
+
* Embeds an artifact for semantic search when Pinecone is configured.
|
|
572
|
+
* Gracefully degrades when Pinecone env vars are not set.
|
|
573
|
+
*
|
|
574
|
+
* Contract: embedArtifact(roomDir, filePath) -> { success: boolean, reason?: string, embeddingId?: string }
|
|
575
|
+
*
|
|
576
|
+
* @param {string} roomDir - Absolute path to room directory
|
|
577
|
+
* @param {string} filePath - Absolute path to .md artifact file
|
|
578
|
+
* @returns {Promise<{success: boolean, reason?: string, embeddingId?: string}>}
|
|
579
|
+
*/
|
|
580
|
+
async function embedArtifact(roomDir, filePath) {
|
|
581
|
+
// Read the artifact content (validates the file exists)
|
|
582
|
+
const resolved = path.resolve(filePath);
|
|
583
|
+
if (!fs.existsSync(resolved)) {
|
|
584
|
+
return { success: false, reason: `Artifact not found: ${filePath}` };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const apiKey = process.env.PINECONE_API_KEY;
|
|
588
|
+
const index = process.env.PINECONE_INDEX;
|
|
589
|
+
|
|
590
|
+
if (!apiKey || !index) {
|
|
591
|
+
return {
|
|
592
|
+
success: false,
|
|
593
|
+
reason: 'Pinecone Tier 2 not configured - set PINECONE_API_KEY and PINECONE_INDEX to enable semantic search',
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Pinecone env vars are set but integration not yet wired
|
|
598
|
+
return {
|
|
599
|
+
success: false,
|
|
600
|
+
reason: 'Pinecone Tier 2 integration not yet implemented - stub ready for future wiring',
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// --- Design-by-Analogy Edge Creation (DBA-08) ---
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Create an ANALOGOUS_TO edge between two artifacts.
|
|
608
|
+
* Records functional analogy with distance, fitness, and transfer mapping.
|
|
609
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
610
|
+
* @param {string} sourceId - Source artifact ID
|
|
611
|
+
* @param {string} targetId - Target artifact ID
|
|
612
|
+
* @param {object} props - Edge properties
|
|
613
|
+
* @param {string} [props.analogy_distance='near'] - near|far|cross-domain
|
|
614
|
+
* @param {number} [props.structural_fitness=0.0] - 0-1 structural fitness score
|
|
615
|
+
* @param {string} [props.source_domain=''] - Domain of the source analogy
|
|
616
|
+
* @param {string} [props.transfer_map='{}'] - JSON string mapping source to target elements
|
|
617
|
+
* @param {string} [props.discovery_method='hsi'] - hsi|brain|llm|external|user
|
|
618
|
+
* @returns {Promise<boolean>}
|
|
619
|
+
*/
|
|
620
|
+
async function createAnalogyEdge(conn, sourceId, targetId, props = {}) {
|
|
621
|
+
const edgeProps = JSON.stringify({
|
|
622
|
+
analogy_distance: props.analogy_distance || 'near',
|
|
623
|
+
structural_fitness: props.structural_fitness || 0.0,
|
|
624
|
+
source_domain: props.source_domain || '',
|
|
625
|
+
transfer_map: props.transfer_map || '{}',
|
|
626
|
+
discovery_method: props.discovery_method || 'hsi',
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
conn.prepare(
|
|
630
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
631
|
+
).run(sourceId, targetId, 'ANALOGOUS_TO', edgeProps);
|
|
632
|
+
return Promise.resolve(true);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Create a STRUCTURALLY_ISOMORPHIC edge between two sections.
|
|
637
|
+
* Records identical relational topology between room sections.
|
|
638
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
639
|
+
* @param {string} sectionA - Source section name
|
|
640
|
+
* @param {string} sectionB - Target section name
|
|
641
|
+
* @param {object} props - Edge properties
|
|
642
|
+
* @param {number} [props.isomorphism_score=0.0] - 0-1 isomorphism score
|
|
643
|
+
* @param {string} [props.mapped_elements='{}'] - JSON string of element mappings
|
|
644
|
+
* @param {string} [props.source=''] - Source of the isomorphism detection
|
|
645
|
+
* @returns {Promise<boolean>}
|
|
646
|
+
*/
|
|
647
|
+
async function createIsomorphismEdge(conn, sectionA, sectionB, props = {}) {
|
|
648
|
+
const edgeProps = JSON.stringify({
|
|
649
|
+
isomorphism_score: props.isomorphism_score || 0.0,
|
|
650
|
+
mapped_elements: props.mapped_elements || '{}',
|
|
651
|
+
source: props.source || '',
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
conn.prepare(
|
|
655
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
656
|
+
).run(sectionA, sectionB, 'STRUCTURALLY_ISOMORPHIC', edgeProps);
|
|
657
|
+
return Promise.resolve(true);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Create a RESOLVES_VIA edge linking a contradiction to its resolution.
|
|
662
|
+
* Closes the loop: contradiction -> analogy/TRIZ -> resolution.
|
|
663
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
664
|
+
* @param {string} sourceId - Artifact ID (the contradicting artifact)
|
|
665
|
+
* @param {string} targetId - Artifact ID (the resolution artifact)
|
|
666
|
+
* @param {object} props - Edge properties
|
|
667
|
+
* @param {string} [props.resolution_type='direct'] - analogy|triz_principle|direct
|
|
668
|
+
* @param {string} [props.triz_principle=''] - TRIZ principle number/name if applicable
|
|
669
|
+
* @param {string} [props.analogy_source=''] - Source analogy reference if applicable
|
|
670
|
+
* @param {number} [props.confidence=0.0] - 0-1 confidence in resolution
|
|
671
|
+
* @returns {Promise<boolean>}
|
|
672
|
+
*/
|
|
673
|
+
async function createResolutionEdge(conn, sourceId, targetId, props = {}) {
|
|
674
|
+
const edgeProps = JSON.stringify({
|
|
675
|
+
resolution_type: props.resolution_type || 'direct',
|
|
676
|
+
triz_principle: props.triz_principle || '',
|
|
677
|
+
analogy_source: props.analogy_source || '',
|
|
678
|
+
confidence: props.confidence || 0.0,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
conn.prepare(
|
|
682
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
683
|
+
).run(sourceId, targetId, 'RESOLVES_VIA', edgeProps);
|
|
684
|
+
return Promise.resolve(true);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// --- Causal Extraction CRUD (Phase 53) ---
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Create or update a CausalClaim node in SQLite.
|
|
691
|
+
* Writes all 12 properties as JSON. Truncates cause/effect to 200 chars,
|
|
692
|
+
* mechanism to 300 chars to prevent oversized nodes.
|
|
693
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
694
|
+
* @param {object} claim - CausalClaim properties
|
|
695
|
+
* @param {string} claim.id - Unique claim ID
|
|
696
|
+
* @param {string} claim.cause - Cause statement (truncated to 200 chars)
|
|
697
|
+
* @param {string} claim.mechanism - Mechanism explanation (truncated to 300 chars)
|
|
698
|
+
* @param {string} claim.effect - Effect statement (truncated to 200 chars)
|
|
699
|
+
* @param {number} [claim.confidence=0.5] - Confidence score (0-1)
|
|
700
|
+
* @param {string} [claim.evidence='[]'] - JSON array of evidence references
|
|
701
|
+
* @param {string} [claim.source_artifact=''] - Source artifact ID
|
|
702
|
+
* @param {string} [claim.domain='general'] - Domain classification
|
|
703
|
+
* @param {string} [claim.falsifiable_prediction=''] - Testable prediction
|
|
704
|
+
* @param {number} [claim.novelty_score=0.0] - Novelty score (0-1)
|
|
705
|
+
* @param {string} [claim.extraction_method='inferred'] - observed|asserted|inferred
|
|
706
|
+
* @param {string} [claim.created=''] - ISO date string
|
|
707
|
+
* @returns {Promise<boolean>}
|
|
708
|
+
*/
|
|
709
|
+
async function createCausalClaim(conn, claim) {
|
|
710
|
+
const cause = (claim.cause || '').slice(0, 200);
|
|
711
|
+
const mechanism = (claim.mechanism || '').slice(0, 300);
|
|
712
|
+
const effect = (claim.effect || '').slice(0, 200);
|
|
713
|
+
const confidence = typeof claim.confidence === 'number' ? claim.confidence : 0.5;
|
|
714
|
+
const evidence = Array.isArray(claim.evidence) ? JSON.stringify(claim.evidence) : (claim.evidence || '[]');
|
|
715
|
+
const sourceArtifact = claim.source_artifact || '';
|
|
716
|
+
const domain = claim.domain || 'general';
|
|
717
|
+
const prediction = claim.falsifiable_prediction || '';
|
|
718
|
+
const novelty = typeof claim.novelty_score === 'number' ? claim.novelty_score : 0.0;
|
|
719
|
+
const method = claim.extraction_method || 'inferred';
|
|
720
|
+
const created = claim.created || '';
|
|
721
|
+
|
|
722
|
+
const props = JSON.stringify({
|
|
723
|
+
cause, mechanism, effect, confidence, evidence, source_artifact: sourceArtifact,
|
|
724
|
+
domain, falsifiable_prediction: prediction, novelty_score: novelty,
|
|
725
|
+
extraction_method: method, created,
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
conn.prepare(
|
|
729
|
+
'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
|
|
730
|
+
).run(claim.id, 'CausalClaim', props);
|
|
731
|
+
return Promise.resolve(true);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Create an EXTRACTED_FROM edge linking a CausalClaim to its source Artifact.
|
|
736
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
737
|
+
* @param {string} claimId - CausalClaim node ID
|
|
738
|
+
* @param {string} artifactId - Artifact node ID
|
|
739
|
+
* @returns {Promise<boolean>}
|
|
740
|
+
*/
|
|
741
|
+
async function createExtractedFromEdge(conn, claimId, artifactId) {
|
|
742
|
+
conn.prepare(
|
|
743
|
+
'INSERT INTO edges (source, target, type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING'
|
|
744
|
+
).run(claimId, artifactId, 'EXTRACTED_FROM');
|
|
745
|
+
return Promise.resolve(true);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// --- Causal Graph Engine (Phase 54) ---
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Create a CASCADES_TO edge between two CausalClaim nodes.
|
|
752
|
+
* Idempotent via ON CONFLICT. Records cascade type and severity.
|
|
753
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
754
|
+
* @param {string} fromClaimId - Source CausalClaim node ID
|
|
755
|
+
* @param {string} toClaimId - Target CausalClaim node ID
|
|
756
|
+
* @param {object} [opts] - Edge properties
|
|
757
|
+
* @param {string} [opts.cascade_type='invalidation'] - invalidation|weakening|dependency
|
|
758
|
+
* @param {string} [opts.severity='medium'] - high|medium|low
|
|
759
|
+
* @param {number} [opts.path_length=1] - Hop distance
|
|
760
|
+
* @returns {Promise<boolean>}
|
|
761
|
+
*/
|
|
762
|
+
async function createCascadesToEdge(conn, fromClaimId, toClaimId, opts = {}) {
|
|
763
|
+
const edgeProps = JSON.stringify({
|
|
764
|
+
cascade_type: opts.cascade_type || 'invalidation',
|
|
765
|
+
severity: opts.severity || 'medium',
|
|
766
|
+
path_length: typeof opts.path_length === 'number' ? opts.path_length : 1,
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
conn.prepare(
|
|
770
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
771
|
+
).run(fromClaimId, toClaimId, 'CASCADES_TO', edgeProps);
|
|
772
|
+
return Promise.resolve(true);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Export all CausalClaim nodes and CASCADES_TO edges as JSON.
|
|
777
|
+
* Writes .lazygraph-causal-export.json to roomDir for Python engine consumption.
|
|
778
|
+
* Handles empty graphs gracefully (writes JSON with empty arrays).
|
|
779
|
+
* @param {string} roomDir - Path to room directory
|
|
780
|
+
* @returns {Promise<{metadata: object, nodes: Array, edges: Array}>}
|
|
781
|
+
*/
|
|
782
|
+
async function exportCausalGraph(roomDir) {
|
|
783
|
+
const resolved = path.resolve(roomDir);
|
|
784
|
+
const { db, conn } = await openGraph(resolved);
|
|
785
|
+
try {
|
|
786
|
+
// Query all CausalClaim nodes
|
|
787
|
+
let nodes = [];
|
|
788
|
+
try {
|
|
789
|
+
nodes = conn.prepare(
|
|
790
|
+
"SELECT id, json_extract(properties, '$.cause') AS cause, json_extract(properties, '$.mechanism') AS mechanism, json_extract(properties, '$.effect') AS effect, json_extract(properties, '$.confidence') AS confidence, json_extract(properties, '$.domain') AS domain, json_extract(properties, '$.source_artifact') AS source_artifact FROM nodes WHERE type = 'CausalClaim'"
|
|
791
|
+
).all();
|
|
792
|
+
} catch (e) {
|
|
793
|
+
// table may be empty or schema different
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Query all CASCADES_TO edges
|
|
797
|
+
let edges = [];
|
|
798
|
+
try {
|
|
799
|
+
edges = conn.prepare(
|
|
800
|
+
"SELECT source, target, json_extract(properties, '$.cascade_type') AS cascade_type, json_extract(properties, '$.severity') AS severity FROM edges WHERE type = 'CASCADES_TO'"
|
|
801
|
+
).all();
|
|
802
|
+
} catch (e) {
|
|
803
|
+
// no CASCADES_TO edges
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const exportData = {
|
|
807
|
+
metadata: {
|
|
808
|
+
exported_at: new Date().toISOString(),
|
|
809
|
+
node_count: nodes.length,
|
|
810
|
+
edge_count: edges.length,
|
|
811
|
+
},
|
|
812
|
+
nodes,
|
|
813
|
+
edges,
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const exportPath = path.join(resolved, '.lazygraph-causal-export.json');
|
|
817
|
+
fs.writeFileSync(exportPath, JSON.stringify(exportData, null, 2), 'utf-8');
|
|
818
|
+
return exportData;
|
|
819
|
+
} finally {
|
|
820
|
+
await closeGraph(db);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// --- TRIZ Contradiction Enrichment (DBA-09) ---
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Enrich an existing CONTRADICTS edge with TRIZ parameter classification.
|
|
828
|
+
* Looks up triz-matrix.json to suggest inventive principles for the contradiction.
|
|
829
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
830
|
+
* @param {string} artifactA - Source artifact ID
|
|
831
|
+
* @param {string} artifactB - Target artifact ID
|
|
832
|
+
* @param {string} improvingParam - TRIZ parameter being improved (one of 39)
|
|
833
|
+
* @param {string} worseningParam - TRIZ parameter being worsened (one of 39)
|
|
834
|
+
* @returns {Promise<{success: boolean, principles: number[], reason?: string}>}
|
|
835
|
+
*/
|
|
836
|
+
async function enrichContradictionWithTRIZ(conn, artifactA, artifactB, improvingParam, worseningParam) {
|
|
837
|
+
// Load TRIZ matrix
|
|
838
|
+
const matrixPath = path.join(__dirname, '..', '..', 'references', 'methodology', 'triz-matrix.json');
|
|
839
|
+
if (!fs.existsSync(matrixPath)) {
|
|
840
|
+
return { success: false, principles: [], reason: 'triz-matrix.json not found' };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
let matrix;
|
|
844
|
+
try {
|
|
845
|
+
matrix = JSON.parse(fs.readFileSync(matrixPath, 'utf-8'));
|
|
846
|
+
} catch (e) {
|
|
847
|
+
return { success: false, principles: [], reason: 'Failed to parse triz-matrix.json' };
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Look up principles
|
|
851
|
+
const improvingEntry = matrix[improvingParam];
|
|
852
|
+
if (!improvingEntry) {
|
|
853
|
+
return { success: false, principles: [], reason: `Unknown improving parameter: ${improvingParam}` };
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const principles = improvingEntry[worseningParam];
|
|
857
|
+
if (!principles || !Array.isArray(principles) || principles.length === 0) {
|
|
858
|
+
return { success: false, principles: [], reason: `No principles found for ${improvingParam} vs ${worseningParam}` };
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const principlesStr = principles.join(',');
|
|
862
|
+
|
|
863
|
+
// Create a RESOLVES_VIA edge capturing the TRIZ resolution direction
|
|
864
|
+
const edgeProps = JSON.stringify({
|
|
865
|
+
resolution_type: 'triz_principle',
|
|
866
|
+
triz_principle: principlesStr,
|
|
867
|
+
analogy_source: `${improvingParam} vs ${worseningParam}`,
|
|
868
|
+
confidence: 0.7,
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
conn.prepare(
|
|
872
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
873
|
+
).run(artifactA, artifactB, 'RESOLVES_VIA', edgeProps);
|
|
874
|
+
|
|
875
|
+
return { success: true, principles };
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// --- Whitespace Zone CRUD (Phase 61) ---
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Create or update a WhitespaceZone node in SQLite.
|
|
882
|
+
* Writes all properties as JSON. Idempotent.
|
|
883
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
884
|
+
* @param {object} zone - WhitespaceZone properties
|
|
885
|
+
* @param {string} zone.id - Unique zone ID
|
|
886
|
+
* @param {string} zone.brain_framework - Brain framework name this gap relates to
|
|
887
|
+
* @param {number} [zone.density_score=0.0] - Embedding space density score
|
|
888
|
+
* @param {number} [zone.knn_density=0.0] - KNN density from SemNovel algorithm
|
|
889
|
+
* @param {string} [zone.nearest_frameworks='[]'] - JSON array of nearest framework names
|
|
890
|
+
* @param {string} [zone.hypothesis=''] - Generated hypothesis about what should fill this gap
|
|
891
|
+
* @param {number} [zone.strategic_rank=0.0] - Strategic importance ranking
|
|
892
|
+
* @param {string} [zone.problem_type=''] - Problem type classification
|
|
893
|
+
* @param {string} [zone.exploration_status='detected'] - detected|exploring|resolved|dismissed
|
|
894
|
+
* @param {string} [zone.created=''] - ISO date string
|
|
895
|
+
* @returns {Promise<boolean>}
|
|
896
|
+
*/
|
|
897
|
+
async function addWhitespaceZone(conn, zone) {
|
|
898
|
+
const props = JSON.stringify({
|
|
899
|
+
brain_framework: zone.brain_framework || '',
|
|
900
|
+
density_score: typeof zone.density_score === 'number' ? zone.density_score : 0.0,
|
|
901
|
+
knn_density: typeof zone.knn_density === 'number' ? zone.knn_density : 0.0,
|
|
902
|
+
nearest_frameworks: Array.isArray(zone.nearest_frameworks) ? JSON.stringify(zone.nearest_frameworks) : (zone.nearest_frameworks || '[]'),
|
|
903
|
+
hypothesis: (zone.hypothesis || '').slice(0, 500),
|
|
904
|
+
strategic_rank: typeof zone.strategic_rank === 'number' ? zone.strategic_rank : 0.0,
|
|
905
|
+
problem_type: zone.problem_type || '',
|
|
906
|
+
exploration_status: zone.exploration_status || 'detected',
|
|
907
|
+
created: zone.created || '',
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
conn.prepare(
|
|
911
|
+
'INSERT INTO nodes (id, type, properties) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET type = excluded.type, properties = excluded.properties'
|
|
912
|
+
).run(zone.id, 'WhitespaceZone', props);
|
|
913
|
+
return Promise.resolve(true);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Create a WHITESPACE_DETECTED edge from a WhitespaceZone to a nearby Artifact.
|
|
918
|
+
* Records embedding distance. Idempotent.
|
|
919
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
920
|
+
* @param {string} zoneId - WhitespaceZone node ID
|
|
921
|
+
* @param {string} artifactId - Artifact node ID
|
|
922
|
+
* @param {number} [distance=0.0] - Embedding distance between zone centroid and artifact
|
|
923
|
+
* @returns {Promise<boolean>}
|
|
924
|
+
*/
|
|
925
|
+
async function linkWhitespaceToArtifact(conn, zoneId, artifactId, distance = 0.0) {
|
|
926
|
+
const edgeProps = JSON.stringify({ distance });
|
|
927
|
+
conn.prepare(
|
|
928
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
929
|
+
).run(zoneId, artifactId, 'WHITESPACE_DETECTED', edgeProps);
|
|
930
|
+
return Promise.resolve(true);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Create a WHITESPACE_NEAR edge from a WhitespaceZone to a Section.
|
|
935
|
+
* Records relevance score. Idempotent.
|
|
936
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
937
|
+
* @param {string} zoneId - WhitespaceZone node ID
|
|
938
|
+
* @param {string} sectionName - Section node name
|
|
939
|
+
* @param {number} [relevance=0.0] - Relevance score (0-1)
|
|
940
|
+
* @returns {Promise<boolean>}
|
|
941
|
+
*/
|
|
942
|
+
async function linkWhitespaceToSection(conn, zoneId, sectionName, relevance = 0.0) {
|
|
943
|
+
const edgeProps = JSON.stringify({ relevance });
|
|
944
|
+
conn.prepare(
|
|
945
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
946
|
+
).run(zoneId, sectionName, 'WHITESPACE_NEAR', edgeProps);
|
|
947
|
+
return Promise.resolve(true);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Link a WhitespaceZone to an Artifact via DISCOVERY_CYCLE_SOURCE edge.
|
|
952
|
+
* Records which discovery method (hsi, rs, analogy) found this zone.
|
|
953
|
+
* @param {import('node:sqlite').DatabaseSync} conn - node:sqlite DatabaseSync instance
|
|
954
|
+
* @param {string} zoneId - WhitespaceZone ID
|
|
955
|
+
* @param {string} artifactId - Source Artifact ID
|
|
956
|
+
* @param {string} discoveryMethod - 'hsi' | 'rs' | 'analogy'
|
|
957
|
+
* @param {string} [cycleTimestamp] - ISO timestamp of discovery cycle run
|
|
958
|
+
* @returns {Promise<boolean>}
|
|
959
|
+
*/
|
|
960
|
+
async function linkDiscoveryCycleSource(conn, zoneId, artifactId, discoveryMethod, cycleTimestamp = '') {
|
|
961
|
+
const ts = cycleTimestamp || new Date().toISOString();
|
|
962
|
+
const edgeProps = JSON.stringify({ discovery_method: discoveryMethod, cycle_timestamp: ts });
|
|
963
|
+
conn.prepare(
|
|
964
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
965
|
+
).run(zoneId, artifactId, 'DISCOVERY_CYCLE_SOURCE', edgeProps);
|
|
966
|
+
return Promise.resolve(true);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// --- Phase 89-07 Wave 1: generic typed-edge upsert primitive ---
|
|
970
|
+
//
|
|
971
|
+
// upsertEdge is the single chokepoint by which agentic surfaces (Phase 89-07
|
|
972
|
+
// ReverseSalientAgent, Phase 116 tension hook, Phase 117 auto-explore, Phase
|
|
973
|
+
// 118 MVA, Phase 120 breakthrough scan) emit typed cascade edges. It validates
|
|
974
|
+
// the edge type against EDGE_TYPES and performs the same UPSERT pattern used
|
|
975
|
+
// by every other edge writer in this module.
|
|
976
|
+
//
|
|
977
|
+
// Shape: upsertEdge(conn, { type, source, target, properties }) -> { ok, reason? }
|
|
978
|
+
// - conn: node:sqlite DatabaseSync instance (or any object with .prepare).
|
|
979
|
+
// - type: one of EDGE_TYPES (string).
|
|
980
|
+
// - source: source node id (string).
|
|
981
|
+
// - target: target node id (string).
|
|
982
|
+
// - properties: optional object; serialized to JSON.
|
|
983
|
+
//
|
|
984
|
+
// Synchronous (no Promise wrapping); the underlying prepare/run is sync per
|
|
985
|
+
// node:sqlite contract. Per-call shape mirrors the rest of this module.
|
|
986
|
+
//
|
|
987
|
+
// Canon Part 4: every choice is graph data; this primitive lets every agent
|
|
988
|
+
// emit typed edges without bypassing EDGE_TYPES validation. Canon Part 7:
|
|
989
|
+
// reuse-before-build; sibling agents reuse this instead of inlining SQL.
|
|
990
|
+
function upsertEdge(conn, edge) {
|
|
991
|
+
if (!edge || typeof edge !== 'object') {
|
|
992
|
+
return { ok: false, reason: 'invalid_edge_object' };
|
|
993
|
+
}
|
|
994
|
+
const { type, source, target } = edge;
|
|
995
|
+
if (typeof type !== 'string' || !EDGE_TYPES.includes(type)) {
|
|
996
|
+
return { ok: false, reason: 'invalid_edge_type', detail: String(type).slice(0, 40) };
|
|
997
|
+
}
|
|
998
|
+
if (typeof source !== 'string' || source.length === 0) {
|
|
999
|
+
return { ok: false, reason: 'invalid_source_id' };
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
1002
|
+
return { ok: false, reason: 'invalid_target_id' };
|
|
1003
|
+
}
|
|
1004
|
+
const props = edge.properties && typeof edge.properties === 'object' ? edge.properties : {};
|
|
1005
|
+
let propsJson;
|
|
1006
|
+
try {
|
|
1007
|
+
propsJson = JSON.stringify(props);
|
|
1008
|
+
} catch (_e) {
|
|
1009
|
+
return { ok: false, reason: 'properties_serialize_failed' };
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
conn.prepare(
|
|
1013
|
+
'INSERT INTO edges (source, target, type, properties) VALUES (?, ?, ?, ?) ON CONFLICT(source, target, type) DO UPDATE SET properties = excluded.properties'
|
|
1014
|
+
).run(source, target, type, propsJson);
|
|
1015
|
+
} catch (e) {
|
|
1016
|
+
return { ok: false, reason: 'edge_write_failed', detail: String(e.message || '').slice(0, 80) };
|
|
1017
|
+
}
|
|
1018
|
+
return { ok: true, type, source, target };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// --- Exports ---
|
|
1022
|
+
|
|
1023
|
+
module.exports = {
|
|
1024
|
+
EDGE_TYPES,
|
|
1025
|
+
upsertEdge,
|
|
1026
|
+
openGraph,
|
|
1027
|
+
closeGraph,
|
|
1028
|
+
initSchema,
|
|
1029
|
+
indexArtifact,
|
|
1030
|
+
rebuildGraph,
|
|
1031
|
+
queryGraph,
|
|
1032
|
+
graphStats,
|
|
1033
|
+
embedArtifact,
|
|
1034
|
+
// Design-by-Analogy (DBA-08, DBA-09)
|
|
1035
|
+
createAnalogyEdge,
|
|
1036
|
+
createIsomorphismEdge,
|
|
1037
|
+
createResolutionEdge,
|
|
1038
|
+
enrichContradictionWithTRIZ,
|
|
1039
|
+
// Causal Extraction (Phase 53)
|
|
1040
|
+
createCausalClaim,
|
|
1041
|
+
createExtractedFromEdge,
|
|
1042
|
+
// Causal Graph Engine (Phase 54)
|
|
1043
|
+
createCascadesToEdge,
|
|
1044
|
+
exportCausalGraph,
|
|
1045
|
+
// Whitespace Zone Layer (Phase 61)
|
|
1046
|
+
addWhitespaceZone,
|
|
1047
|
+
linkWhitespaceToArtifact,
|
|
1048
|
+
linkWhitespaceToSection,
|
|
1049
|
+
// Discovery Cycle (Phase 64)
|
|
1050
|
+
linkDiscoveryCycleSource,
|
|
1051
|
+
// Stakeholder node type (Phase 84-05, SCOPE-NB-03 / SCOPE-NB-04)
|
|
1052
|
+
STAKEHOLDER_TYPES,
|
|
1053
|
+
createStakeholder,
|
|
1054
|
+
getStakeholder,
|
|
1055
|
+
upsertStakeholder,
|
|
1056
|
+
findStakeholdersByClaim,
|
|
1057
|
+
};
|