nodedex 0.1.0
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/adapters/claude-code-watcher.mjs +336 -0
- package/adapters/hermes-statedb-watcher.mjs +234 -0
- package/adapters/nodedex-capture-core.mjs +129 -0
- package/adapters/nodedex-capture.mjs +169 -0
- package/dist/agent-protocol.d.ts +7 -0
- package/dist/agent-protocol.d.ts.map +1 -0
- package/dist/agent-protocol.js +38 -0
- package/dist/agent-protocol.js.map +1 -0
- package/dist/api-server.d.ts +5 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +351 -0
- package/dist/api-server.js.map +1 -0
- package/dist/boot-env.d.ts +2 -0
- package/dist/boot-env.d.ts.map +1 -0
- package/dist/boot-env.js +12 -0
- package/dist/boot-env.js.map +1 -0
- package/dist/engine/__tests__/search-core.test.d.ts +2 -0
- package/dist/engine/__tests__/search-core.test.d.ts.map +1 -0
- package/dist/engine/__tests__/search-core.test.js +139 -0
- package/dist/engine/__tests__/search-core.test.js.map +1 -0
- package/dist/engine/ai-provider.d.ts +45 -0
- package/dist/engine/ai-provider.d.ts.map +1 -0
- package/dist/engine/ai-provider.js +5 -0
- package/dist/engine/ai-provider.js.map +1 -0
- package/dist/engine/embeddings.d.ts +51 -0
- package/dist/engine/embeddings.d.ts.map +1 -0
- package/dist/engine/embeddings.js +89 -0
- package/dist/engine/embeddings.js.map +1 -0
- package/dist/engine/providers/__tests__/failure-policy.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/failure-policy.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/failure-policy.test.js +134 -0
- package/dist/engine/providers/__tests__/failure-policy.test.js.map +1 -0
- package/dist/engine/providers/__tests__/model-caps.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/model-caps.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/model-caps.test.js +38 -0
- package/dist/engine/providers/__tests__/model-caps.test.js.map +1 -0
- package/dist/engine/providers/__tests__/openai-structured.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/openai-structured.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/openai-structured.test.js +73 -0
- package/dist/engine/providers/__tests__/openai-structured.test.js.map +1 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.d.ts +2 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.d.ts.map +1 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.js +108 -0
- package/dist/engine/providers/__tests__/usage-ledger.test.js.map +1 -0
- package/dist/engine/providers/anthropic.d.ts +17 -0
- package/dist/engine/providers/anthropic.d.ts.map +1 -0
- package/dist/engine/providers/anthropic.js +125 -0
- package/dist/engine/providers/anthropic.js.map +1 -0
- package/dist/engine/providers/failure-policy.d.ts +56 -0
- package/dist/engine/providers/failure-policy.d.ts.map +1 -0
- package/dist/engine/providers/failure-policy.js +120 -0
- package/dist/engine/providers/failure-policy.js.map +1 -0
- package/dist/engine/providers/gemini.d.ts +22 -0
- package/dist/engine/providers/gemini.d.ts.map +1 -0
- package/dist/engine/providers/gemini.js +180 -0
- package/dist/engine/providers/gemini.js.map +1 -0
- package/dist/engine/providers/index.d.ts +8 -0
- package/dist/engine/providers/index.d.ts.map +1 -0
- package/dist/engine/providers/index.js +67 -0
- package/dist/engine/providers/index.js.map +1 -0
- package/dist/engine/providers/local.d.ts +12 -0
- package/dist/engine/providers/local.d.ts.map +1 -0
- package/dist/engine/providers/local.js +46 -0
- package/dist/engine/providers/local.js.map +1 -0
- package/dist/engine/providers/model-caps.d.ts +6 -0
- package/dist/engine/providers/model-caps.d.ts.map +1 -0
- package/dist/engine/providers/model-caps.js +49 -0
- package/dist/engine/providers/model-caps.js.map +1 -0
- package/dist/engine/providers/openai.d.ts +30 -0
- package/dist/engine/providers/openai.d.ts.map +1 -0
- package/dist/engine/providers/openai.js +309 -0
- package/dist/engine/providers/openai.js.map +1 -0
- package/dist/engine/providers/usage-ledger.d.ts +69 -0
- package/dist/engine/providers/usage-ledger.d.ts.map +1 -0
- package/dist/engine/providers/usage-ledger.js +209 -0
- package/dist/engine/providers/usage-ledger.js.map +1 -0
- package/dist/engine/search-core.d.ts +40 -0
- package/dist/engine/search-core.d.ts.map +1 -0
- package/dist/engine/search-core.js +109 -0
- package/dist/engine/search-core.js.map +1 -0
- package/dist/engine/vector-math.d.ts +5 -0
- package/dist/engine/vector-math.d.ts.map +1 -0
- package/dist/engine/vector-math.js +25 -0
- package/dist/engine/vector-math.js.map +1 -0
- package/dist/home-env.d.ts +26 -0
- package/dist/home-env.d.ts.map +1 -0
- package/dist/home-env.js +87 -0
- package/dist/home-env.js.map +1 -0
- package/dist/mcp-server.d.ts +13 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +79 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/middleware/auth.d.ts +23 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +104 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/auto-recall.d.ts +7 -0
- package/dist/middleware/auto-recall.d.ts.map +1 -0
- package/dist/middleware/auto-recall.js +257 -0
- package/dist/middleware/auto-recall.js.map +1 -0
- package/dist/middleware/auto-reflect.d.ts +4 -0
- package/dist/middleware/auto-reflect.d.ts.map +1 -0
- package/dist/middleware/auto-reflect.js +5 -0
- package/dist/middleware/auto-reflect.js.map +1 -0
- package/dist/middleware/reflect/apply-flag-verdict.d.ts +27 -0
- package/dist/middleware/reflect/apply-flag-verdict.d.ts.map +1 -0
- package/dist/middleware/reflect/apply-flag-verdict.js +57 -0
- package/dist/middleware/reflect/apply-flag-verdict.js.map +1 -0
- package/dist/middleware/reflect/arc-entity-resolve.d.ts +29 -0
- package/dist/middleware/reflect/arc-entity-resolve.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-entity-resolve.js +356 -0
- package/dist/middleware/reflect/arc-entity-resolve.js.map +1 -0
- package/dist/middleware/reflect/arc-inactivity-timer.d.ts +47 -0
- package/dist/middleware/reflect/arc-inactivity-timer.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-inactivity-timer.js +175 -0
- package/dist/middleware/reflect/arc-inactivity-timer.js.map +1 -0
- package/dist/middleware/reflect/arc-pipeline.d.ts +33 -0
- package/dist/middleware/reflect/arc-pipeline.d.ts.map +1 -0
- package/dist/middleware/reflect/arc-pipeline.js +498 -0
- package/dist/middleware/reflect/arc-pipeline.js.map +1 -0
- package/dist/middleware/reflect/comprehend-pergroup.d.ts +100 -0
- package/dist/middleware/reflect/comprehend-pergroup.d.ts.map +1 -0
- package/dist/middleware/reflect/comprehend-pergroup.js +610 -0
- package/dist/middleware/reflect/comprehend-pergroup.js.map +1 -0
- package/dist/middleware/reflect/comprehend.d.ts +237 -0
- package/dist/middleware/reflect/comprehend.d.ts.map +1 -0
- package/dist/middleware/reflect/comprehend.js +706 -0
- package/dist/middleware/reflect/comprehend.js.map +1 -0
- package/dist/middleware/reflect/config.d.ts +34 -0
- package/dist/middleware/reflect/config.d.ts.map +1 -0
- package/dist/middleware/reflect/config.js +131 -0
- package/dist/middleware/reflect/config.js.map +1 -0
- package/dist/middleware/reflect/context.d.ts +138 -0
- package/dist/middleware/reflect/context.d.ts.map +1 -0
- package/dist/middleware/reflect/context.js +619 -0
- package/dist/middleware/reflect/context.js.map +1 -0
- package/dist/middleware/reflect/cost-breakdown.d.ts +69 -0
- package/dist/middleware/reflect/cost-breakdown.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-breakdown.js +63 -0
- package/dist/middleware/reflect/cost-breakdown.js.map +1 -0
- package/dist/middleware/reflect/cost-guard.d.ts +102 -0
- package/dist/middleware/reflect/cost-guard.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-guard.js +243 -0
- package/dist/middleware/reflect/cost-guard.js.map +1 -0
- package/dist/middleware/reflect/cost-pricing.d.ts +54 -0
- package/dist/middleware/reflect/cost-pricing.d.ts.map +1 -0
- package/dist/middleware/reflect/cost-pricing.js +148 -0
- package/dist/middleware/reflect/cost-pricing.js.map +1 -0
- package/dist/middleware/reflect/cross-group-link.d.ts +61 -0
- package/dist/middleware/reflect/cross-group-link.d.ts.map +1 -0
- package/dist/middleware/reflect/cross-group-link.js +212 -0
- package/dist/middleware/reflect/cross-group-link.js.map +1 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.d.ts +70 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.d.ts.map +1 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.js +0 -0
- package/dist/middleware/reflect/dedup-by-source-and-value.js.map +1 -0
- package/dist/middleware/reflect/describe-roots.d.ts +58 -0
- package/dist/middleware/reflect/describe-roots.d.ts.map +1 -0
- package/dist/middleware/reflect/describe-roots.js +266 -0
- package/dist/middleware/reflect/describe-roots.js.map +1 -0
- package/dist/middleware/reflect/flag-reviewer-startup.d.ts +16 -0
- package/dist/middleware/reflect/flag-reviewer-startup.d.ts.map +1 -0
- package/dist/middleware/reflect/flag-reviewer-startup.js +107 -0
- package/dist/middleware/reflect/flag-reviewer-startup.js.map +1 -0
- package/dist/middleware/reflect/flag-reviewer.d.ts +69 -0
- package/dist/middleware/reflect/flag-reviewer.d.ts.map +1 -0
- package/dist/middleware/reflect/flag-reviewer.js +520 -0
- package/dist/middleware/reflect/flag-reviewer.js.map +1 -0
- package/dist/middleware/reflect/inline-dedup.d.ts +26 -0
- package/dist/middleware/reflect/inline-dedup.d.ts.map +1 -0
- package/dist/middleware/reflect/inline-dedup.js +131 -0
- package/dist/middleware/reflect/inline-dedup.js.map +1 -0
- package/dist/middleware/reflect/justify-decisions.d.ts +37 -0
- package/dist/middleware/reflect/justify-decisions.d.ts.map +1 -0
- package/dist/middleware/reflect/justify-decisions.js +159 -0
- package/dist/middleware/reflect/justify-decisions.js.map +1 -0
- package/dist/middleware/reflect/nl-accept.d.ts +35 -0
- package/dist/middleware/reflect/nl-accept.d.ts.map +1 -0
- package/dist/middleware/reflect/nl-accept.js +167 -0
- package/dist/middleware/reflect/nl-accept.js.map +1 -0
- package/dist/middleware/reflect/pass0.d.ts +20 -0
- package/dist/middleware/reflect/pass0.d.ts.map +1 -0
- package/dist/middleware/reflect/pass0.js +423 -0
- package/dist/middleware/reflect/pass0.js.map +1 -0
- package/dist/middleware/reflect/pass1.d.ts +17 -0
- package/dist/middleware/reflect/pass1.d.ts.map +1 -0
- package/dist/middleware/reflect/pass1.js +241 -0
- package/dist/middleware/reflect/pass1.js.map +1 -0
- package/dist/middleware/reflect/pass2-quarantine.d.ts +129 -0
- package/dist/middleware/reflect/pass2-quarantine.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-quarantine.js +272 -0
- package/dist/middleware/reflect/pass2-quarantine.js.map +1 -0
- package/dist/middleware/reflect/pass2-seams.d.ts +205 -0
- package/dist/middleware/reflect/pass2-seams.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-seams.js +279 -0
- package/dist/middleware/reflect/pass2-seams.js.map +1 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.d.ts +37 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.js +531 -0
- package/dist/middleware/reflect/pass2-split-orchestrator.js.map +1 -0
- package/dist/middleware/reflect/pass2.d.ts +17 -0
- package/dist/middleware/reflect/pass2.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2.js +324 -0
- package/dist/middleware/reflect/pass2.js.map +1 -0
- package/dist/middleware/reflect/pass2a.d.ts +141 -0
- package/dist/middleware/reflect/pass2a.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2a.js +404 -0
- package/dist/middleware/reflect/pass2a.js.map +1 -0
- package/dist/middleware/reflect/pass2b.d.ts +108 -0
- package/dist/middleware/reflect/pass2b.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2b.js +480 -0
- package/dist/middleware/reflect/pass2b.js.map +1 -0
- package/dist/middleware/reflect/pass2c.d.ts +113 -0
- package/dist/middleware/reflect/pass2c.d.ts.map +1 -0
- package/dist/middleware/reflect/pass2c.js +360 -0
- package/dist/middleware/reflect/pass2c.js.map +1 -0
- package/dist/middleware/reflect/pass3-batch.d.ts +62 -0
- package/dist/middleware/reflect/pass3-batch.d.ts.map +1 -0
- package/dist/middleware/reflect/pass3-batch.js +139 -0
- package/dist/middleware/reflect/pass3-batch.js.map +1 -0
- package/dist/middleware/reflect/pass3.d.ts +23 -0
- package/dist/middleware/reflect/pass3.d.ts.map +1 -0
- package/dist/middleware/reflect/pass3.js +371 -0
- package/dist/middleware/reflect/pass3.js.map +1 -0
- package/dist/middleware/reflect/pass4-slice.d.ts +25 -0
- package/dist/middleware/reflect/pass4-slice.d.ts.map +1 -0
- package/dist/middleware/reflect/pass4-slice.js +315 -0
- package/dist/middleware/reflect/pass4-slice.js.map +1 -0
- package/dist/middleware/reflect/pass4.d.ts +30 -0
- package/dist/middleware/reflect/pass4.d.ts.map +1 -0
- package/dist/middleware/reflect/pass4.js +193 -0
- package/dist/middleware/reflect/pass4.js.map +1 -0
- package/dist/middleware/reflect/pass5.d.ts +22 -0
- package/dist/middleware/reflect/pass5.d.ts.map +1 -0
- package/dist/middleware/reflect/pass5.js +178 -0
- package/dist/middleware/reflect/pass5.js.map +1 -0
- package/dist/middleware/reflect/pass_judge.d.ts +44 -0
- package/dist/middleware/reflect/pass_judge.d.ts.map +1 -0
- package/dist/middleware/reflect/pass_judge.js +263 -0
- package/dist/middleware/reflect/pass_judge.js.map +1 -0
- package/dist/middleware/reflect/pipeline-flags.d.ts +140 -0
- package/dist/middleware/reflect/pipeline-flags.d.ts.map +1 -0
- package/dist/middleware/reflect/pipeline-flags.js +314 -0
- package/dist/middleware/reflect/pipeline-flags.js.map +1 -0
- package/dist/middleware/reflect/pipeline.d.ts +237 -0
- package/dist/middleware/reflect/pipeline.d.ts.map +1 -0
- package/dist/middleware/reflect/pipeline.js +3114 -0
- package/dist/middleware/reflect/pipeline.js.map +1 -0
- package/dist/middleware/reflect/promptOverride.d.ts +14 -0
- package/dist/middleware/reflect/promptOverride.d.ts.map +1 -0
- package/dist/middleware/reflect/promptOverride.js +28 -0
- package/dist/middleware/reflect/promptOverride.js.map +1 -0
- package/dist/middleware/reflect/provenance-check.d.ts +48 -0
- package/dist/middleware/reflect/provenance-check.d.ts.map +1 -0
- package/dist/middleware/reflect/provenance-check.js +180 -0
- package/dist/middleware/reflect/provenance-check.js.map +1 -0
- package/dist/middleware/reflect/provenance-reviewer.d.ts +52 -0
- package/dist/middleware/reflect/provenance-reviewer.d.ts.map +1 -0
- package/dist/middleware/reflect/provenance-reviewer.js +253 -0
- package/dist/middleware/reflect/provenance-reviewer.js.map +1 -0
- package/dist/middleware/reflect/prune-collapsed-types.d.ts +11 -0
- package/dist/middleware/reflect/prune-collapsed-types.d.ts.map +1 -0
- package/dist/middleware/reflect/prune-collapsed-types.js +32 -0
- package/dist/middleware/reflect/prune-collapsed-types.js.map +1 -0
- package/dist/middleware/reflect/recognize-root.d.ts +75 -0
- package/dist/middleware/reflect/recognize-root.d.ts.map +1 -0
- package/dist/middleware/reflect/recognize-root.js +204 -0
- package/dist/middleware/reflect/recognize-root.js.map +1 -0
- package/dist/middleware/reflect/render-agent-flag.d.ts +25 -0
- package/dist/middleware/reflect/render-agent-flag.d.ts.map +1 -0
- package/dist/middleware/reflect/render-agent-flag.js +39 -0
- package/dist/middleware/reflect/render-agent-flag.js.map +1 -0
- package/dist/middleware/reflect/retrieve-graph-slice.d.ts +54 -0
- package/dist/middleware/reflect/retrieve-graph-slice.d.ts.map +1 -0
- package/dist/middleware/reflect/retrieve-graph-slice.js +173 -0
- package/dist/middleware/reflect/retrieve-graph-slice.js.map +1 -0
- package/dist/middleware/reflect/root-relatedness.d.ts +31 -0
- package/dist/middleware/reflect/root-relatedness.d.ts.map +1 -0
- package/dist/middleware/reflect/root-relatedness.js +92 -0
- package/dist/middleware/reflect/root-relatedness.js.map +1 -0
- package/dist/middleware/reflect/schema-heal.d.ts +22 -0
- package/dist/middleware/reflect/schema-heal.d.ts.map +1 -0
- package/dist/middleware/reflect/schema-heal.js +119 -0
- package/dist/middleware/reflect/schema-heal.js.map +1 -0
- package/dist/middleware/reflect/schema-validator.d.ts +85 -0
- package/dist/middleware/reflect/schema-validator.d.ts.map +1 -0
- package/dist/middleware/reflect/schema-validator.js +196 -0
- package/dist/middleware/reflect/schema-validator.js.map +1 -0
- package/dist/middleware/reflect/stage-audit-graph.d.ts +115 -0
- package/dist/middleware/reflect/stage-audit-graph.d.ts.map +1 -0
- package/dist/middleware/reflect/stage-audit-graph.js +563 -0
- package/dist/middleware/reflect/stage-audit-graph.js.map +1 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.d.ts +87 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.d.ts.map +1 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.js +256 -0
- package/dist/middleware/reflect/stage-d-resolve-graph.js.map +1 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.d.ts +15 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.d.ts.map +1 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.js +91 -0
- package/dist/middleware/reflect/synthesizeFromSceneCard.js.map +1 -0
- package/dist/middleware/reflect/types.d.ts +261 -0
- package/dist/middleware/reflect/types.d.ts.map +1 -0
- package/dist/middleware/reflect/types.js +3 -0
- package/dist/middleware/reflect/types.js.map +1 -0
- package/dist/middleware/reflect/v2-integrate.d.ts +120 -0
- package/dist/middleware/reflect/v2-integrate.d.ts.map +1 -0
- package/dist/middleware/reflect/v2-integrate.js +388 -0
- package/dist/middleware/reflect/v2-integrate.js.map +1 -0
- package/dist/middleware/reflect/v2-judge.d.ts +44 -0
- package/dist/middleware/reflect/v2-judge.d.ts.map +1 -0
- package/dist/middleware/reflect/v2-judge.js +191 -0
- package/dist/middleware/reflect/v2-judge.js.map +1 -0
- package/dist/relation-sets.d.ts +2 -0
- package/dist/relation-sets.d.ts.map +1 -0
- package/dist/relation-sets.js +35 -0
- package/dist/relation-sets.js.map +1 -0
- package/dist/routes/__tests__/flags.test.d.ts +2 -0
- package/dist/routes/__tests__/flags.test.d.ts.map +1 -0
- package/dist/routes/__tests__/flags.test.js +257 -0
- package/dist/routes/__tests__/flags.test.js.map +1 -0
- package/dist/routes/__tests__/models-catalog.test.d.ts +2 -0
- package/dist/routes/__tests__/models-catalog.test.d.ts.map +1 -0
- package/dist/routes/__tests__/models-catalog.test.js +130 -0
- package/dist/routes/__tests__/models-catalog.test.js.map +1 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.d.ts +2 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.d.ts.map +1 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.js +38 -0
- package/dist/routes/__tests__/reflect-pause-drain.test.js.map +1 -0
- package/dist/routes/__tests__/spend-pause-drain.test.d.ts +2 -0
- package/dist/routes/__tests__/spend-pause-drain.test.d.ts.map +1 -0
- package/dist/routes/__tests__/spend-pause-drain.test.js +38 -0
- package/dist/routes/__tests__/spend-pause-drain.test.js.map +1 -0
- package/dist/routes/admin.d.ts +49 -0
- package/dist/routes/admin.d.ts.map +1 -0
- package/dist/routes/admin.js +471 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/blocks.d.ts +4 -0
- package/dist/routes/blocks.d.ts.map +1 -0
- package/dist/routes/blocks.js +893 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/chat-proxy.d.ts +5 -0
- package/dist/routes/chat-proxy.d.ts.map +1 -0
- package/dist/routes/chat-proxy.js +225 -0
- package/dist/routes/chat-proxy.js.map +1 -0
- package/dist/routes/conversations.d.ts +4 -0
- package/dist/routes/conversations.d.ts.map +1 -0
- package/dist/routes/conversations.js +139 -0
- package/dist/routes/conversations.js.map +1 -0
- package/dist/routes/flags.d.ts +4 -0
- package/dist/routes/flags.d.ts.map +1 -0
- package/dist/routes/flags.js +151 -0
- package/dist/routes/flags.js.map +1 -0
- package/dist/routes/inject.d.ts +4 -0
- package/dist/routes/inject.d.ts.map +1 -0
- package/dist/routes/inject.js +183 -0
- package/dist/routes/inject.js.map +1 -0
- package/dist/routes/mcp-http.d.ts +5 -0
- package/dist/routes/mcp-http.d.ts.map +1 -0
- package/dist/routes/mcp-http.js +94 -0
- package/dist/routes/mcp-http.js.map +1 -0
- package/dist/routes/quarantine.d.ts +4 -0
- package/dist/routes/quarantine.d.ts.map +1 -0
- package/dist/routes/quarantine.js +66 -0
- package/dist/routes/quarantine.js.map +1 -0
- package/dist/routes/recall.d.ts +5 -0
- package/dist/routes/recall.d.ts.map +1 -0
- package/dist/routes/recall.js +573 -0
- package/dist/routes/recall.js.map +1 -0
- package/dist/routes/reflect.d.ts +5 -0
- package/dist/routes/reflect.d.ts.map +1 -0
- package/dist/routes/reflect.js +231 -0
- package/dist/routes/reflect.js.map +1 -0
- package/dist/routes/session.d.ts +4 -0
- package/dist/routes/session.d.ts.map +1 -0
- package/dist/routes/session.js +418 -0
- package/dist/routes/session.js.map +1 -0
- package/dist/routes/state.d.ts +116 -0
- package/dist/routes/state.d.ts.map +1 -0
- package/dist/routes/state.js +621 -0
- package/dist/routes/state.js.map +1 -0
- package/dist/routes/usage.d.ts +3 -0
- package/dist/routes/usage.d.ts.map +1 -0
- package/dist/routes/usage.js +141 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/workspace.d.ts +5 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +435 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +298 -0
- package/dist/server.js.map +1 -0
- package/dist/store/__tests__/backup.test.d.ts +2 -0
- package/dist/store/__tests__/backup.test.d.ts.map +1 -0
- package/dist/store/__tests__/backup.test.js +53 -0
- package/dist/store/__tests__/backup.test.js.map +1 -0
- package/dist/store/__tests__/quality.test.d.ts +2 -0
- package/dist/store/__tests__/quality.test.d.ts.map +1 -0
- package/dist/store/__tests__/quality.test.js +75 -0
- package/dist/store/__tests__/quality.test.js.map +1 -0
- package/dist/store/backup.d.ts +14 -0
- package/dist/store/backup.d.ts.map +1 -0
- package/dist/store/backup.js +95 -0
- package/dist/store/backup.js.map +1 -0
- package/dist/store/database.d.ts +407 -0
- package/dist/store/database.d.ts.map +1 -0
- package/dist/store/database.js +2004 -0
- package/dist/store/database.js.map +1 -0
- package/dist/store/quality.d.ts +25 -0
- package/dist/store/quality.d.ts.map +1 -0
- package/dist/store/quality.js +48 -0
- package/dist/store/quality.js.map +1 -0
- package/dist/tools/__tests__/assemble-block-chains.test.d.ts +2 -0
- package/dist/tools/__tests__/assemble-block-chains.test.d.ts.map +1 -0
- package/dist/tools/__tests__/assemble-block-chains.test.js +118 -0
- package/dist/tools/__tests__/assemble-block-chains.test.js.map +1 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.d.ts +2 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.d.ts.map +1 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.js +68 -0
- package/dist/tools/__tests__/filter-roots-by-concepts.test.js.map +1 -0
- package/dist/tools/__tests__/flag-surface.test.d.ts +2 -0
- package/dist/tools/__tests__/flag-surface.test.d.ts.map +1 -0
- package/dist/tools/__tests__/flag-surface.test.js +130 -0
- package/dist/tools/__tests__/flag-surface.test.js.map +1 -0
- package/dist/tools/core.d.ts +5 -0
- package/dist/tools/core.d.ts.map +1 -0
- package/dist/tools/core.js +962 -0
- package/dist/tools/core.js.map +1 -0
- package/dist/tools/derive.d.ts +5 -0
- package/dist/tools/derive.d.ts.map +1 -0
- package/dist/tools/derive.js +182 -0
- package/dist/tools/derive.js.map +1 -0
- package/dist/tools/flag-surface.d.ts +26 -0
- package/dist/tools/flag-surface.d.ts.map +1 -0
- package/dist/tools/flag-surface.js +59 -0
- package/dist/tools/flag-surface.js.map +1 -0
- package/dist/tools/helpers.d.ts +99 -0
- package/dist/tools/helpers.d.ts.map +1 -0
- package/dist/tools/helpers.js +243 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/projects.d.ts +5 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +175 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/system.d.ts +5 -0
- package/dist/tools/system.d.ts.map +1 -0
- package/dist/tools/system.js +1361 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/tasks.d.ts +5 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +289 -0
- package/dist/tools/tasks.js.map +1 -0
- package/package.json +69 -0
- package/scripts/nodedex-entry.mjs +396 -0
- package/tui-dist/App.js +185 -0
- package/tui-dist/api.js +197 -0
- package/tui-dist/cli.js +53 -0
- package/tui-dist/components.js +63 -0
- package/tui-dist/config.js +242 -0
- package/tui-dist/connect-snippets.js +98 -0
- package/tui-dist/feed.js +51 -0
- package/tui-dist/health.js +465 -0
- package/tui-dist/hooks.js +23 -0
- package/tui-dist/memory.js +220 -0
- package/tui-dist/onboarding.js +498 -0
- package/tui-dist/review.js +193 -0
- package/tui-dist/servers.js +556 -0
- package/tui-dist/smoke.js +15 -0
- package/tui-dist/theme.js +106 -0
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { blockEmbeddingText } from "../engine/embeddings.js";
|
|
3
|
+
import { ok, err, cosineSim, assembleBlockChains, filterRootsByConcepts } from "./helpers.js";
|
|
4
|
+
import { searchBlocks, rootContextFor, allWeak, WEAK_NOTE } from "../engine/search-core.js";
|
|
5
|
+
// ─── Keyword concept extractor ───────────────────────────────────
|
|
6
|
+
// Extracts meaningful tokens from text as placeholder concepts.
|
|
7
|
+
// Not abstract (that's the agent's job) but better than nothing.
|
|
8
|
+
const CONCEPT_STOPWORDS = new Set([
|
|
9
|
+
"the", "is", "a", "an", "to", "of", "in", "for", "on", "with", "and", "or", "but",
|
|
10
|
+
"it", "this", "that", "how", "what", "why", "can", "do", "be", "are", "was", "were",
|
|
11
|
+
"will", "i", "my", "we", "our", "use", "used", "using", "new", "all", "one", "two",
|
|
12
|
+
"via", "way", "when", "where", "which", "who", "would", "should", "could", "may",
|
|
13
|
+
"might", "must", "let", "get", "has", "have", "had", "been", "its", "their", "they",
|
|
14
|
+
"them", "from", "than", "then", "there", "these", "those", "into", "out", "over",
|
|
15
|
+
"under", "through", "about", "without", "approach", "method", "system", "based",
|
|
16
|
+
"make", "allows", "enables", "provides", "ensure", "ensures", "called", "known",
|
|
17
|
+
"defined", "given", "contains", "requires", "return", "returns", "define",
|
|
18
|
+
]);
|
|
19
|
+
function extractKeywordConcepts(texts) {
|
|
20
|
+
const words = texts.join(" ")
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]/g, " ")
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.filter((w) => w.length > 3 && !CONCEPT_STOPWORDS.has(w));
|
|
25
|
+
return [...new Set(words)].slice(0, 6);
|
|
26
|
+
}
|
|
27
|
+
export function registerCoreTools(server, db, embeddings) {
|
|
28
|
+
// ─── Tool: workspace_remember ────────────────────────────────────
|
|
29
|
+
server.tool("workspace_remember", `EDGE / escape-hatch — you rarely call this. A background pipeline already extracts facts, decisions, dead-ends, constraints, insights, etc. from your conversation automatically. Use workspace_remember ONLY to: fix a graph error, deliberately seed a block, or coordinate in a multi-agent setup. In normal work just DO the work — the pipeline captures it.
|
|
30
|
+
|
|
31
|
+
FIELDS:
|
|
32
|
+
- essence: One-line description — what this IS (REQUIRED)
|
|
33
|
+
- is_a: Parent category (e.g. 'pricing_model', 'api_provider', 'debugging_technique')
|
|
34
|
+
- unique: Key-value pairs of what makes this distinct
|
|
35
|
+
- has: Properties, components, steps (for process blocks: put the procedure steps here)
|
|
36
|
+
- concepts: IMPORTANT — abstract concept tags that enable cross-domain retrieval.
|
|
37
|
+
Tag with domain-agnostic patterns, not just topic words.
|
|
38
|
+
Example: a debugging process → ['systematic_elimination', 'hypothesis_testing', 'isolation']
|
|
39
|
+
Good concepts let this block surface when future problems share the PATTERN, not just the topic.
|
|
40
|
+
- relations: Links to other blocks (e.g. triggered_by, based_on, part_of)
|
|
41
|
+
|
|
42
|
+
COMMON BLOCK TYPES (epistemic — your relationship to the knowledge; full schemas → docs/reference/block-types.md):
|
|
43
|
+
- fact → an observation or measurement that changed understanding
|
|
44
|
+
- decision → a choice made and why (binding — don't re-open unless asked)
|
|
45
|
+
- dead_end → an approach tried and ABANDONED, and why (this is what Rule 1 checks against — protects future agents from repeating it)
|
|
46
|
+
- constraint → an external limit that cannot be overridden
|
|
47
|
+
- insight → a conclusion from combining multiple facts
|
|
48
|
+
- question → a known unknown, still open (workspace_gaps() surfaces these)
|
|
49
|
+
- hypothesis → proposed but not yet verified
|
|
50
|
+
- preference → a standing lean, not a committed choice
|
|
51
|
+
- entity → a named thing (person, system, organization)
|
|
52
|
+
- process → a SKILL / PROCEDURE — put steps in has:{}
|
|
53
|
+
- note → catch-all when nothing sharper fits
|
|
54
|
+
|
|
55
|
+
For skills/procedures: type='process', steps in has:{}, abstract patterns in concepts:[].`, {
|
|
56
|
+
label: z.string().describe("Block label (lowercase, underscore-separated, e.g. 'vapi_pricing')"),
|
|
57
|
+
type: z
|
|
58
|
+
.string()
|
|
59
|
+
.describe("Block type (e.g., 'fact', 'decision', 'note', or a custom type)"),
|
|
60
|
+
essence: z.string().describe("One-line description of this block's core meaning"),
|
|
61
|
+
is_a: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Parent category or what this is a type of (e.g., 'pricing_data', 'voice_api', 'person')"),
|
|
65
|
+
unique: z
|
|
66
|
+
.record(z.string(), z.string())
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Key-value pairs of what makes this distinct (e.g., { price: '$0.12/min', model: 'flat_rate' })"),
|
|
69
|
+
has: z
|
|
70
|
+
.record(z.string(), z.union([z.string(), z.array(z.string())]))
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Properties, components, features (e.g., { features: ['voice', 'realtime'], limitations: ['no SMS'] })"),
|
|
73
|
+
concepts: z
|
|
74
|
+
.array(z.string())
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Abstract tags or ideas (e.g., ['voice_api', 'pricing', 'saas'])"),
|
|
77
|
+
source: z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Where this information came from"),
|
|
81
|
+
ttl: z
|
|
82
|
+
.enum(["session", "1hr", "24hr", "1week", "project", "permanent"])
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Time-to-live. Default: permanent"),
|
|
85
|
+
priority: z
|
|
86
|
+
.enum(["high", "medium", "low"])
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Importance signal — high: check before acting, medium: notable, low: background. Shown in tree view."),
|
|
89
|
+
flow_role: z
|
|
90
|
+
.enum(["problem", "cause", "mechanism", "outcome", "solution", "trigger"])
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Semantic position in a reasoning chain — problem→cause→mechanism→outcome. Enriches chain view."),
|
|
93
|
+
is_sensitive: z
|
|
94
|
+
.boolean()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("If true, essence and content will be encrypted in the database using AES-256. (Requires WORKSPACE_ENCRYPTION_KEY env var)"),
|
|
97
|
+
relations: z
|
|
98
|
+
.array(z.object({
|
|
99
|
+
type: z.string().describe("Relation type (e.g., 'uses', 'related_to', 'part_of')"),
|
|
100
|
+
target_id: z.string().describe("Target block ID or label"),
|
|
101
|
+
}))
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Relations to existing blocks"),
|
|
104
|
+
agent_id: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Agent name or ID saving this block (e.g., 'planner', 'coder', 'claude'). Used for attribution."),
|
|
108
|
+
project_id: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Project block ID or label to assign this block to"),
|
|
112
|
+
force: z
|
|
113
|
+
.boolean()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("If true, save even when a semantic conflict is detected. Default: false"),
|
|
116
|
+
save_context: z
|
|
117
|
+
.object({
|
|
118
|
+
trigger: z.string().optional().describe("Why you are saving this block right now — the insight or question that prompted it"),
|
|
119
|
+
triggered_by: z.array(z.string()).optional().describe("Block IDs or labels that you were reading when this insight occurred. Creates prompted_by relations."),
|
|
120
|
+
problem_being_solved: z.string().optional().describe("What problem or question this block addresses"),
|
|
121
|
+
block_role: z.enum(["foundation", "derived", "synthesis"]).optional().describe("foundation=others will build from this, derived=builds on existing blocks, synthesis=bridges multiple clusters"),
|
|
122
|
+
})
|
|
123
|
+
.optional()
|
|
124
|
+
.describe("Why this block is being saved right now. Preserves cognitive context that the block content alone cannot capture."),
|
|
125
|
+
}, async (params) => {
|
|
126
|
+
try {
|
|
127
|
+
// ── Label duplicate check ──────────────────────────────────
|
|
128
|
+
const existing = db.keywordSearch(params.label, 3);
|
|
129
|
+
const duplicate = existing.find((b) => b.label.toLowerCase() === params.label.toLowerCase());
|
|
130
|
+
if (duplicate) {
|
|
131
|
+
// Multi-agent conflict: different agent trying to save same label
|
|
132
|
+
if (duplicate.created_by && params.agent_id && duplicate.created_by !== params.agent_id) {
|
|
133
|
+
return err("AGENT_CONFLICT", `Block '${duplicate.label}' was already saved by agent '${duplicate.created_by}'. ` +
|
|
134
|
+
`Use workspace_update to modify it, or workspace_challenge if you believe it is incorrect.`, { existing_block: { id: duplicate.id, label: duplicate.label, essence: duplicate.essence, created_by: duplicate.created_by } });
|
|
135
|
+
}
|
|
136
|
+
return err("DUPLICATE_DETECTED", `Similar block exists: '${duplicate.label}' (id: ${duplicate.id}). Use workspace_update instead.`, { existing_block: { id: duplicate.id, label: duplicate.label, essence: duplicate.essence } });
|
|
137
|
+
}
|
|
138
|
+
// ── Near-duplicate detection ───────────────────────────────
|
|
139
|
+
// High similarity (>0.88) = probably the same block with different wording.
|
|
140
|
+
// Warn the agent rather than silently creating redundant knowledge.
|
|
141
|
+
if (!params.force && embeddings.isAvailable()) {
|
|
142
|
+
const vec = await embeddings.embed(params.essence);
|
|
143
|
+
if (vec) {
|
|
144
|
+
const nearMatches = db.semanticSearch(vec, 3, undefined, 0.88);
|
|
145
|
+
for (const match of nearMatches) {
|
|
146
|
+
if (match.label.toLowerCase() === params.label.toLowerCase())
|
|
147
|
+
continue;
|
|
148
|
+
// Register this as an open conflict for review
|
|
149
|
+
const vec2 = await embeddings.embed(match.essence);
|
|
150
|
+
const sim = vec && vec2 ? cosineSim(vec, vec2) : 0.9;
|
|
151
|
+
// Save incoming block first so we can register conflict between the two
|
|
152
|
+
// (We'll log a temporary "pending_save" label for tracing)
|
|
153
|
+
try {
|
|
154
|
+
db.createConflict(match.id, match.id, sim);
|
|
155
|
+
}
|
|
156
|
+
catch { /* non-critical */ }
|
|
157
|
+
return err("NEAR_DUPLICATE", `Block '${match.label}' (${match.id}) is very similar to what you're saving (similarity: ${Math.round(sim * 100)}%). ` +
|
|
158
|
+
`This is probably the same knowledge with different wording. ` +
|
|
159
|
+
`Options: (1) workspace_update('${match.id}', ...) to update it, ` +
|
|
160
|
+
`(2) workspace_resolve_conflict to merge, ` +
|
|
161
|
+
`(3) force:true to save anyway.`, {
|
|
162
|
+
similar_block: { id: match.id, label: match.label, essence: match.essence },
|
|
163
|
+
similarity: Math.round(sim * 100) / 100,
|
|
164
|
+
your_essence: params.essence,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── Auto-concepts if none provided ─────────────────────────
|
|
170
|
+
// Extracts keyword-level concepts immediately (zero latency, no API).
|
|
171
|
+
// These are placeholder concepts — the agent should improve them with
|
|
172
|
+
// workspace_update(id, {concepts:[...]}) using more abstract patterns.
|
|
173
|
+
const agentConcepts = params.concepts ?? [];
|
|
174
|
+
const autoConceptsGenerated = agentConcepts.length === 0;
|
|
175
|
+
const finalConcepts = agentConcepts.length > 0
|
|
176
|
+
? agentConcepts
|
|
177
|
+
: extractKeywordConcepts([
|
|
178
|
+
params.label.replace(/_/g, " "),
|
|
179
|
+
params.essence,
|
|
180
|
+
params.is_a ?? "",
|
|
181
|
+
]);
|
|
182
|
+
// Build structured content
|
|
183
|
+
const content = {};
|
|
184
|
+
if (params.is_a)
|
|
185
|
+
content.is_a = params.is_a;
|
|
186
|
+
if (params.unique)
|
|
187
|
+
content.unique = params.unique;
|
|
188
|
+
if (params.has)
|
|
189
|
+
content.has = params.has;
|
|
190
|
+
content.concepts = finalConcepts;
|
|
191
|
+
if (params.save_context)
|
|
192
|
+
content.save_context = params.save_context;
|
|
193
|
+
// Track concept origin so enrichment never overwrites agent-tagged concepts
|
|
194
|
+
content.concepts_source = agentConcepts.length > 0 ? "agent" : "keyword_auto";
|
|
195
|
+
// Embedding — canonical recipe (essence + concepts), Tier 2 cleanup 2026-06-15.
|
|
196
|
+
// Was a contextual "[label | type | project] / related_to: …" header (the old
|
|
197
|
+
// "Change 4"). Dropped so a manually-remembered block embeds with the SAME recipe
|
|
198
|
+
// as a pipeline block — otherwise the cosine drifts when these are compared
|
|
199
|
+
// stored-vs-stored (dedup / Stage-D / Pass-4). Uses finalConcepts (what the block
|
|
200
|
+
// is actually stored with at createBlock below), not the raw params. See
|
|
201
|
+
// blockEmbeddingText for the recipe rationale.
|
|
202
|
+
const embedding = await embeddings.embed(blockEmbeddingText({ essence: params.essence, concepts: finalConcepts }));
|
|
203
|
+
// Create block
|
|
204
|
+
const block = db.createBlock({
|
|
205
|
+
label: params.label,
|
|
206
|
+
type: params.type,
|
|
207
|
+
essence: params.essence,
|
|
208
|
+
content,
|
|
209
|
+
concepts: finalConcepts,
|
|
210
|
+
ttl: params.type === "dead_end" ? "permanent" : (params.ttl || "permanent"),
|
|
211
|
+
source: params.source,
|
|
212
|
+
created_by: params.agent_id,
|
|
213
|
+
embedding: embedding || undefined,
|
|
214
|
+
is_sensitive: params.is_sensitive || false,
|
|
215
|
+
...(params.priority ? { priority: params.priority } : {}),
|
|
216
|
+
...(params.flow_role ? { flow_role: params.flow_role } : {}),
|
|
217
|
+
});
|
|
218
|
+
// Create explicit relations
|
|
219
|
+
const explicitRelTypes = new Set();
|
|
220
|
+
if (params.relations) {
|
|
221
|
+
for (const rel of params.relations) {
|
|
222
|
+
const targetBlock = db.getBlock(rel.target_id);
|
|
223
|
+
if (targetBlock) {
|
|
224
|
+
db.createRelation({
|
|
225
|
+
source_id: block.id,
|
|
226
|
+
target_id: targetBlock.id,
|
|
227
|
+
type: rel.type,
|
|
228
|
+
bidirectional: true,
|
|
229
|
+
});
|
|
230
|
+
explicitRelTypes.add(rel.type);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Create prompted_by relations from save_context.triggered_by
|
|
235
|
+
if (params.save_context?.triggered_by?.length) {
|
|
236
|
+
for (const targetRef of params.save_context.triggered_by) {
|
|
237
|
+
const targetBlock = db.getBlock(targetRef);
|
|
238
|
+
if (targetBlock) {
|
|
239
|
+
db.createRelation({
|
|
240
|
+
source_id: block.id,
|
|
241
|
+
target_id: targetBlock.id,
|
|
242
|
+
type: "prompted_by",
|
|
243
|
+
created_by: params.agent_id || "agent",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// No auto-link: agent must explicitly specify part_of relations.
|
|
249
|
+
// Blind auto-linking to the first project causes hub contamination.
|
|
250
|
+
// ── Quality score — immediate feedback on block depth ────────
|
|
251
|
+
const qc = (() => { try {
|
|
252
|
+
return typeof block.content === "string" ? JSON.parse(block.content) : (block.content || {});
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return {};
|
|
256
|
+
} })();
|
|
257
|
+
const qMissing = [];
|
|
258
|
+
let qScore = 1; // essence always present
|
|
259
|
+
if (qc.is_a)
|
|
260
|
+
qScore++;
|
|
261
|
+
else
|
|
262
|
+
qMissing.push("is_a");
|
|
263
|
+
if (qc.unique && Object.keys(qc.unique).length >= 2)
|
|
264
|
+
qScore++;
|
|
265
|
+
else
|
|
266
|
+
qMissing.push("unique{} (≥2 props)");
|
|
267
|
+
if (finalConcepts.length >= 3)
|
|
268
|
+
qScore++;
|
|
269
|
+
else
|
|
270
|
+
qMissing.push(`concepts (have ${finalConcepts.length}, need ≥3)`);
|
|
271
|
+
// project-root blocks earn this point automatically — children link to them later
|
|
272
|
+
if (params.type === "project") {
|
|
273
|
+
qScore++;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const savedRelCount = db.getRelations(block.id).length;
|
|
277
|
+
if (savedRelCount > 0)
|
|
278
|
+
qScore++;
|
|
279
|
+
else
|
|
280
|
+
qMissing.push("relations");
|
|
281
|
+
}
|
|
282
|
+
// Persist quality score so recall ranking can use it without recomputing
|
|
283
|
+
db.updateBlock(block.id, { quality_score: Math.min(qScore, 5) });
|
|
284
|
+
const result = {
|
|
285
|
+
id: block.id,
|
|
286
|
+
label: block.label,
|
|
287
|
+
type: block.type,
|
|
288
|
+
status: block.status,
|
|
289
|
+
created_by: block.created_by || null,
|
|
290
|
+
concepts: finalConcepts,
|
|
291
|
+
embedding_generated: embedding !== null,
|
|
292
|
+
quality: {
|
|
293
|
+
score: qScore,
|
|
294
|
+
max: 6,
|
|
295
|
+
thin: qScore < 3,
|
|
296
|
+
missing: qMissing,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
if (qScore < 3) {
|
|
300
|
+
result.quality_hint = `Block is thin (${qScore}/6). Missing: ${qMissing.slice(0, 2).join(", ")}. Call workspace_review("${block.id}") for Gemini suggestions, or add fields now.`;
|
|
301
|
+
}
|
|
302
|
+
// ── Tag vocabulary hints ─────────────────────────────────────
|
|
303
|
+
// Surface concept tags that are new to the workspace vocabulary.
|
|
304
|
+
// Helps agents reuse existing tags instead of fragmenting the concept graph.
|
|
305
|
+
if (agentConcepts.length > 0) {
|
|
306
|
+
const allBlocks = db.getAllBlocks();
|
|
307
|
+
const existingTagSet = new Set();
|
|
308
|
+
for (const b of allBlocks) {
|
|
309
|
+
if (b.id === block.id)
|
|
310
|
+
continue; // exclude the block we just saved
|
|
311
|
+
const tags = JSON.parse(b.concepts || "[]");
|
|
312
|
+
for (const t of tags)
|
|
313
|
+
existingTagSet.add(t);
|
|
314
|
+
}
|
|
315
|
+
const newTags = [];
|
|
316
|
+
for (const tag of agentConcepts) {
|
|
317
|
+
if (existingTagSet.has(tag))
|
|
318
|
+
continue; // already in vocabulary
|
|
319
|
+
// Find existing tags that share a common prefix (first 5 chars) or substring
|
|
320
|
+
const similar = [...existingTagSet].filter(t => t !== tag && (t.startsWith(tag.slice(0, 5)) ||
|
|
321
|
+
tag.startsWith(t.slice(0, 5)) ||
|
|
322
|
+
t.includes(tag.slice(0, 6)) ||
|
|
323
|
+
tag.includes(t.slice(0, 6)))).slice(0, 2);
|
|
324
|
+
newTags.push({ tag, similar_existing: similar });
|
|
325
|
+
}
|
|
326
|
+
if (newTags.length > 0) {
|
|
327
|
+
result.tag_hints = newTags.map(t => t.similar_existing.length > 0
|
|
328
|
+
? `'${t.tag}' is new — similar existing: ${t.similar_existing.map(s => `'${s}'`).join(", ")}. Reuse if meaning matches.`
|
|
329
|
+
: `'${t.tag}' is new to the vocabulary.`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Nudge agent to improve auto-extracted concepts with abstract patterns
|
|
333
|
+
if (autoConceptsGenerated) {
|
|
334
|
+
result.concepts_source = "keyword_auto";
|
|
335
|
+
result.concepts_hint =
|
|
336
|
+
`Concepts were auto-extracted from your text. For cross-domain retrieval to work well, ` +
|
|
337
|
+
`update with abstract patterns: workspace_update("${block.id}", { ` +
|
|
338
|
+
`concepts: ["pattern_name", "abstract_principle", ...] })`;
|
|
339
|
+
}
|
|
340
|
+
// ── Coordinates check: flag missing causal chain ──────────────────
|
|
341
|
+
// Every block except projects needs triggered_by — it is the primary coordinate.
|
|
342
|
+
// "What block was I reading when this occurred to me?"
|
|
343
|
+
// If the cause isn't in the graph yet → create it first, then re-save with triggered_by.
|
|
344
|
+
if (block.type !== "project") {
|
|
345
|
+
const hasCausalChain = (params.save_context?.triggered_by?.length ?? 0) > 0 ||
|
|
346
|
+
(params.relations ?? []).some((r) => ["prompted_by", "derived_from", "based_on", "triggered_by"].includes(r.type));
|
|
347
|
+
if (!hasCausalChain) {
|
|
348
|
+
result.missing_coordinates =
|
|
349
|
+
`No triggered_by — this block has no causal chain. ` +
|
|
350
|
+
`Ask: "what block was I reading when this occurred?" ` +
|
|
351
|
+
`Then re-save with save_context.triggered_by: ["<that_block_id>"], or create the cause block first if it isn't in the graph yet.`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// ── Project link check: flag orphan blocks ────────────────────────
|
|
355
|
+
// Every block except projects should have a project_id or a known project prefix.
|
|
356
|
+
if (block.type !== "project" && !block.project_id) {
|
|
357
|
+
const projectLabels = db.getAllBlocks()
|
|
358
|
+
.filter((b) => b.type === "project")
|
|
359
|
+
.map((b) => b.label);
|
|
360
|
+
const hasProjectPrefix = projectLabels.some((pl) => block.label.startsWith(pl + "_"));
|
|
361
|
+
if (!hasProjectPrefix) {
|
|
362
|
+
const suggestions = projectLabels.map((pl) => `${pl}_${block.label}`).join(" | ");
|
|
363
|
+
result.missing_project_link =
|
|
364
|
+
`Label "${block.label}" has no project prefix — ` +
|
|
365
|
+
`this block is invisible in project tree navigation. ` +
|
|
366
|
+
`Rename to: ${suggestions}`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return ok(result);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
return err("CREATE_FAILED", String(error));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
// ─── Tool: workspace_get ─────────────────────────────────────────
|
|
376
|
+
server.tool("workspace_get", `Retrieve a knowledge block at the level of detail you actually need.
|
|
377
|
+
|
|
378
|
+
DETAIL LEVELS (default: "surface" — start here, drill down only if needed):
|
|
379
|
+
- "surface" → id, label, type, essence, concepts[]
|
|
380
|
+
Use when: scanning, checking existence, deciding if worth reading
|
|
381
|
+
- "content" → + is_a, unique{}, has{} (the full knowledge body / procedure steps)
|
|
382
|
+
Use when: you need the actual facts, properties, or skill steps
|
|
383
|
+
- "relations" → surface + outgoing/incoming links + the causal CHAIN(s) this block sits on
|
|
384
|
+
Use when: navigating the graph — surfaces the whole arc (cause→outcome), not the bare block
|
|
385
|
+
- "full" → everything: content + relations + metadata (source, dates, ttl, aliases)
|
|
386
|
+
Use when: auditing, debugging, or you genuinely need all fields
|
|
387
|
+
|
|
388
|
+
Tip: use "surface" first. If the essence tells you what you need, stop there.`, {
|
|
389
|
+
id: z.string().describe("Block ID (blk_xxx) or label"),
|
|
390
|
+
detail: z.enum(["surface", "content", "relations", "full"]).optional()
|
|
391
|
+
.describe("Level of detail to return. Default: 'surface'"),
|
|
392
|
+
}, async (params) => {
|
|
393
|
+
try {
|
|
394
|
+
const block = db.getBlock(params.id);
|
|
395
|
+
if (!block) {
|
|
396
|
+
return err("BLOCK_NOT_FOUND", `No block found with id or label '${params.id}'`, { suggestion: "Try workspace_search to find the block." });
|
|
397
|
+
}
|
|
398
|
+
const detail = params.detail ?? "surface";
|
|
399
|
+
let content = {};
|
|
400
|
+
try {
|
|
401
|
+
content = JSON.parse(block.content);
|
|
402
|
+
}
|
|
403
|
+
catch { /* ignore */ }
|
|
404
|
+
// ── surface (always included) ─────────────────────────────
|
|
405
|
+
const base = {
|
|
406
|
+
id: block.id,
|
|
407
|
+
label: block.label,
|
|
408
|
+
type: block.type,
|
|
409
|
+
status: block.status,
|
|
410
|
+
essence: block.essence,
|
|
411
|
+
concepts: content.concepts || [],
|
|
412
|
+
created_by: block.created_by || null,
|
|
413
|
+
locked: block.locked || false,
|
|
414
|
+
flow_role: block.flow_role || null,
|
|
415
|
+
chain_id: block.chain_id || null,
|
|
416
|
+
};
|
|
417
|
+
// Derivation staleness warning: if any input block was updated after this block was created
|
|
418
|
+
const derivation = content?.derivation;
|
|
419
|
+
const inputIds = Array.isArray(derivation?.input_ids) ? derivation.input_ids : [];
|
|
420
|
+
if (inputIds.length > 0) {
|
|
421
|
+
const staleInputs = inputIds.filter((inputId) => {
|
|
422
|
+
const inputBlock = db.getBlock(inputId);
|
|
423
|
+
return inputBlock && new Date(inputBlock.updated_at).getTime() > new Date(block.created_at).getTime();
|
|
424
|
+
});
|
|
425
|
+
if (staleInputs.length > 0) {
|
|
426
|
+
base.derivation_warning = `${staleInputs.length} source block(s) were updated after this insight was derived. Re-derive with workspace_derive() to refresh.`;
|
|
427
|
+
base.stale_inputs = staleInputs;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (detail === "surface") {
|
|
431
|
+
return ok({ ...base, detail_level: "surface",
|
|
432
|
+
hint: "Call workspace_get(id, 'content') for the full knowledge body, or 'relations' for links + the causal chain(s) this block sits on." });
|
|
433
|
+
}
|
|
434
|
+
// ── content ───────────────────────────────────────────────
|
|
435
|
+
if (detail === "content" || detail === "full") {
|
|
436
|
+
base.is_a = content.is_a || null;
|
|
437
|
+
base.unique = content.unique || {};
|
|
438
|
+
base.has = content.has || {};
|
|
439
|
+
}
|
|
440
|
+
// ── relations ─────────────────────────────────────────────
|
|
441
|
+
if (detail === "relations" || detail === "full") {
|
|
442
|
+
const outgoing = db.getRelations(block.id).filter((r) => r.direction === "outgoing");
|
|
443
|
+
const incoming = db.getAllIncomingRelations(block.id);
|
|
444
|
+
base.outgoing = outgoing.map((r) => ({ type: r.type, to: r.target_label, id: r.target_id }));
|
|
445
|
+
base.incoming = incoming.map((r) => ({ type: r.type, from: r.source_label, id: r.source_id }));
|
|
446
|
+
// Surface the causal arc(s) this block sits on (chains) PLUS every chain
|
|
447
|
+
// reachable by a causal path from them (linked_chains = the connected
|
|
448
|
+
// component, distance-ranked) — the whole linked story, not the bare node
|
|
449
|
+
// and not the whole root. member_of-based (overlap-aware). See assembleBlockChains.
|
|
450
|
+
const { chains, linked_chains } = assembleBlockChains(db, block);
|
|
451
|
+
if (chains.length > 0)
|
|
452
|
+
base.chains = chains;
|
|
453
|
+
if (linked_chains.length > 0)
|
|
454
|
+
base.linked_chains = linked_chains;
|
|
455
|
+
}
|
|
456
|
+
// ── full metadata ─────────────────────────────────────────
|
|
457
|
+
if (detail === "full") {
|
|
458
|
+
base.metadata = {
|
|
459
|
+
source: block.source,
|
|
460
|
+
created_at: block.created_at,
|
|
461
|
+
updated_at: block.updated_at,
|
|
462
|
+
last_accessed: block.last_accessed,
|
|
463
|
+
access_count: block.access_count,
|
|
464
|
+
ttl: block.ttl,
|
|
465
|
+
aliases: JSON.parse(block.aliases || "[]"),
|
|
466
|
+
is_sensitive: block.is_sensitive,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
base.detail_level = detail;
|
|
470
|
+
return ok(base);
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
return err("GET_FAILED", String(error));
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
// ─── Tool: workspace_search ──────────────────────────────────────
|
|
477
|
+
server.tool("workspace_search", `FALLBACK search — use only when you can't construct the label or orient with workspace_filter. Returns ISOLATED blocks (headlines), not the story; after a hit, workspace_get(label, "relations") to anchor and walk the chain.
|
|
478
|
+
Three signals: semantic similarity, keyword match, and concept overlap. Concept matching enables cross-domain results — a query about "flow control" may surface rate-limiting or traffic-shaping blocks sharing abstract concept tags. Each result shows match_types so you know why it surfaced.
|
|
479
|
+
(Prefer: construct the label and workspace_get it directly when you know what you want; workspace_filter(concepts) to orient on a new task.)`, {
|
|
480
|
+
query: z.string().describe("Natural language search query"),
|
|
481
|
+
type: z.string().optional().describe("Filter by block type (e.g. 'process', 'fact')"),
|
|
482
|
+
limit: z.number().optional().describe("Max results. Default: 10"),
|
|
483
|
+
}, async (params) => {
|
|
484
|
+
try {
|
|
485
|
+
// Shared scorer (engine/search-core.ts): three signals, ranked by match
|
|
486
|
+
// quality ONLY — currency comes from the supersedes edge, never a clock.
|
|
487
|
+
const { hits, signals } = await searchBlocks(db, embeddings, {
|
|
488
|
+
query: params.query, type: params.type, limit: params.limit || 10,
|
|
489
|
+
});
|
|
490
|
+
// Currency annotation: superseded blocks stay ACTIVE (the supersedes edge is the
|
|
491
|
+
// currency marker, not status) — so a bare search hit must say what replaced it,
|
|
492
|
+
// or stale would leak as current. Batched single query over the result ids.
|
|
493
|
+
const supersededBy = db.getSupersededByLabels(hits.map(({ block }) => block.id));
|
|
494
|
+
// Root context — the tree's containment attached per hit, so the agent judges
|
|
495
|
+
// "which world is this from?" on one line instead of resolving project_id each.
|
|
496
|
+
const rootCtx = rootContextFor(db, hits.map(({ block }) => block));
|
|
497
|
+
const results = hits.map(({ block, score, matchTypes }) => ({
|
|
498
|
+
id: block.id,
|
|
499
|
+
label: block.label,
|
|
500
|
+
type: block.type,
|
|
501
|
+
essence: block.essence,
|
|
502
|
+
status: block.status,
|
|
503
|
+
score: Math.round(score * 100) / 100,
|
|
504
|
+
match_types: matchTypes,
|
|
505
|
+
is_sensitive: block.is_sensitive,
|
|
506
|
+
locked: block.locked || false,
|
|
507
|
+
...(typeof block.project_id === "string" && rootCtx.has(block.project_id)
|
|
508
|
+
? rootCtx.get(block.project_id)
|
|
509
|
+
: {}),
|
|
510
|
+
...(supersededBy.has(block.id)
|
|
511
|
+
? { superseded_by: supersededBy.get(block.id), note: "SUPERSEDED — read the superseding block for current truth" }
|
|
512
|
+
: {}),
|
|
513
|
+
}));
|
|
514
|
+
return ok({
|
|
515
|
+
query: params.query,
|
|
516
|
+
total_results: results.length,
|
|
517
|
+
results,
|
|
518
|
+
signals_used: signals,
|
|
519
|
+
// Off-graph queries still return nearest-neighbor hits (semantic never
|
|
520
|
+
// comes back empty) — label the shrug so it can't be read as an answer.
|
|
521
|
+
...(allWeak(hits) ? { note: WEAK_NOTE } : {}),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
return err("SEARCH_FAILED", String(error));
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
// ─── Tool: workspace_filter ──────────────────────────────────────
|
|
529
|
+
server.tool("workspace_filter", `COLD-START orientation — find which project ROOT(s) are relevant when you don't know any labels yet.
|
|
530
|
+
|
|
531
|
+
Give the first-principle CONCEPTS of what you're working on; get back ranked root suggestions, each with its one-line description and the specific blocks that matched (your entry points). This is a FILTER over concept tags + the strict label — NOT a fuzzy surface search.
|
|
532
|
+
|
|
533
|
+
ENTER the named things your task is ABOUT (technologies, mechanisms, failure-modes, domain nouns), e.g. ["latency","n+1-query","caching"]. Multiple terms — more is better.
|
|
534
|
+
DON'T enter generic/process words ("fix","issue","system","help") or full sentences.
|
|
535
|
+
|
|
536
|
+
These are SUGGESTIONS, not "the" root: open one with workspace_get(label, "relations") to anchor, then navigate from there (its chain comes back with it).`, {
|
|
537
|
+
concepts: z.array(z.string()).describe("First-principle concept terms of your current task. Multiple, e.g. ['latency','n+1-query']."),
|
|
538
|
+
limit: z.number().optional().describe("Max root suggestions. Default 8."),
|
|
539
|
+
}, async (params) => {
|
|
540
|
+
try {
|
|
541
|
+
const suggestions = filterRootsByConcepts(db, params.concepts, { limit: params.limit });
|
|
542
|
+
return ok({
|
|
543
|
+
concepts: params.concepts,
|
|
544
|
+
total: suggestions.length,
|
|
545
|
+
suggestions,
|
|
546
|
+
hint: suggestions.length === 0
|
|
547
|
+
? "No roots matched. Try broader/abstract terms, or workspace_search for a fuzzy surface search."
|
|
548
|
+
: "Open a suggested root or entry block with workspace_get(label, 'relations') to anchor — its causal chain returns with it.",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
return err("FILTER_FAILED", String(error));
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
// ─── Tool: workspace_tree ────────────────────────────────────────
|
|
556
|
+
server.tool("workspace_tree", `ORIENT (browse) — list the project ROOTS with their one-line descriptions, for when you don't have concepts to filter by yet. LEAN by design: roots + descriptions + block counts only — NOT the deep tree (that would bloat your context). Empty graph → says so plainly. Then drill in: workspace_filter(concepts) for the relevant root, or workspace_get(root-label, "relations").`, {}, async () => {
|
|
557
|
+
try {
|
|
558
|
+
const all = db.getAllBlocks(); // excludes archived by design
|
|
559
|
+
const projects = all.filter((b) => b.type === "project");
|
|
560
|
+
if (projects.length === 0) {
|
|
561
|
+
return ok({
|
|
562
|
+
projects: [],
|
|
563
|
+
hint: "The graph is empty — nothing has been stored yet. As you and the user work, the pipeline creates project roots and fills them in.",
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
// block count per root (cheap signal of how substantial each root is)
|
|
567
|
+
const childCount = new Map();
|
|
568
|
+
for (const b of all) {
|
|
569
|
+
if (b.project_id)
|
|
570
|
+
childCount.set(b.project_id, (childCount.get(b.project_id) ?? 0) + 1);
|
|
571
|
+
}
|
|
572
|
+
const roots = projects
|
|
573
|
+
.map((p) => ({ root: p.label, description: p.essence || null, blocks: childCount.get(p.id) ?? 0 }))
|
|
574
|
+
.sort((a, b) => b.blocks - a.blocks); // most substantial first
|
|
575
|
+
return ok({
|
|
576
|
+
projects: roots,
|
|
577
|
+
hint: "Drill into a root with workspace_filter(concepts) or workspace_get(root-label, \"relations\").",
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
return err("TREE_FAILED", String(error));
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
// ─── Tool: workspace_list ────────────────────────────────────────
|
|
585
|
+
server.tool("workspace_list", `Structured browse/CHECK — list blocks by project + type (+ exact label_prefix / concept). The precise form of the dead-end check: workspace_list(project, type="dead_end") returns EVERY dead-end in a project (zero false positives), and likewise constraints, decisions, etc.
|
|
586
|
+
Results are HEADLINES (label, type, essence) — and most blocks mean little alone, so each carries on_chain (the named chain it sits on). To get the actual story, workspace_get(label, "relations") the one you care about and walk its chain. Use workspace_filter when you have concepts but not a project; use this when you want an exhaustive typed list within a project.`, {
|
|
587
|
+
project: z.string().optional().describe("Scope to a project (root label or id). Pulls the root + sub-projects + its label-prefix namespace."),
|
|
588
|
+
type: z.string().optional().describe("Block type filter, e.g. 'dead_end', 'constraint', 'decision', 'fact'."),
|
|
589
|
+
label_prefix: z.string().optional().describe("Exact label-prefix filter (zero false positives), e.g. 'checkout-incident_dead_end'."),
|
|
590
|
+
concept: z.string().optional().describe("Filter to blocks tagged with this concept."),
|
|
591
|
+
limit: z.number().optional().describe("Max results. Default 50."),
|
|
592
|
+
}, async (params) => {
|
|
593
|
+
try {
|
|
594
|
+
const all = db.getAllBlocks(); // active, non-archived
|
|
595
|
+
let blocks = all;
|
|
596
|
+
if (params.project) {
|
|
597
|
+
const root = db.getBlock(params.project);
|
|
598
|
+
if (root && root.type === "project") {
|
|
599
|
+
const scope = new Set([root.id]);
|
|
600
|
+
const projs = all.filter((b) => b.type === "project");
|
|
601
|
+
let grew = true;
|
|
602
|
+
while (grew) {
|
|
603
|
+
grew = false;
|
|
604
|
+
for (const p of projs)
|
|
605
|
+
if (p.project_id && scope.has(p.project_id) && !scope.has(p.id)) {
|
|
606
|
+
scope.add(p.id);
|
|
607
|
+
grew = true;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const prefix = root.label + "_";
|
|
611
|
+
blocks = blocks.filter((b) => scope.has(b.id) || (b.project_id != null && scope.has(b.project_id)) || b.label.startsWith(prefix));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// FAIL LOUD — a silent [] here would poison a dead-end check.
|
|
615
|
+
const matched = blocks.filter((b) => b.label.startsWith(params.project + "_") || b.label === params.project);
|
|
616
|
+
if (matched.length === 0) {
|
|
617
|
+
return err("PROJECT_NOT_FOUND", `No project matches '${params.project}' (by id or label), and no labels start with '${params.project}_'.`, { known_projects: all.filter((b) => b.type === "project").map((p) => p.label) });
|
|
618
|
+
}
|
|
619
|
+
blocks = matched;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (params.type)
|
|
623
|
+
blocks = blocks.filter((b) => b.type === params.type);
|
|
624
|
+
if (params.label_prefix)
|
|
625
|
+
blocks = blocks.filter((b) => b.label.startsWith(params.label_prefix));
|
|
626
|
+
if (params.concept) {
|
|
627
|
+
const c = params.concept.toLowerCase();
|
|
628
|
+
blocks = blocks.filter((b) => {
|
|
629
|
+
try {
|
|
630
|
+
return JSON.parse(b.concepts || "[]").some((x) => String(x).toLowerCase().includes(c));
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
const limit = params.limit ?? 50;
|
|
638
|
+
const sliced = blocks.slice(0, limit);
|
|
639
|
+
// Annotate each headline with the chain it sits on (a block alone means little).
|
|
640
|
+
const chainName = new Map();
|
|
641
|
+
const onChain = (cid) => {
|
|
642
|
+
if (!cid)
|
|
643
|
+
return null;
|
|
644
|
+
if (!chainName.has(cid))
|
|
645
|
+
chainName.set(cid, db.getBlock(cid)?.label ?? cid);
|
|
646
|
+
return chainName.get(cid) ?? null;
|
|
647
|
+
};
|
|
648
|
+
// Currency annotation: superseded blocks stay ACTIVE (the edge is the currency
|
|
649
|
+
// marker, not status) — a typed list must say what replaced them or a stale
|
|
650
|
+
// decision reads as current. Batched single query over the page.
|
|
651
|
+
const supersededBy = db.getSupersededByLabels(sliced.map((b) => b.id));
|
|
652
|
+
const results = sliced.map((b) => {
|
|
653
|
+
const row = { label: b.label, type: b.type, essence: b.essence, on_chain: onChain(b.chain_id) };
|
|
654
|
+
if (supersededBy.has(b.id))
|
|
655
|
+
row.superseded_by = supersededBy.get(b.id);
|
|
656
|
+
// For tasks, status is the headline ("what's still open?") — surface it (+ priority)
|
|
657
|
+
// so the list is usable as a task view without opening each one.
|
|
658
|
+
if (b.type === "task") {
|
|
659
|
+
try {
|
|
660
|
+
const u = (typeof b.content === "string" ? JSON.parse(b.content) : b.content)?.unique || {};
|
|
661
|
+
if (u.status)
|
|
662
|
+
row.status = u.status;
|
|
663
|
+
if (u.priority)
|
|
664
|
+
row.priority = u.priority;
|
|
665
|
+
}
|
|
666
|
+
catch { /* ignore malformed content */ }
|
|
667
|
+
}
|
|
668
|
+
return row;
|
|
669
|
+
});
|
|
670
|
+
return ok({
|
|
671
|
+
total: blocks.length, returned: results.length, results,
|
|
672
|
+
hint: "Headlines only — a block means little alone. Open one with workspace_get(label, \"relations\") to walk its chain (on_chain shows where each sits).",
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
return err("LIST_FAILED", String(error));
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
// ─── Tool: workspace_history ─────────────────────────────────────
|
|
680
|
+
server.tool("workspace_history", `Retrieve the audit trail of changes made to the workspace.
|
|
681
|
+
Use to answer: "what changed in this block?", "what did agent X do?", "what happened since I last ran?".
|
|
682
|
+
Every workspace_remember, workspace_update, workspace_validate, and workspace_forget is logged.`, {
|
|
683
|
+
block_id: z.string().optional().describe("Show history for one specific block (ID or label). Omit for workspace-wide."),
|
|
684
|
+
since: z.string().optional().describe("ISO timestamp — only show changes after this time. E.g. '2026-03-15T10:00:00Z'"),
|
|
685
|
+
changed_by: z.string().optional().describe("Filter by agent name (e.g. 'claude', 'planner')"),
|
|
686
|
+
limit: z.number().optional().describe("Max entries to return. Default: 20"),
|
|
687
|
+
}, async (params) => {
|
|
688
|
+
try {
|
|
689
|
+
const limit = params.limit ?? 20;
|
|
690
|
+
// Resolve label → id if needed
|
|
691
|
+
let blockId;
|
|
692
|
+
if (params.block_id) {
|
|
693
|
+
const b = db.getBlock(params.block_id);
|
|
694
|
+
blockId = b?.id ?? params.block_id;
|
|
695
|
+
}
|
|
696
|
+
const raw = db.getHistory(blockId, limit * 3); // over-fetch, filter in JS
|
|
697
|
+
const sinceMs = params.since ? new Date(params.since).getTime() : 0;
|
|
698
|
+
const filtered = raw
|
|
699
|
+
.filter((h) => {
|
|
700
|
+
if (h.field_changed === "embedding")
|
|
701
|
+
return false; // skip vector noise
|
|
702
|
+
if (sinceMs && new Date(h.changed_at).getTime() < sinceMs)
|
|
703
|
+
return false;
|
|
704
|
+
if (params.changed_by && h.changed_by !== params.changed_by)
|
|
705
|
+
return false;
|
|
706
|
+
return true;
|
|
707
|
+
})
|
|
708
|
+
.slice(0, limit);
|
|
709
|
+
if (filtered.length === 0) {
|
|
710
|
+
return ok({
|
|
711
|
+
entries: [],
|
|
712
|
+
hint: params.since
|
|
713
|
+
? `No changes found since ${params.since}.`
|
|
714
|
+
: "No history found for these filters.",
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
// Enrich with block labels for readability
|
|
718
|
+
const allBlocks = db.getAllBlocks();
|
|
719
|
+
const blockMap = new Map(allBlocks.map((b) => [b.id, b.label]));
|
|
720
|
+
const entries = filtered.map((h) => ({
|
|
721
|
+
at: h.changed_at,
|
|
722
|
+
block: blockMap.get(h.block_id) ?? h.block_id,
|
|
723
|
+
block_id: h.block_id,
|
|
724
|
+
field: h.field_changed,
|
|
725
|
+
from: h.old_value ?? "(none)",
|
|
726
|
+
to: h.new_value ?? "(none)",
|
|
727
|
+
by: h.changed_by ?? "unknown",
|
|
728
|
+
reason: h.reason ?? undefined,
|
|
729
|
+
}));
|
|
730
|
+
return ok({
|
|
731
|
+
total: entries.length,
|
|
732
|
+
filters: {
|
|
733
|
+
block: params.block_id ?? "all",
|
|
734
|
+
since: params.since ?? "all time",
|
|
735
|
+
changed_by: params.changed_by ?? "all agents",
|
|
736
|
+
},
|
|
737
|
+
entries,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
return err("HISTORY_FAILED", String(error));
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
// ─── Tool: workspace_update ──────────────────────────────────────
|
|
745
|
+
server.tool("workspace_update", `Update an existing block. Can update any field: essence, is_a, unique, has, concepts, source. Creates an audit trail.`, {
|
|
746
|
+
id: z.string().describe("Block ID or label to update"),
|
|
747
|
+
changes: z.object({
|
|
748
|
+
essence: z.string().optional().describe("Updated one-line description"),
|
|
749
|
+
is_a: z.string().optional().describe("Updated parent category"),
|
|
750
|
+
unique: z.record(z.string(), z.string()).optional().describe("Updated unique properties (merged with existing)"),
|
|
751
|
+
has: z.record(z.string(), z.union([z.string(), z.array(z.string())])).optional().describe("Updated properties (merged with existing)"),
|
|
752
|
+
concepts: z.array(z.string()).optional().describe("Updated concept tags (replaces existing)"),
|
|
753
|
+
source: z.string().optional().describe("Updated source"),
|
|
754
|
+
label: z.string().optional().describe("Updated label"),
|
|
755
|
+
is_sensitive: z.boolean().optional().describe("Encrypt the block data (cannot be undone via update)"),
|
|
756
|
+
locked: z.boolean().optional().describe("Lock (true) or unlock (false) this block. Locked blocks cannot be updated without force:true."),
|
|
757
|
+
}).describe("Fields to update"),
|
|
758
|
+
reason: z.string().optional().describe("Why this update was made (stored in history)"),
|
|
759
|
+
force: z.boolean().optional().describe("If true, update even if the block is locked. Default: false"),
|
|
760
|
+
challenges_block: z.string().optional().describe("ID or label of a block this update contradicts — creates a 'contradicts' relation"),
|
|
761
|
+
}, async (params) => {
|
|
762
|
+
try {
|
|
763
|
+
const block = db.getBlock(params.id);
|
|
764
|
+
if (!block) {
|
|
765
|
+
return err("BLOCK_NOT_FOUND", `No block found with id or label '${params.id}'`);
|
|
766
|
+
}
|
|
767
|
+
const dbChanges = {};
|
|
768
|
+
const existingContent = JSON.parse(block.content);
|
|
769
|
+
// Handle structured content fields — merge with existing
|
|
770
|
+
if (params.changes.is_a !== undefined || params.changes.unique !== undefined ||
|
|
771
|
+
params.changes.has !== undefined || params.changes.concepts !== undefined) {
|
|
772
|
+
const newContent = { ...existingContent };
|
|
773
|
+
if (params.changes.is_a !== undefined)
|
|
774
|
+
newContent.is_a = params.changes.is_a;
|
|
775
|
+
if (params.changes.unique !== undefined)
|
|
776
|
+
newContent.unique = { ...(existingContent.unique || {}), ...params.changes.unique };
|
|
777
|
+
if (params.changes.has !== undefined)
|
|
778
|
+
newContent.has = { ...(existingContent.has || {}), ...params.changes.has };
|
|
779
|
+
if (params.changes.concepts !== undefined)
|
|
780
|
+
newContent.concepts = params.changes.concepts;
|
|
781
|
+
dbChanges.content = JSON.stringify(newContent);
|
|
782
|
+
}
|
|
783
|
+
// Handle flat fields
|
|
784
|
+
if (params.changes.essence !== undefined)
|
|
785
|
+
dbChanges.essence = params.changes.essence;
|
|
786
|
+
if (params.changes.source !== undefined)
|
|
787
|
+
dbChanges.source = params.changes.source;
|
|
788
|
+
if (params.changes.label !== undefined)
|
|
789
|
+
dbChanges.label = params.changes.label;
|
|
790
|
+
if (params.changes.is_sensitive !== undefined)
|
|
791
|
+
dbChanges.is_sensitive = params.changes.is_sensitive;
|
|
792
|
+
if (params.changes.locked !== undefined)
|
|
793
|
+
dbChanges.locked = params.changes.locked ? 1 : 0;
|
|
794
|
+
// Prevent removing encryption
|
|
795
|
+
if (params.changes.is_sensitive === false && block.is_sensitive === true) {
|
|
796
|
+
return err("UPDATE_FAILED", "Removing encryption (is_sensitive: false) is not allowed.");
|
|
797
|
+
}
|
|
798
|
+
// Regenerate embedding if content changed
|
|
799
|
+
if (dbChanges.essence || dbChanges.content) {
|
|
800
|
+
const essenceForEmbed = dbChanges.essence || block.essence;
|
|
801
|
+
const conceptsForEmbed = dbChanges.concepts ?? block.concepts;
|
|
802
|
+
const newEmbedding = await embeddings.embed(blockEmbeddingText({ essence: essenceForEmbed, concepts: conceptsForEmbed }));
|
|
803
|
+
if (newEmbedding) {
|
|
804
|
+
dbChanges.embedding = JSON.stringify(newEmbedding);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const updated = db.updateBlock(block.id, dbChanges, params.reason, undefined, params.force);
|
|
808
|
+
// Handle challenges_block — creates contradicts relation
|
|
809
|
+
let challengeResult;
|
|
810
|
+
if (params.challenges_block) {
|
|
811
|
+
const challenged = db.getBlock(params.challenges_block);
|
|
812
|
+
if (challenged) {
|
|
813
|
+
db.createRelation({ source_id: block.id, target_id: challenged.id, type: "contradicts", created_by: "agent" });
|
|
814
|
+
challengeResult = { challenged_id: challenged.id, challenged_label: challenged.label };
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return ok({
|
|
818
|
+
id: updated.id,
|
|
819
|
+
label: updated.label,
|
|
820
|
+
updated_fields: Object.keys(params.changes),
|
|
821
|
+
reason: params.reason,
|
|
822
|
+
...(challengeResult ? { challenged: challengeResult } : {}),
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
catch (error) {
|
|
826
|
+
return err("UPDATE_FAILED", String(error));
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
// ─── Tool: workspace_forget ──────────────────────────────────────
|
|
830
|
+
server.tool("workspace_forget", `Archive a block (soft delete). Preserved in history but hidden from recall.`, {
|
|
831
|
+
id: z.string().describe("Block ID or label to archive"),
|
|
832
|
+
reason: z.string().optional().describe("Why this block is being archived"),
|
|
833
|
+
}, async (params) => {
|
|
834
|
+
try {
|
|
835
|
+
const success = db.archiveBlock(params.id, params.reason);
|
|
836
|
+
if (!success)
|
|
837
|
+
return err("BLOCK_NOT_FOUND", `No block found with id or label '${params.id}'`);
|
|
838
|
+
return ok({ archived: true, id: params.id, reason: params.reason || "Archived by agent" });
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
return err("ARCHIVE_FAILED", String(error));
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
// ─── Tool: workspace_batch_save ──────────────────────────────────
|
|
845
|
+
server.tool("workspace_batch_save", `Save multiple knowledge blocks in a single call. Reduces round-trips when saving 3+ related facts at once.
|
|
846
|
+
Each block follows the same schema as workspace_remember. Returns created block IDs in order.
|
|
847
|
+
Use this during research sessions to collapse 5-10 workspace_remember calls into one.`, {
|
|
848
|
+
blocks: z.array(z.object({
|
|
849
|
+
label: z.string().describe("Block label (lowercase_underscore)"),
|
|
850
|
+
type: z.string().describe("Block type: fact, decision, insight, note, etc."),
|
|
851
|
+
essence: z.string().describe("One-line description"),
|
|
852
|
+
is_a: z.string().optional(),
|
|
853
|
+
unique: z.record(z.string(), z.string()).optional(),
|
|
854
|
+
has: z.record(z.string(), z.union([z.string(), z.array(z.string())])).optional(),
|
|
855
|
+
concepts: z.array(z.string()).optional(),
|
|
856
|
+
ttl: z.enum(["session", "1hr", "24hr", "1week", "project", "permanent"]).optional(),
|
|
857
|
+
relations: z.array(z.object({ type: z.string(), target_id: z.string() })).optional(),
|
|
858
|
+
save_context: z.object({
|
|
859
|
+
triggered_by: z.array(z.string()).optional().describe("Block IDs or labels that caused this block — creates prompted_by relations"),
|
|
860
|
+
problem_being_solved: z.string().optional(),
|
|
861
|
+
}).optional().describe("Causal chain — triggered_by is the primary coordinate"),
|
|
862
|
+
})).min(1).describe("Array of blocks to save"),
|
|
863
|
+
project_id: z.string().optional().describe("Project to link all blocks to (creates part_of relations)"),
|
|
864
|
+
}, async (params) => {
|
|
865
|
+
try {
|
|
866
|
+
const created = [];
|
|
867
|
+
for (const spec of params.blocks) {
|
|
868
|
+
const content = {};
|
|
869
|
+
if (spec.is_a)
|
|
870
|
+
content.is_a = spec.is_a;
|
|
871
|
+
if (spec.unique)
|
|
872
|
+
content.unique = spec.unique;
|
|
873
|
+
if (spec.has)
|
|
874
|
+
content.has = spec.has;
|
|
875
|
+
if (spec.save_context)
|
|
876
|
+
content.save_context = spec.save_context;
|
|
877
|
+
const embedding = await embeddings.embed(blockEmbeddingText({ essence: spec.essence, concepts: spec.concepts }));
|
|
878
|
+
const block = db.createBlock({
|
|
879
|
+
label: spec.label,
|
|
880
|
+
type: spec.type || "fact",
|
|
881
|
+
essence: spec.essence,
|
|
882
|
+
content,
|
|
883
|
+
concepts: spec.concepts || [],
|
|
884
|
+
ttl: spec.type === "dead_end" ? "permanent" : (spec.ttl || "permanent"),
|
|
885
|
+
embedding: embedding || undefined,
|
|
886
|
+
});
|
|
887
|
+
// Relations provided in spec
|
|
888
|
+
for (const rel of (spec.relations || [])) {
|
|
889
|
+
const target = db.getBlock(rel.target_id);
|
|
890
|
+
if (target)
|
|
891
|
+
db.createRelation({ source_id: block.id, target_id: target.id, type: rel.type });
|
|
892
|
+
}
|
|
893
|
+
// prompted_by relations from save_context.triggered_by
|
|
894
|
+
if (spec.save_context?.triggered_by?.length) {
|
|
895
|
+
for (const targetRef of spec.save_context.triggered_by) {
|
|
896
|
+
const targetBlock = db.getBlock(targetRef);
|
|
897
|
+
if (targetBlock) {
|
|
898
|
+
db.createRelation({ source_id: block.id, target_id: targetBlock.id, type: "prompted_by" });
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Auto-link to project via project_id column
|
|
903
|
+
if (params.project_id) {
|
|
904
|
+
const project = db.getBlock(params.project_id);
|
|
905
|
+
if (project)
|
|
906
|
+
db.updateBlock(block.id, { project_id: project.id });
|
|
907
|
+
}
|
|
908
|
+
// Compute quality score (same 6-point rubric as workspace_remember)
|
|
909
|
+
const qc = (() => { try {
|
|
910
|
+
return typeof block.content === "string" ? JSON.parse(block.content) : (block.content || {});
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
return {};
|
|
914
|
+
} })();
|
|
915
|
+
const blockConcepts = spec.concepts || [];
|
|
916
|
+
let qScore = 1;
|
|
917
|
+
if (qc.is_a)
|
|
918
|
+
qScore++;
|
|
919
|
+
if (qc.unique && Object.keys(qc.unique).length >= 2)
|
|
920
|
+
qScore++;
|
|
921
|
+
if (blockConcepts.length >= 3)
|
|
922
|
+
qScore++;
|
|
923
|
+
if (spec.type === "project") {
|
|
924
|
+
qScore++;
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
if (db.getRelations(block.id).length > 0)
|
|
928
|
+
qScore++;
|
|
929
|
+
}
|
|
930
|
+
db.updateBlock(block.id, { quality_score: Math.min(qScore, 5) });
|
|
931
|
+
// ── Coordinates check: warn if no causal chain ───────────
|
|
932
|
+
const entry = { id: block.id, label: block.label, type: block.type, quality_score: qScore };
|
|
933
|
+
if (spec.type !== "project") {
|
|
934
|
+
const hasCausalChain = (spec.save_context?.triggered_by?.length ?? 0) > 0 ||
|
|
935
|
+
(spec.relations ?? []).some((r) => ["prompted_by", "derived_from", "based_on", "triggered_by"].includes(r.type));
|
|
936
|
+
if (!hasCausalChain) {
|
|
937
|
+
entry.missing_coordinates =
|
|
938
|
+
`No triggered_by — add save_context.triggered_by to establish causal chain.`;
|
|
939
|
+
}
|
|
940
|
+
// ── Project link check: warn if orphan ───────────────
|
|
941
|
+
if (!block.project_id) {
|
|
942
|
+
const projectLabels = db.getAllBlocks()
|
|
943
|
+
.filter((b) => b.type === "project")
|
|
944
|
+
.map((b) => b.label);
|
|
945
|
+
const hasProjectPrefix = projectLabels.some((pl) => block.label.startsWith(pl + "_"));
|
|
946
|
+
if (!hasProjectPrefix) {
|
|
947
|
+
entry.missing_project_link =
|
|
948
|
+
`Label "${block.label}" has no project prefix — invisible in tree navigation.`;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
created.push(entry);
|
|
953
|
+
}
|
|
954
|
+
db.save();
|
|
955
|
+
return ok({ saved: created.length, blocks: created });
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
return err("BATCH_SAVE_FAILED", String(error));
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
//# sourceMappingURL=core.js.map
|