@quolu/lattice 0.1.0 → 0.12.2
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/README.md +87 -4
- package/bin/lattice-bridge.mjs +87 -0
- package/bin/lattice-dashboard.mjs +70 -0
- package/bin/lattice-mcp.mjs +9 -9
- package/bin/lattice.mjs +61 -8
- package/docs/bridge-setup.md +87 -0
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -0
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -0
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -0
- package/package.json +19 -7
- package/sensor/dist/bin/command-supervision.d.ts +1 -1
- package/sensor/dist/bin/command-supervision.js +4 -4
- package/sensor/dist/bin/command-supervision.js.map +1 -1
- package/sensor/dist/bin/fatal-handler.js +2 -2
- package/sensor/dist/bin/fatal-handler.js.map +1 -1
- package/sensor/dist/bin/{codegraph.js → lattice-sensor.js} +207 -328
- package/sensor/dist/bin/node-version-check.d.ts +7 -5
- package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
- package/sensor/dist/bin/node-version-check.js +19 -14
- package/sensor/dist/bin/node-version-check.js.map +1 -1
- package/sensor/dist/context/index.d.ts +1 -1
- package/sensor/dist/context/index.js +6 -6
- package/sensor/dist/context/index.js.map +1 -1
- package/sensor/dist/db/index.d.ts +5 -5
- package/sensor/dist/db/index.d.ts.map +1 -1
- package/sensor/dist/db/index.js +7 -7
- package/sensor/dist/db/index.js.map +1 -1
- package/sensor/dist/db/migrations.js +1 -1
- package/sensor/dist/db/migrations.js.map +1 -1
- package/sensor/dist/db/queries.d.ts +1 -1
- package/sensor/dist/db/queries.js +5 -5
- package/sensor/dist/db/queries.js.map +1 -1
- package/sensor/dist/db/schema.sql +2 -2
- package/sensor/dist/db/sqlite-adapter.d.ts +2 -2
- package/sensor/dist/db/sqlite-adapter.js +3 -3
- package/sensor/dist/db/sqlite-adapter.js.map +1 -1
- package/sensor/dist/db/wal-valve.d.ts +1 -1
- package/sensor/dist/db/wal-valve.d.ts.map +1 -1
- package/sensor/dist/db/wal-valve.js +2 -2
- package/sensor/dist/db/wal-valve.js.map +1 -1
- package/sensor/dist/directory.d.ts +27 -63
- package/sensor/dist/directory.d.ts.map +1 -1
- package/sensor/dist/directory.js +78 -134
- package/sensor/dist/directory.js.map +1 -1
- package/sensor/dist/errors.d.ts +15 -15
- package/sensor/dist/errors.d.ts.map +1 -1
- package/sensor/dist/errors.js +21 -21
- package/sensor/dist/errors.js.map +1 -1
- package/sensor/dist/extraction/dynamic-import.d.ts +1 -1
- package/sensor/dist/extraction/dynamic-import.js +1 -1
- package/sensor/dist/extraction/extraction-version.d.ts +1 -1
- package/sensor/dist/extraction/extraction-version.js +1 -1
- package/sensor/dist/extraction/generated-detection.d.ts +1 -1
- package/sensor/dist/extraction/generated-detection.js +1 -1
- package/sensor/dist/extraction/grammars.d.ts +3 -3
- package/sensor/dist/extraction/grammars.js +5 -5
- package/sensor/dist/extraction/grammars.js.map +1 -1
- package/sensor/dist/extraction/index.d.ts +6 -6
- package/sensor/dist/extraction/index.js +50 -50
- package/sensor/dist/extraction/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/decode.d.ts +1 -1
- package/sensor/dist/extraction/kernel/decode.js +1 -1
- package/sensor/dist/extraction/kernel/index.d.ts +3 -3
- package/sensor/dist/extraction/kernel/index.js +6 -6
- package/sensor/dist/extraction/kernel/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/layout.d.ts +1 -1
- package/sensor/dist/extraction/kernel/layout.js +1 -1
- package/sensor/dist/extraction/kernel/loader.d.ts +6 -6
- package/sensor/dist/extraction/kernel/loader.js +13 -13
- package/sensor/dist/extraction/kernel/loader.js.map +1 -1
- package/sensor/dist/extraction/languages/cfquery.d.ts +1 -1
- package/sensor/dist/extraction/languages/cfquery.js +1 -1
- package/sensor/dist/extraction/languages/lua.d.ts +11 -0
- package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/lua.js +1 -0
- package/sensor/dist/extraction/languages/lua.js.map +1 -1
- package/sensor/dist/extraction/parse-pool.d.ts +5 -5
- package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
- package/sensor/dist/extraction/parse-pool.js +4 -4
- package/sensor/dist/extraction/parse-worker.js +1 -1
- package/sensor/dist/extraction/parse-worker.js.map +1 -1
- package/sensor/dist/extraction/store-writer.d.ts +1 -1
- package/sensor/dist/extraction/store-writer.js +1 -1
- package/sensor/dist/extraction/svelte-extractor.d.ts +1 -1
- package/sensor/dist/extraction/svelte-extractor.js +1 -1
- package/sensor/dist/extraction/tree-sitter-helpers.js +1 -1
- package/sensor/dist/extraction/tree-sitter.d.ts +2 -2
- package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter.js +71 -4
- package/sensor/dist/extraction/tree-sitter.js.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts +2 -2
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.js +5 -5
- package/sensor/dist/extraction/wasm-runtime-flags.js.map +1 -1
- package/sensor/dist/index.d.ts +36 -36
- package/sensor/dist/index.d.ts.map +1 -1
- package/sensor/dist/index.js +71 -71
- package/sensor/dist/index.js.map +1 -1
- package/sensor/dist/mcp/daemon-manager.js +1 -1
- package/sensor/dist/mcp/daemon-paths.d.ts +3 -3
- package/sensor/dist/mcp/daemon-paths.js +7 -7
- package/sensor/dist/mcp/daemon-paths.js.map +1 -1
- package/sensor/dist/mcp/daemon-registry.d.ts +2 -2
- package/sensor/dist/mcp/daemon-registry.js +5 -5
- package/sensor/dist/mcp/daemon.d.ts +7 -7
- package/sensor/dist/mcp/daemon.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon.js +21 -21
- package/sensor/dist/mcp/daemon.js.map +1 -1
- package/sensor/dist/mcp/dynamic-boundaries.js +1 -1
- package/sensor/dist/mcp/engine.d.ts +9 -9
- package/sensor/dist/mcp/engine.d.ts.map +1 -1
- package/sensor/dist/mcp/engine.js +44 -44
- package/sensor/dist/mcp/engine.js.map +1 -1
- package/sensor/dist/mcp/index.d.ts +14 -14
- package/sensor/dist/mcp/index.d.ts.map +1 -1
- package/sensor/dist/mcp/index.js +41 -41
- package/sensor/dist/mcp/index.js.map +1 -1
- package/sensor/dist/mcp/liveness-watchdog.js +7 -7
- package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
- package/sensor/dist/mcp/ppid-watchdog.d.ts +3 -3
- package/sensor/dist/mcp/ppid-watchdog.js +2 -2
- package/sensor/dist/mcp/proxy.d.ts.map +1 -1
- package/sensor/dist/mcp/proxy.js +37 -37
- package/sensor/dist/mcp/proxy.js.map +1 -1
- package/sensor/dist/mcp/query-pool.d.ts +3 -3
- package/sensor/dist/mcp/query-pool.js +7 -7
- package/sensor/dist/mcp/query-pool.js.map +1 -1
- package/sensor/dist/mcp/query-worker.d.ts +2 -2
- package/sensor/dist/mcp/query-worker.js +7 -7
- package/sensor/dist/mcp/query-worker.js.map +1 -1
- package/sensor/dist/mcp/server-instructions.d.ts +10 -12
- package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
- package/sensor/dist/mcp/server-instructions.js +39 -37
- package/sensor/dist/mcp/server-instructions.js.map +1 -1
- package/sensor/dist/mcp/session.d.ts +2 -2
- package/sensor/dist/mcp/session.js +18 -18
- package/sensor/dist/mcp/session.js.map +1 -1
- package/sensor/dist/mcp/startup-handshake.d.ts +3 -3
- package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
- package/sensor/dist/mcp/startup-handshake.js +3 -3
- package/sensor/dist/mcp/startup-handshake.js.map +1 -1
- package/sensor/dist/mcp/tools.d.ts +53 -53
- package/sensor/dist/mcp/tools.d.ts.map +1 -1
- package/sensor/dist/mcp/tools.js +180 -216
- package/sensor/dist/mcp/tools.js.map +1 -1
- package/sensor/dist/mcp/transport.d.ts +1 -1
- package/sensor/dist/mcp/transport.js +6 -6
- package/sensor/dist/mcp/transport.js.map +1 -1
- package/sensor/dist/mcp/version.d.ts +2 -2
- package/sensor/dist/mcp/version.d.ts.map +1 -1
- package/sensor/dist/mcp/version.js +3 -3
- package/sensor/dist/mcp/version.js.map +1 -1
- package/sensor/dist/project-config.d.ts +6 -6
- package/sensor/dist/project-config.d.ts.map +1 -1
- package/sensor/dist/project-config.js +10 -10
- package/sensor/dist/project-config.js.map +1 -1
- package/sensor/dist/resolution/callback-synthesizer.js +4 -4
- package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
- package/sensor/dist/resolution/frameworks/drupal.d.ts +3 -3
- package/sensor/dist/resolution/frameworks/drupal.js +3 -3
- package/sensor/dist/resolution/frameworks/fabric.js +1 -1
- package/sensor/dist/resolution/frameworks/fabric.js.map +1 -1
- package/sensor/dist/resolution/frameworks/java.js +1 -1
- package/sensor/dist/resolution/frameworks/java.js.map +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.d.ts +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.js +1 -1
- package/sensor/dist/resolution/index.d.ts +1 -1
- package/sensor/dist/resolution/index.js +9 -9
- package/sensor/dist/resolution/index.js.map +1 -1
- package/sensor/dist/resolution/name-matcher.js +2 -2
- package/sensor/dist/resolution/name-matcher.js.map +1 -1
- package/sensor/dist/resolution/resolver-pool.d.ts +2 -2
- package/sensor/dist/resolution/resolver-pool.js +4 -4
- package/sensor/dist/resolution/resolver-pool.js.map +1 -1
- package/sensor/dist/resolution/resolver-worker.js +2 -2
- package/sensor/dist/resolution/resolver-worker.js.map +1 -1
- package/sensor/dist/resolution/workspace-packages.js +1 -1
- package/sensor/dist/resolution/workspace-packages.js.map +1 -1
- package/sensor/dist/search/identifier-segments.d.ts +1 -1
- package/sensor/dist/search/identifier-segments.js +1 -1
- package/sensor/dist/search/query-utils.d.ts +1 -0
- package/sensor/dist/search/query-utils.d.ts.map +1 -1
- package/sensor/dist/search/query-utils.js +10 -3
- package/sensor/dist/search/query-utils.js.map +1 -1
- package/sensor/dist/sync/git-hooks.d.ts +9 -9
- package/sensor/dist/sync/git-hooks.d.ts.map +1 -1
- package/sensor/dist/sync/git-hooks.js +15 -15
- package/sensor/dist/sync/git-hooks.js.map +1 -1
- package/sensor/dist/sync/watch-policy.d.ts +2 -2
- package/sensor/dist/sync/watch-policy.js +5 -5
- package/sensor/dist/sync/watch-policy.js.map +1 -1
- package/sensor/dist/sync/watcher.d.ts +2 -2
- package/sensor/dist/sync/watcher.d.ts.map +1 -1
- package/sensor/dist/sync/watcher.js +16 -16
- package/sensor/dist/sync/watcher.js.map +1 -1
- package/sensor/dist/sync/worktree.d.ts +5 -5
- package/sensor/dist/sync/worktree.d.ts.map +1 -1
- package/sensor/dist/sync/worktree.js +9 -9
- package/sensor/dist/sync/worktree.js.map +1 -1
- package/sensor/dist/telemetry/index.d.ts +8 -8
- package/sensor/dist/telemetry/index.d.ts.map +1 -1
- package/sensor/dist/telemetry/index.js +16 -16
- package/sensor/dist/telemetry/index.js.map +1 -1
- package/sensor/dist/types.d.ts +2 -2
- package/sensor/dist/types.js +1 -1
- package/sensor/dist/ui/color.d.ts +2 -2
- package/sensor/dist/ui/color.js +2 -2
- package/sensor/dist/ui/glyphs.d.ts +3 -3
- package/sensor/dist/ui/glyphs.js +7 -7
- package/sensor/dist/ui/glyphs.js.map +1 -1
- package/sensor/dist/utils.d.ts +4 -4
- package/sensor/dist/utils.js +8 -8
- package/sensor/dist/utils.js.map +1 -1
- package/sensor/package.json +8 -13
- package/src/artifact-contracts.mjs +2 -2
- package/src/boundary-compiler.mjs +28 -28
- package/src/boundary-observation-compiler-v2.mjs +10 -10
- package/src/bounded-seam.mjs +144 -0
- package/src/bridge-cli.mjs +227 -0
- package/src/bridge-config.mjs +346 -0
- package/src/bridge-daemon.mjs +328 -0
- package/src/bridge-launch-agent.mjs +311 -0
- package/src/bridge-server.mjs +309 -0
- package/src/cli-help.mjs +163 -0
- package/src/control-compiler.mjs +30 -30
- package/src/dag-chain.mjs +261 -0
- package/src/factory-diagnostics.mjs +8 -7
- package/src/hash-chain.mjs +76 -0
- package/src/node-version-guard.mjs +5 -4
- package/src/project-cli.mjs +392 -0
- package/src/project-identity.mjs +71 -0
- package/src/rc1-evidence-bundle.mjs +16 -16
- package/src/rc1-v4-campaign.mjs +15 -14
- package/src/rc1-v5-artifact-set.mjs +2 -2
- package/src/rc1-v5-campaign.mjs +15 -14
- package/src/rc1-v6-artifact-set.mjs +18 -18
- package/src/rc1-v6-campaign.mjs +16 -16
- package/src/rc1-v6-causal-binding.mjs +16 -14
- package/src/rc1-v6-measurement.mjs +20 -38
- package/src/rc2-artifact-set.mjs +45 -40
- package/src/rc2-campaign.mjs +48 -66
- package/src/rc2-delivery-policy-front-end.mjs +30 -30
- package/src/rc2-rc1-transfer-front-end.mjs +5 -5
- package/src/rc3-actual-dogfood.mjs +6 -6
- package/src/rc3-dogfood-scaffold.mjs +12 -7
- package/src/rc3-scripted-campaign.mjs +75 -21
- package/src/rc4-stage1-dogfood.mjs +24 -7
- package/src/runtime-cli.mjs +2424 -45
- package/src/runtime-contracts.mjs +7 -7
- package/src/runtime-control-store.mjs +547 -0
- package/src/runtime-controller-protocol.mjs +517 -0
- package/src/runtime-direct-os-observer.mjs +284 -0
- package/src/runtime-engine.mjs +2 -2
- package/src/runtime-errors.mjs +1 -1
- package/src/runtime-event-store.mjs +15 -43
- package/src/runtime-front-end.mjs +26 -26
- package/src/runtime-gate-store.mjs +477 -0
- package/src/runtime-hold-recompile.mjs +214 -23
- package/src/runtime-lifecycle-lock.mjs +290 -0
- package/src/runtime-managed-supervisor.mjs +1276 -0
- package/src/runtime-multi-epoch-store.mjs +761 -0
- package/src/runtime-projection.mjs +62 -0
- package/src/seam-transform.mjs +38 -38
- package/src/{codegraph-adapter.mjs → sensor-adapter.mjs} +7 -7
- package/src/sensor-cli.mjs +107 -0
- package/src/sensor-runtime.mjs +48 -0
- package/src/timestamp-contract.mjs +8 -0
- package/src/todo-chain.mjs +178 -0
- package/src/todo-cli.mjs +1032 -0
- package/src/todo-contracts.mjs +501 -0
- package/src/todo-dashboard-registry.mjs +318 -0
- package/src/todo-gantt-html.mjs +416 -0
- package/src/todo-gantt-layout.mjs +603 -0
- package/src/todo-gantt-live.mjs +269 -0
- package/src/todo-gantt-presentation.mjs +217 -0
- package/src/todo-gantt-svg.mjs +249 -0
- package/src/todo-markdown-renderer.mjs +260 -0
- package/src/todo-migration.mjs +260 -0
- package/src/todo-narrative-anchor.mjs +130 -0
- package/src/todo-revision.mjs +425 -0
- package/src/todo-status.mjs +340 -0
- package/src/todo-store.mjs +3451 -0
- package/src/treatment-compiler.mjs +28 -28
- package/src/treatment-runner.mjs +42 -44
- package/sensor/dist/bin/codegraph.d.ts +0 -26
- package/sensor/dist/bin/codegraph.d.ts.map +0 -1
- package/sensor/dist/bin/codegraph.js.map +0 -1
- package/sensor/dist/bin/uninstall.d.ts +0 -14
- package/sensor/dist/bin/uninstall.d.ts.map +0 -1
- package/sensor/dist/bin/uninstall.js +0 -36
- package/sensor/dist/bin/uninstall.js.map +0 -1
- package/sensor/dist/installer/beta-signup.d.ts +0 -54
- package/sensor/dist/installer/beta-signup.d.ts.map +0 -1
- package/sensor/dist/installer/beta-signup.js +0 -178
- package/sensor/dist/installer/beta-signup.js.map +0 -1
- package/sensor/dist/installer/config-writer.d.ts +0 -28
- package/sensor/dist/installer/config-writer.d.ts.map +0 -1
- package/sensor/dist/installer/config-writer.js +0 -91
- package/sensor/dist/installer/config-writer.js.map +0 -1
- package/sensor/dist/installer/index.d.ts +0 -142
- package/sensor/dist/installer/index.d.ts.map +0 -1
- package/sensor/dist/installer/index.js +0 -622
- package/sensor/dist/installer/index.js.map +0 -1
- package/sensor/dist/installer/instructions-template.d.ts +0 -41
- package/sensor/dist/installer/instructions-template.d.ts.map +0 -1
- package/sensor/dist/installer/instructions-template.js +0 -53
- package/sensor/dist/installer/instructions-template.js.map +0 -1
- package/sensor/dist/installer/targets/antigravity.d.ts +0 -57
- package/sensor/dist/installer/targets/antigravity.d.ts.map +0 -1
- package/sensor/dist/installer/targets/antigravity.js +0 -308
- package/sensor/dist/installer/targets/antigravity.js.map +0 -1
- package/sensor/dist/installer/targets/claude.d.ts +0 -62
- package/sensor/dist/installer/targets/claude.d.ts.map +0 -1
- package/sensor/dist/installer/targets/claude.js +0 -454
- package/sensor/dist/installer/targets/claude.js.map +0 -1
- package/sensor/dist/installer/targets/codex.d.ts +0 -18
- package/sensor/dist/installer/targets/codex.d.ts.map +0 -1
- package/sensor/dist/installer/targets/codex.js +0 -185
- package/sensor/dist/installer/targets/codex.js.map +0 -1
- package/sensor/dist/installer/targets/cursor.d.ts +0 -35
- package/sensor/dist/installer/targets/cursor.d.ts.map +0 -1
- package/sensor/dist/installer/targets/cursor.js +0 -254
- package/sensor/dist/installer/targets/cursor.js.map +0 -1
- package/sensor/dist/installer/targets/gemini.d.ts +0 -26
- package/sensor/dist/installer/targets/gemini.d.ts.map +0 -1
- package/sensor/dist/installer/targets/gemini.js +0 -165
- package/sensor/dist/installer/targets/gemini.js.map +0 -1
- package/sensor/dist/installer/targets/hermes.d.ts +0 -18
- package/sensor/dist/installer/targets/hermes.d.ts.map +0 -1
- package/sensor/dist/installer/targets/hermes.js +0 -359
- package/sensor/dist/installer/targets/hermes.js.map +0 -1
- package/sensor/dist/installer/targets/kiro.d.ts +0 -27
- package/sensor/dist/installer/targets/kiro.d.ts.map +0 -1
- package/sensor/dist/installer/targets/kiro.js +0 -178
- package/sensor/dist/installer/targets/kiro.js.map +0 -1
- package/sensor/dist/installer/targets/opencode.d.ts +0 -38
- package/sensor/dist/installer/targets/opencode.d.ts.map +0 -1
- package/sensor/dist/installer/targets/opencode.js +0 -288
- package/sensor/dist/installer/targets/opencode.js.map +0 -1
- package/sensor/dist/installer/targets/registry.d.ts +0 -35
- package/sensor/dist/installer/targets/registry.d.ts.map +0 -1
- package/sensor/dist/installer/targets/registry.js +0 -91
- package/sensor/dist/installer/targets/registry.js.map +0 -1
- package/sensor/dist/installer/targets/shared.d.ts +0 -101
- package/sensor/dist/installer/targets/shared.d.ts.map +0 -1
- package/sensor/dist/installer/targets/shared.js +0 -264
- package/sensor/dist/installer/targets/shared.js.map +0 -1
- package/sensor/dist/installer/targets/toml.d.ts +0 -52
- package/sensor/dist/installer/targets/toml.d.ts.map +0 -1
- package/sensor/dist/installer/targets/toml.js +0 -147
- package/sensor/dist/installer/targets/toml.js.map +0 -1
- package/sensor/dist/installer/targets/types.d.ts +0 -108
- package/sensor/dist/installer/targets/types.d.ts.map +0 -1
- package/sensor/dist/installer/targets/types.js +0 -16
- package/sensor/dist/installer/targets/types.js.map +0 -1
- package/sensor/dist/upgrade/index.d.ts +0 -171
- package/sensor/dist/upgrade/index.d.ts.map +0 -1
- package/sensor/dist/upgrade/index.js +0 -659
- package/sensor/dist/upgrade/index.js.map +0 -1
- package/sensor/dist/upgrade/remove-binary.d.ts +0 -87
- package/sensor/dist/upgrade/remove-binary.d.ts.map +0 -1
- package/sensor/dist/upgrade/remove-binary.js +0 -289
- package/sensor/dist/upgrade/remove-binary.js.map +0 -1
- package/sensor/dist/upgrade/update-check.d.ts +0 -92
- package/sensor/dist/upgrade/update-check.d.ts.map +0 -1
- package/sensor/dist/upgrade/update-check.js +0 -258
- package/sensor/dist/upgrade/update-check.js.map +0 -1
- package/src/bootstrap.mjs +0 -88
package/sensor/dist/mcp/tools.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* MCP Tool Definitions
|
|
4
4
|
*
|
|
5
|
-
* Defines the tools exposed by the
|
|
5
|
+
* Defines the tools exposed by the LatticeSensor MCP server.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.ToolHandler = exports.tools = exports.IndexOpenError = exports.PathRefusalError = exports.NotIndexedError = void 0;
|
|
@@ -15,12 +15,12 @@ exports.formatDegradedBanner = formatDegradedBanner;
|
|
|
15
15
|
exports.getStaticTools = getStaticTools;
|
|
16
16
|
const directory_1 = require("../directory");
|
|
17
17
|
const version_1 = require("./version");
|
|
18
|
-
// Lazy-load the heavy
|
|
18
|
+
// Lazy-load the heavy LatticeSensor chain off the MCP startup path — see the same
|
|
19
19
|
// helper in engine.ts. ToolHandler must load to answer tools/list (static
|
|
20
20
|
// schemas), but it must NOT drag in sqlite/query layers before the daemon binds;
|
|
21
|
-
//
|
|
21
|
+
// LatticeSensor is pulled in only when a tool actually opens a project. require() is
|
|
22
22
|
// sync + cached (CommonJS build).
|
|
23
|
-
const
|
|
23
|
+
const loadLatticeSensor = () => require('../index').default;
|
|
24
24
|
const worktree_1 = require("../sync/worktree");
|
|
25
25
|
const query_utils_1 = require("../search/query-utils");
|
|
26
26
|
const fs_1 = require("fs");
|
|
@@ -28,11 +28,11 @@ const utils_1 = require("../utils");
|
|
|
28
28
|
const generated_detection_1 = require("../extraction/generated-detection");
|
|
29
29
|
const dynamic_boundaries_1 = require("./dynamic-boundaries");
|
|
30
30
|
/**
|
|
31
|
-
* An expected, recoverable "
|
|
31
|
+
* An expected, recoverable "lattice sensor can't serve this" condition — most
|
|
32
32
|
* importantly a project with no index. The dispatch catch converts these to
|
|
33
33
|
* SUCCESS-shaped responses (guidance text, NO isError): an `isError: true`
|
|
34
34
|
* early in a session teaches the agent the toolset is broken and it stops
|
|
35
|
-
* calling
|
|
35
|
+
* calling lattice sensor entirely (observed repeatedly), which is exactly wrong
|
|
36
36
|
* for conditions the agent can simply work around (use built-in tools for
|
|
37
37
|
* that codebase / pass projectPath). isError is reserved for "stop trying"
|
|
38
38
|
* cases: security refusals ({@link PathRefusalError}) and genuine
|
|
@@ -51,13 +51,13 @@ exports.PathRefusalError = PathRefusalError;
|
|
|
51
51
|
/**
|
|
52
52
|
* ADR 0049 Decision 5②: a genuine malfunction opening the default project's
|
|
53
53
|
* index — DB open failure, schema mismatch, integrity error, or lock
|
|
54
|
-
* contention — as OPPOSED to the project simply having no `.
|
|
54
|
+
* contention — as OPPOSED to the project simply having no `.lattice/sensor/` at
|
|
55
55
|
* all (that stays {@link NotIndexedError}, the success-shaped guidance case).
|
|
56
56
|
* This is fail-closed by design: it stays `isError: true` (via `execute`'s
|
|
57
57
|
* catch, same as {@link PathRefusalError}) rather than the NotIndexedError
|
|
58
58
|
* success-shaped guidance, because silently degrading to "no default
|
|
59
59
|
* project" here would hide a broken index behind text that reads as "just
|
|
60
|
-
* run `
|
|
60
|
+
* run `lattice sensor init . --json`" — actively misleading for a project that WAS
|
|
61
61
|
* indexed and IS now broken.
|
|
62
62
|
*/
|
|
63
63
|
class IndexOpenError extends Error {
|
|
@@ -89,7 +89,7 @@ const MAX_PATH_LENGTH = 4_096;
|
|
|
89
89
|
*/
|
|
90
90
|
const RUST_PATH_PREFIXES = new Set(['crate', 'super', 'self']);
|
|
91
91
|
/**
|
|
92
|
-
* Node kinds that contain other symbols. For these, `
|
|
92
|
+
* Node kinds that contain other symbols. For these, `lattice_sensor_node` with
|
|
93
93
|
* `includeCode=true` returns a structural outline (member names + signatures
|
|
94
94
|
* + line numbers) instead of the full body, which for a large class is a
|
|
95
95
|
* multi-thousand-character wall of source that bloats the agent's context.
|
|
@@ -129,7 +129,7 @@ function normalizeQuerySpelling(query) {
|
|
|
129
129
|
.replace(/(^|[\s,()[\]])(?!(?:kind|lang|language|path|name):)([a-z_][\w@]*):([A-Za-z_][\w@]*)(?=$|[\s,()[\]])/g, '$1$2.$3');
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
|
-
* Calculate the recommended number of
|
|
132
|
+
* Calculate the recommended number of lattice_sensor_explore calls based on project size.
|
|
133
133
|
* Larger codebases need more exploration calls to cover their surface area,
|
|
134
134
|
* but smaller ones should use fewer to avoid unnecessary overhead.
|
|
135
135
|
*/
|
|
@@ -244,21 +244,21 @@ function getExploreOutputBudget(fileCount) {
|
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
/**
|
|
247
|
-
* Whether `
|
|
247
|
+
* Whether `lattice_sensor_explore` should prefix source lines with their line
|
|
248
248
|
* numbers (cat -n style: `<num>\t<code>`).
|
|
249
249
|
*
|
|
250
250
|
* Line numbers let the agent cite `file:line` straight from the explore
|
|
251
251
|
* payload instead of re-Reading the file just to find a line number — the
|
|
252
252
|
* dominant residual cost on precise-tracing questions (#185 follow-up).
|
|
253
253
|
*
|
|
254
|
-
* Defaults ON. Set `
|
|
254
|
+
* Defaults ON. Set `LATTICE_SENSOR_EXPLORE_LINENUMS=0` to disable (used by the
|
|
255
255
|
* A/B harness to measure the payload-cost vs. read-savings tradeoff).
|
|
256
256
|
*/
|
|
257
257
|
function exploreLineNumbersEnabled() {
|
|
258
|
-
return process.env.
|
|
258
|
+
return process.env.LATTICE_SENSOR_EXPLORE_LINENUMS !== '0';
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
261
|
-
* Adaptive explore sizing (default ON). `
|
|
261
|
+
* Adaptive explore sizing (default ON). `lattice_sensor_explore` skeletonizes OFF-SPINE
|
|
262
262
|
* polymorphic-sibling files — a file whose class is one of ≥3 interchangeable
|
|
263
263
|
* implementations of a shared interface (e.g. OkHttp's `: Interceptor` classes) —
|
|
264
264
|
* to class + member signatures (bodies elided), keeping the on-spine exemplar full.
|
|
@@ -267,10 +267,10 @@ function exploreLineNumbersEnabled() {
|
|
|
267
267
|
* search, reads flat). It is PROVABLY INERT elsewhere: distinct pipeline steps (no
|
|
268
268
|
* ≥3-implementer supertype, e.g. Excalidraw's `renderStaticScene`) and on-spine
|
|
269
269
|
* files keep full source — output is byte-identical to shipped on excalidraw /
|
|
270
|
-
* tokio / django / vscode / gin. Set `
|
|
270
|
+
* tokio / django / vscode / gin. Set `LATTICE_SENSOR_ADAPTIVE_EXPLORE=0` to disable.
|
|
271
271
|
*/
|
|
272
272
|
function adaptiveExploreEnabled() {
|
|
273
|
-
return process.env.
|
|
273
|
+
return process.env.LATTICE_SENSOR_ADAPTIVE_EXPLORE !== '0' && process.env.LATTICE_SENSOR_ADAPTIVE_EXPLORE !== 'false';
|
|
274
274
|
}
|
|
275
275
|
/**
|
|
276
276
|
* How long the FIRST tool call waits on the post-open catch-up reconcile before
|
|
@@ -281,12 +281,12 @@ function adaptiveExploreEnabled() {
|
|
|
281
281
|
* for a clean answer, then serve and let the reconcile finish in the background
|
|
282
282
|
* (it yields to the event loop, so a concurrent read still runs).
|
|
283
283
|
*
|
|
284
|
-
* `
|
|
284
|
+
* `LATTICE_SENSOR_CATCHUP_GATE_TIMEOUT_MS` overrides the default; `0` restores the
|
|
285
285
|
* old unbounded-wait behavior (always block until the reconcile completes).
|
|
286
286
|
*/
|
|
287
287
|
const DEFAULT_CATCHUP_GATE_TIMEOUT_MS = 3000;
|
|
288
288
|
function resolveCatchUpGateTimeoutMs() {
|
|
289
|
-
const raw = process.env.
|
|
289
|
+
const raw = process.env.LATTICE_SENSOR_CATCHUP_GATE_TIMEOUT_MS;
|
|
290
290
|
if (raw === undefined || raw === '')
|
|
291
291
|
return DEFAULT_CATCHUP_GATE_TIMEOUT_MS;
|
|
292
292
|
const n = Number(raw);
|
|
@@ -311,7 +311,7 @@ function numberSourceLines(slice, firstLineNumber) {
|
|
|
311
311
|
return out.join('\n');
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
|
-
* Unique line-prefix for a per-file source section in
|
|
314
|
+
* Unique line-prefix for a per-file source section in lattice_sensor_explore output.
|
|
315
315
|
* Issue #778: tool results dropped ATX headings (`####`, `##`, `###`) for bold
|
|
316
316
|
* labels so Markdown-rendering MCP clients (e.g. the Claude Code VSCode
|
|
317
317
|
* extension) stop blowing every header up to H1–H4. The path is bold + a code
|
|
@@ -320,12 +320,12 @@ function numberSourceLines(slice, firstLineNumber) {
|
|
|
320
320
|
* truncation boundary (`handleExplore`) keys off to cut on whole file sections.
|
|
321
321
|
*/
|
|
322
322
|
const FILE_SECTION_PREFIX = '**`';
|
|
323
|
-
// Placeholder for
|
|
323
|
+
// Placeholder for lattice_sensor_explore's "Found N symbols across M files." line.
|
|
324
324
|
// The honest N/M can only be known after the final truncation drops trailing
|
|
325
325
|
// sections (#1046), so the header is emitted as this sentinel and substituted
|
|
326
326
|
// at the very end. This bracketed token never occurs in rendered source or a
|
|
327
327
|
// file path, so the final string-replace can't collide.
|
|
328
|
-
const SUMMARY_SENTINEL = '[[
|
|
328
|
+
const SUMMARY_SENTINEL = '[[lattice sensor-explore-summary]]';
|
|
329
329
|
function fileSectionHeader(filePath, suffix) {
|
|
330
330
|
return suffix
|
|
331
331
|
? `${FILE_SECTION_PREFIX}${filePath}\`** — ${suffix}`
|
|
@@ -345,7 +345,7 @@ function formatStaleBanner(stale) {
|
|
|
345
345
|
return ` - ${p.path} (edited ${ageMs}ms ago, ${label})`;
|
|
346
346
|
});
|
|
347
347
|
return ('⚠️ Some files referenced below were edited since the last index sync — ' +
|
|
348
|
-
'their
|
|
348
|
+
'their lattice sensor entries may be stale:\n' +
|
|
349
349
|
lines.join('\n') +
|
|
350
350
|
'\nFor accurate content of those specific files, Read them directly. ' +
|
|
351
351
|
'The rest of this response is fresh.');
|
|
@@ -373,10 +373,10 @@ function formatStaleFooter(stale) {
|
|
|
373
373
|
* `getPendingFiles()` is empty, so the per-file banner above can't fire even
|
|
374
374
|
* though the index is now FROZEN and silently drifting stale. Leads with the
|
|
375
375
|
* agent-actionable instruction (Read directly) and carries the reason, which
|
|
376
|
-
* already names the operator remedy (`
|
|
376
|
+
* already names the operator remedy (`lattice sensor sync . --json` / git hooks).
|
|
377
377
|
*/
|
|
378
378
|
function formatDegradedBanner(reason) {
|
|
379
|
-
return ('⚠️
|
|
379
|
+
return ('⚠️ LatticeSensor auto-sync is DISABLED — live file watching stopped, so the index is ' +
|
|
380
380
|
'frozen and any file edited since then is stale here. Read files directly to confirm ' +
|
|
381
381
|
'current content before relying on it.' +
|
|
382
382
|
(reason ? `\n Reason: ${reason}` : ''));
|
|
@@ -386,10 +386,10 @@ function formatDegradedBanner(reason) {
|
|
|
386
386
|
*/
|
|
387
387
|
const projectPathProperty = {
|
|
388
388
|
type: 'string',
|
|
389
|
-
description: 'Absolute path to the project to query (or any directory inside it) —
|
|
389
|
+
description: 'Absolute path to the project to query (or any directory inside it) — lattice sensor uses the nearest .lattice/sensor/ index at or above that path. Omit to use this session\'s default project. Pass it to query a second codebase, or when the server root has no index of its own (e.g. a monorepo where only sub-projects are indexed, so there is no default project).',
|
|
390
390
|
};
|
|
391
391
|
/**
|
|
392
|
-
* EVERY
|
|
392
|
+
* EVERY lattice sensor tool is query-only: it reads the pre-built index and never
|
|
393
393
|
* mutates the workspace (indexing is the user's explicit CLI call, never the
|
|
394
394
|
* agent's). Advertising this read-only contract lets clients that gate on it run
|
|
395
395
|
* the tools where a possibly-mutating tool would be blocked — most concretely,
|
|
@@ -406,9 +406,9 @@ const READ_ONLY_ANNOTATIONS = {
|
|
|
406
406
|
openWorldHint: false,
|
|
407
407
|
};
|
|
408
408
|
/**
|
|
409
|
-
* All
|
|
409
|
+
* All LatticeSensor MCP tools
|
|
410
410
|
*
|
|
411
|
-
* Designed for minimal context usage - use
|
|
411
|
+
* Designed for minimal context usage - use lattice_sensor_explore as the primary tool
|
|
412
412
|
* (one call usually answers the whole question), and only use other tools for
|
|
413
413
|
* targeted follow-up queries.
|
|
414
414
|
*
|
|
@@ -416,8 +416,8 @@ const READ_ONLY_ANNOTATIONS = {
|
|
|
416
416
|
*/
|
|
417
417
|
exports.tools = [
|
|
418
418
|
{
|
|
419
|
-
name: '
|
|
420
|
-
description: 'Quick symbol search by name. Returns locations only (no code). Use
|
|
419
|
+
name: 'lattice_sensor_search',
|
|
420
|
+
description: 'Quick symbol search by name. Returns locations only (no code). Use lattice_sensor_explore instead to get the actual source / understand an area in one call.',
|
|
421
421
|
inputSchema: {
|
|
422
422
|
type: 'object',
|
|
423
423
|
properties: {
|
|
@@ -442,8 +442,8 @@ exports.tools = [
|
|
|
442
442
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
name: '
|
|
446
|
-
description: 'List functions that call <symbol>. For the full flow, use
|
|
445
|
+
name: 'lattice_sensor_callers',
|
|
446
|
+
description: 'List functions that call <symbol>. For the full flow, use lattice_sensor_explore.',
|
|
447
447
|
inputSchema: {
|
|
448
448
|
type: 'object',
|
|
449
449
|
properties: {
|
|
@@ -467,8 +467,8 @@ exports.tools = [
|
|
|
467
467
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
468
468
|
},
|
|
469
469
|
{
|
|
470
|
-
name: '
|
|
471
|
-
description: 'List functions that <symbol> calls. For the full flow, use
|
|
470
|
+
name: 'lattice_sensor_callees',
|
|
471
|
+
description: 'List functions that <symbol> calls. For the full flow, use lattice_sensor_explore.',
|
|
472
472
|
inputSchema: {
|
|
473
473
|
type: 'object',
|
|
474
474
|
properties: {
|
|
@@ -492,7 +492,7 @@ exports.tools = [
|
|
|
492
492
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
493
493
|
},
|
|
494
494
|
{
|
|
495
|
-
name: '
|
|
495
|
+
name: 'lattice_sensor_impact',
|
|
496
496
|
description: 'List symbols affected by changing <symbol>. Use before a refactor.',
|
|
497
497
|
inputSchema: {
|
|
498
498
|
type: 'object',
|
|
@@ -517,8 +517,8 @@ exports.tools = [
|
|
|
517
517
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
518
518
|
},
|
|
519
519
|
{
|
|
520
|
-
name: '
|
|
521
|
-
description: 'Two modes. (1) READ A FILE — use INSTEAD of the Read tool: pass `file` (a path or basename) with no `symbol` and it returns that file\'s current on-disk source with line numbers, exactly the shape Read gives you (`<n>\\t<line>`, safe to Edit from), narrowable with `offset`/`limit` just like Read — PLUS a one-line note of which files depend on it. Same bytes as Read, faster (served from the index), with the blast radius attached. Use it whenever you would Read a source file. (2) ONE SYMBOL you can name — its location, signature, verbatim source (includeCode=true) and caller/callee trail in one call, so before changing it you see what calls it and what your edit would break. For an AMBIGUOUS name it returns EVERY matching definition\'s body in one call (so you never Read a file to find the right overload); pass `file`/`line` to pin one. Use
|
|
520
|
+
name: 'lattice_sensor_node',
|
|
521
|
+
description: 'Two modes. (1) READ A FILE — use INSTEAD of the Read tool: pass `file` (a path or basename) with no `symbol` and it returns that file\'s current on-disk source with line numbers, exactly the shape Read gives you (`<n>\\t<line>`, safe to Edit from), narrowable with `offset`/`limit` just like Read — PLUS a one-line note of which files depend on it. Same bytes as Read, faster (served from the index), with the blast radius attached. Use it whenever you would Read a source file. (2) ONE SYMBOL you can name — its location, signature, verbatim source (includeCode=true) and caller/callee trail in one call, so before changing it you see what calls it and what your edit would break. For an AMBIGUOUS name it returns EVERY matching definition\'s body in one call (so you never Read a file to find the right overload); pass `file`/`line` to pin one. Use lattice_sensor_explore for several related symbols or the full flow.',
|
|
522
522
|
inputSchema: {
|
|
523
523
|
type: 'object',
|
|
524
524
|
properties: {
|
|
@@ -559,14 +559,14 @@ exports.tools = [
|
|
|
559
559
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
560
560
|
},
|
|
561
561
|
{
|
|
562
|
-
name: '
|
|
562
|
+
name: 'lattice_sensor_explore',
|
|
563
563
|
description: 'PRIMARY TOOL — call FIRST for almost any question OR before an edit: how does X work, architecture, a bug, where/what is X, surveying an area, or the symbols you are about to change. Returns the verbatim source of the relevant symbols grouped by file in ONE capped call (Read-equivalent — treat the shown source as already Read; do NOT re-open those files), plus the call path among them. Query can be a natural-language question OR a bag of symbol/file names. Usually the ONLY call you need — more accurate context, in far fewer tokens and round-trips than a search/Read/Grep loop.',
|
|
564
564
|
inputSchema: {
|
|
565
565
|
type: 'object',
|
|
566
566
|
properties: {
|
|
567
567
|
query: {
|
|
568
568
|
type: 'string',
|
|
569
|
-
description: 'Symbol names, file names, or short code terms to explore (e.g., "AuthService loginUser session-manager", "GraphTraverser BFS impact traversal.ts"). For a flow question, name the symbols spanning the flow (e.g. "mutateElement renderScene"). A natural-language question works too — no prior
|
|
569
|
+
description: 'Symbol names, file names, or short code terms to explore (e.g., "AuthService loginUser session-manager", "GraphTraverser BFS impact traversal.ts"). For a flow question, name the symbols spanning the flow (e.g. "mutateElement renderScene"). A natural-language question works too — no prior lattice_sensor_search needed.',
|
|
570
570
|
},
|
|
571
571
|
maxFiles: {
|
|
572
572
|
type: 'number',
|
|
@@ -580,7 +580,7 @@ exports.tools = [
|
|
|
580
580
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
581
581
|
},
|
|
582
582
|
{
|
|
583
|
-
name: '
|
|
583
|
+
name: 'lattice_sensor_status',
|
|
584
584
|
description: 'Index health check (files / nodes / edges) AND the version/execution-mode declaration surface: reports the running Lattice sensor version, the index schema stats, and a machine-readable `mode: daemon|direct` + `reason` field describing how this session is served (see ADR 0049). Skip unless debugging.',
|
|
585
585
|
inputSchema: {
|
|
586
586
|
type: 'object',
|
|
@@ -591,7 +591,7 @@ exports.tools = [
|
|
|
591
591
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
592
592
|
},
|
|
593
593
|
{
|
|
594
|
-
name: '
|
|
594
|
+
name: 'lattice_sensor_files',
|
|
595
595
|
description: 'Indexed file tree with language + symbol counts. Faster than Glob for project layout.',
|
|
596
596
|
inputSchema: {
|
|
597
597
|
type: 'object',
|
|
@@ -630,7 +630,7 @@ exports.tools = [
|
|
|
630
630
|
*
|
|
631
631
|
* Used for the NO-DEFAULT-PROJECT tool surface (issue #993): when the MCP server
|
|
632
632
|
* has no default project to fall back to — a gateway server started outside any
|
|
633
|
-
* repo, or a monorepo root whose `.
|
|
633
|
+
* repo, or a monorepo root whose `.lattice/sensor/` indexes live only in sub-projects
|
|
634
634
|
* — every call MUST carry an explicit `projectPath`, so the schema should say so.
|
|
635
635
|
* A `required` field is a HIGH-salience channel (MCP clients surface and often
|
|
636
636
|
* validate it), unlike the instructions text the reporter found too weak to stop
|
|
@@ -659,45 +659,41 @@ function withRequiredProjectPath(defs) {
|
|
|
659
659
|
/**
|
|
660
660
|
* Allowlist-filtered tool definitions WITHOUT an engine — the static surface the
|
|
661
661
|
* proxy answers `tools/list` with before any project is open. Mirrors
|
|
662
|
-
* `ToolHandler.getTools()` in the no-
|
|
662
|
+
* `ToolHandler.getTools()` in the no-LatticeSensor case (the dynamic per-repo budget
|
|
663
663
|
* note in a description only adds once `cg` is loaded; the schemas are static).
|
|
664
664
|
*/
|
|
665
665
|
function getStaticTools() {
|
|
666
|
-
const raw = process.env.
|
|
666
|
+
const raw = process.env.LATTICE_SENSOR_MCP_TOOLS;
|
|
667
667
|
if (!raw || !raw.trim()) {
|
|
668
|
-
return exports.tools.filter(t => DEFAULT_MCP_TOOLS.has(t.name.replace(/^
|
|
668
|
+
return exports.tools.filter(t => DEFAULT_MCP_TOOLS.has(t.name.replace(/^lattice_sensor_/, '')));
|
|
669
669
|
}
|
|
670
|
-
const allow = new Set(raw.split(',').map(s => s.trim().replace(/^
|
|
671
|
-
return allow.size ? exports.tools.filter(t => allow.has(t.name.replace(/^
|
|
670
|
+
const allow = new Set(raw.split(',').map(s => s.trim().replace(/^lattice_sensor_/, '')).filter(Boolean));
|
|
671
|
+
return allow.size ? exports.tools.filter(t => allow.has(t.name.replace(/^lattice_sensor_/, ''))) : exports.tools;
|
|
672
672
|
}
|
|
673
673
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
* narrower slice of what explore already does, and presence itself steers
|
|
678
|
-
* mis-picks, so they are no longer LISTED to agents.
|
|
679
|
-
*
|
|
680
|
-
* The other defined tools (`node`, `search`, `callers`, plus callees/impact/files/
|
|
681
|
-
* status) remain fully functional — handlers stay, the library API and CLI are
|
|
682
|
-
* untouched, and `CODEGRAPH_MCP_TOOLS=explore,node,...` re-enables any of them.
|
|
674
|
+
* ADR 0049 / 0059: Lattice MCP v1 publishes exactly these eight compatibility
|
|
675
|
+
* tool names. The provider is Lattice; narrowing the default based on repo size
|
|
676
|
+
* would silently remove contracted surfaces and make host acceptance ambiguous.
|
|
683
677
|
*/
|
|
684
|
-
const DEFAULT_MCP_TOOLS = new Set([
|
|
678
|
+
const DEFAULT_MCP_TOOLS = new Set([
|
|
679
|
+
'search', 'callers', 'callees', 'impact', 'node', 'explore', 'status', 'files',
|
|
680
|
+
]);
|
|
685
681
|
/**
|
|
686
|
-
* Tool handler that executes tools against a
|
|
682
|
+
* Tool handler that executes tools against a LatticeSensor instance
|
|
687
683
|
*
|
|
688
684
|
* Supports cross-project queries via the projectPath parameter.
|
|
689
685
|
* Other projects are opened on-demand and cached for performance.
|
|
690
686
|
*/
|
|
691
687
|
class ToolHandler {
|
|
692
688
|
cg;
|
|
693
|
-
// Cache of opened
|
|
689
|
+
// Cache of opened LatticeSensor instances for cross-project queries
|
|
694
690
|
projectCache = new Map();
|
|
695
691
|
// The directory the server last searched for a default project. Surfaced in
|
|
696
692
|
// the "not initialized" error so users can see why detection missed.
|
|
697
693
|
defaultProjectHint = null;
|
|
698
694
|
// Per-start-path cache of the git worktree/index mismatch (issue #155). The
|
|
699
695
|
// mismatch is a fixed property of (where the request came from → which
|
|
700
|
-
// .
|
|
696
|
+
// .lattice/sensor/ it resolves to), so the up-to-two `git rev-parse` spawns run
|
|
701
697
|
// once and every later tool call reuses the result — never shelling out to
|
|
702
698
|
// git on the hot path. `undefined` = not computed yet; `null` = no mismatch.
|
|
703
699
|
worktreeMismatchCache = new Map();
|
|
@@ -719,12 +715,12 @@ class ToolHandler {
|
|
|
719
715
|
// ADR 0049 Decision 5②: the reason the DEFAULT project's index failed to
|
|
720
716
|
// open (DB open failure, schema mismatch, integrity error, lock
|
|
721
717
|
// contention) — set by the engine's init path, distinct from "no
|
|
722
|
-
// .
|
|
718
|
+
// .lattice/sensor/ found" (which leaves this null and falls through to
|
|
723
719
|
// NotIndexedError's success-shaped guidance). Cleared on a successful
|
|
724
|
-
// {@link
|
|
720
|
+
// {@link setDefaultLatticeSensor}.
|
|
725
721
|
openFailure = null;
|
|
726
722
|
// ADR 0049 Decision 5④: this session's execution mode, reported by
|
|
727
|
-
//
|
|
723
|
+
// lattice_sensor_status as a machine-readable field. Defaults to a direct/
|
|
728
724
|
// no-daemon placeholder — every real caller (MCPServer.startDirect, the
|
|
729
725
|
// shared Daemon, the local-handshake proxy's fallback engine) calls
|
|
730
726
|
// {@link setExecutionMode} before any tool call can reach a live engine.
|
|
@@ -742,20 +738,20 @@ class ToolHandler {
|
|
|
742
738
|
this.queryPool = pool;
|
|
743
739
|
}
|
|
744
740
|
/**
|
|
745
|
-
* Update the default
|
|
741
|
+
* Update the default LatticeSensor instance (e.g. after lazy initialization).
|
|
746
742
|
* A successful open supersedes any previously recorded open failure.
|
|
747
743
|
*/
|
|
748
|
-
|
|
744
|
+
setDefaultLatticeSensor(cg) {
|
|
749
745
|
this.cg = cg;
|
|
750
746
|
this.openFailure = null;
|
|
751
747
|
}
|
|
752
748
|
/**
|
|
753
749
|
* ADR 0049 Decision 5②: record that the DEFAULT project's index failed to
|
|
754
|
-
* open for a reason OTHER than "no .
|
|
755
|
-
* this from its init catch block. `
|
|
750
|
+
* open for a reason OTHER than "no .lattice/sensor/ found" — the engine calls
|
|
751
|
+
* this from its init catch block. `getLatticeSensor()` throws it (fail closed)
|
|
756
752
|
* instead of the NotIndexedError guidance the next time a tool call needs
|
|
757
753
|
* the default project, so the failure surfaces as `isError: true` rather
|
|
758
|
-
* than being silently absorbed into "just run
|
|
754
|
+
* than being silently absorbed into "just run lattice sensor init . --json" prose.
|
|
759
755
|
*/
|
|
760
756
|
setOpenFailure(err) {
|
|
761
757
|
this.openFailure = err;
|
|
@@ -764,7 +760,7 @@ class ToolHandler {
|
|
|
764
760
|
* ADR 0049 Decision 5④: record this session's execution mode
|
|
765
761
|
* (`daemon`/`direct`) and the typed reason direct mode was chosen (or
|
|
766
762
|
* `'daemon'` when routed through the shared daemon). Surfaced verbatim by
|
|
767
|
-
* `
|
|
763
|
+
* `lattice_sensor_status`.
|
|
768
764
|
*/
|
|
769
765
|
setExecutionMode(mode, reason) {
|
|
770
766
|
this.executionMode = { mode, reason };
|
|
@@ -813,8 +809,8 @@ class ToolHandler {
|
|
|
813
809
|
timedOut,
|
|
814
810
|
]);
|
|
815
811
|
if (outcome === 'timeout') {
|
|
816
|
-
process.stderr.write(`[
|
|
817
|
-
`Set
|
|
812
|
+
process.stderr.write(`[LatticeSensor MCP] Catch-up reconcile still running after ${timeoutMs}ms; serving this tool call now and finishing the reconcile in the background (#905). ` +
|
|
813
|
+
`Set LATTICE_SENSOR_CATCHUP_GATE_TIMEOUT_MS=0 to always wait for it.\n`);
|
|
818
814
|
}
|
|
819
815
|
}
|
|
820
816
|
finally {
|
|
@@ -830,46 +826,46 @@ class ToolHandler {
|
|
|
830
826
|
this.defaultProjectHint = searchedPath;
|
|
831
827
|
}
|
|
832
828
|
/**
|
|
833
|
-
* Whether a default
|
|
829
|
+
* Whether a default LatticeSensor instance is available
|
|
834
830
|
*/
|
|
835
|
-
|
|
831
|
+
hasDefaultLatticeSensor() {
|
|
836
832
|
return this.cg !== null;
|
|
837
833
|
}
|
|
838
834
|
/**
|
|
839
|
-
* Optional allowlist of exposed tools, parsed from the
|
|
835
|
+
* Optional allowlist of exposed tools, parsed from the LATTICE_SENSOR_MCP_TOOLS
|
|
840
836
|
* env var (comma-separated short names, e.g. "trace,search,node,context").
|
|
841
|
-
* Unset/empty →
|
|
837
|
+
* Unset/empty → the contracted eight-tool surface. Lets an operator (or an A/B harness)
|
|
842
838
|
* trim the tool surface without rebuilding the client config; the ablated
|
|
843
839
|
* tool is then truly absent from ListTools rather than merely denied on call.
|
|
844
|
-
* Matching is on the short form, so "node" and "
|
|
840
|
+
* Matching is on the short form, so "node" and "lattice_sensor_node" both work.
|
|
845
841
|
*/
|
|
846
842
|
toolAllowlist() {
|
|
847
|
-
const raw = process.env.
|
|
843
|
+
const raw = process.env.LATTICE_SENSOR_MCP_TOOLS;
|
|
848
844
|
if (!raw || !raw.trim())
|
|
849
845
|
return null;
|
|
850
|
-
const short = (s) => s.trim().replace(/^
|
|
846
|
+
const short = (s) => s.trim().replace(/^lattice_sensor_/, '');
|
|
851
847
|
const set = new Set(raw.split(',').map(short).filter(Boolean));
|
|
852
848
|
return set.size ? set : null;
|
|
853
849
|
}
|
|
854
|
-
/** Whether a tool name passes the
|
|
850
|
+
/** Whether a tool name passes the LATTICE_SENSOR_MCP_TOOLS allowlist (if any). */
|
|
855
851
|
isToolAllowed(name) {
|
|
856
852
|
const allow = this.toolAllowlist();
|
|
857
|
-
return !allow || allow.has(name.replace(/^
|
|
853
|
+
return !allow || allow.has(name.replace(/^lattice_sensor_/, ''));
|
|
858
854
|
}
|
|
859
855
|
/**
|
|
860
856
|
* Get tool definitions with dynamic descriptions based on project size.
|
|
861
|
-
* The
|
|
862
|
-
* scaled to the number of indexed files. Honors the
|
|
857
|
+
* The lattice_sensor_explore tool description includes a budget recommendation
|
|
858
|
+
* scaled to the number of indexed files. Honors the LATTICE_SENSOR_MCP_TOOLS
|
|
863
859
|
* allowlist so a trimmed surface is reflected in ListTools.
|
|
864
860
|
*/
|
|
865
861
|
getTools() {
|
|
866
862
|
const allow = this.toolAllowlist();
|
|
867
|
-
// No explicit allowlist → the
|
|
863
|
+
// No explicit allowlist → the contracted eight-tool surface (see
|
|
868
864
|
// DEFAULT_MCP_TOOLS for the evidence). An allowlist replaces the
|
|
869
865
|
// default entirely, so any defined tool can be re-enabled.
|
|
870
866
|
let visible = allow
|
|
871
|
-
? exports.tools.filter(t => allow.has(t.name.replace(/^
|
|
872
|
-
: exports.tools.filter(t => DEFAULT_MCP_TOOLS.has(t.name.replace(/^
|
|
867
|
+
? exports.tools.filter(t => allow.has(t.name.replace(/^lattice_sensor_/, '')))
|
|
868
|
+
: exports.tools.filter(t => DEFAULT_MCP_TOOLS.has(t.name.replace(/^lattice_sensor_/, '')));
|
|
873
869
|
// No default project loaded → no-root-index case (#993): a gateway server
|
|
874
870
|
// started outside any repo, or a monorepo root whose indexes live in
|
|
875
871
|
// sub-projects. With nothing to fall back to, EVERY call needs an explicit
|
|
@@ -885,40 +881,8 @@ class ToolHandler {
|
|
|
885
881
|
try {
|
|
886
882
|
const stats = this.cg.getStats();
|
|
887
883
|
const budget = getExploreBudget(stats.fileCount);
|
|
888
|
-
// Tiny-repo tool gating: on projects under TINY_REPO_FILE_THRESHOLD
|
|
889
|
-
// files, only expose the core trio (search, node, explore) — one
|
|
890
|
-
// below even the 4-tool default: at this scale callers, too, reduces
|
|
891
|
-
// to one grep. (Historical note: the audit below ran when context and
|
|
892
|
-
// trace still existed; its "5 core tools" are today's trio.)
|
|
893
|
-
//
|
|
894
|
-
// n=2 audits ruled out cutting below 5 tools:
|
|
895
|
-
// - 3-tool gate (search + context + trace): cost regressed on
|
|
896
|
-
// cobra/ky/sinatra. The agent fell back to raw Reads to cover
|
|
897
|
-
// what codegraph_node + codegraph_explore would have answered.
|
|
898
|
-
// - 1-tool gate (search only): catastrophic regression — express
|
|
899
|
-
// went from -43% WIN to +107% LOSS. With only search, the agent
|
|
900
|
-
// can't navigate the call graph structurally and reads everything.
|
|
901
|
-
//
|
|
902
|
-
// 5 is the empirical lower bound. Tools beyond search/context/
|
|
903
|
-
// node/explore/trace pay overhead that the agent doesn't recoup
|
|
904
|
-
// on tiny-repo flow questions.
|
|
905
|
-
// ITER4: raise threshold 150 → 500 so single-file frameworks
|
|
906
|
-
// (sinatra at 159, slim_framework around 200) also get the
|
|
907
|
-
// 5-tool surface. The empirical 5-tool floor was set on <150
|
|
908
|
-
// probes; iter3 measurement showed sinatra is structurally the
|
|
909
|
-
// SAME problem as cobra (single-file WITHOUT-arm Read wins),
|
|
910
|
-
// so it deserves the same gating.
|
|
911
|
-
const TINY_REPO_FILE_THRESHOLD = 500;
|
|
912
|
-
const TINY_REPO_CORE_TOOLS = new Set([
|
|
913
|
-
'codegraph_explore',
|
|
914
|
-
'codegraph_search',
|
|
915
|
-
'codegraph_node',
|
|
916
|
-
]);
|
|
917
|
-
if (stats.fileCount < TINY_REPO_FILE_THRESHOLD) {
|
|
918
|
-
visible = visible.filter(t => TINY_REPO_CORE_TOOLS.has(t.name));
|
|
919
|
-
}
|
|
920
884
|
return visible.map(tool => {
|
|
921
|
-
if (tool.name === '
|
|
885
|
+
if (tool.name === 'lattice_sensor_explore') {
|
|
922
886
|
return {
|
|
923
887
|
...tool,
|
|
924
888
|
description: `${tool.description} Budget: make at most ${budget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).`,
|
|
@@ -932,15 +896,15 @@ class ToolHandler {
|
|
|
932
896
|
}
|
|
933
897
|
}
|
|
934
898
|
/**
|
|
935
|
-
* Get
|
|
899
|
+
* Get LatticeSensor instance for a project
|
|
936
900
|
*
|
|
937
|
-
* If projectPath is provided, opens that project's
|
|
938
|
-
* Otherwise returns the default
|
|
901
|
+
* If projectPath is provided, opens that project's LatticeSensor (cached).
|
|
902
|
+
* Otherwise returns the default LatticeSensor instance.
|
|
939
903
|
*
|
|
940
|
-
* Walks up parent directories to find the nearest .
|
|
904
|
+
* Walks up parent directories to find the nearest .lattice/sensor/ folder,
|
|
941
905
|
* similar to how git finds .git/ directories.
|
|
942
906
|
*/
|
|
943
|
-
|
|
907
|
+
getLatticeSensor(projectPath) {
|
|
944
908
|
if (!projectPath) {
|
|
945
909
|
if (!this.cg) {
|
|
946
910
|
// ADR 0049 Decision 5②: a recorded open failure (DB open error,
|
|
@@ -950,31 +914,31 @@ class ToolHandler {
|
|
|
950
914
|
// NotIndexedError guidance below, which would misleadingly read as
|
|
951
915
|
// "this project just isn't indexed yet".
|
|
952
916
|
if (this.openFailure) {
|
|
953
|
-
throw new IndexOpenError(`The default project's
|
|
917
|
+
throw new IndexOpenError(`The default project's LatticeSensor index failed to open: ${this.openFailure.message}\n` +
|
|
954
918
|
'This is a genuine malfunction (corrupt/locked database, schema mismatch, or ' +
|
|
955
919
|
'similar) — NOT a missing index. Retry the call once; if it persists, the index ' +
|
|
956
|
-
'may need to be rebuilt (`
|
|
920
|
+
'may need to be rebuilt (`lattice sensor init . --json` after removing `.lattice/sensor/`) or a ' +
|
|
957
921
|
'stale lock cleared.');
|
|
958
922
|
}
|
|
959
923
|
const searched = this.defaultProjectHint ?? process.cwd();
|
|
960
|
-
throw new NotIndexedError('No
|
|
961
|
-
`Searched for a .
|
|
924
|
+
throw new NotIndexedError('No LatticeSensor project is loaded for this session.\n' +
|
|
925
|
+
`Searched for a .lattice/sensor/ directory starting from: ${searched}\n` +
|
|
962
926
|
'Either the server root has no index of its own (e.g. a monorepo where only ' +
|
|
963
927
|
"sub-projects are indexed), or the MCP client launched the server outside your " +
|
|
964
928
|
'project without reporting the workspace root. Either way, target the project ' +
|
|
965
929
|
'explicitly:\n' +
|
|
966
930
|
' • Pass projectPath to the tool call, e.g. projectPath: "/absolute/path/to/your/project" ' +
|
|
967
|
-
'(any project that has a .
|
|
931
|
+
'(any project that has a .lattice/sensor/ — including a sub-project of a monorepo)\n' +
|
|
968
932
|
' • Or add --path to the server\'s MCP config args: ["serve", "--mcp", "--path", "/absolute/path/to/your/project"]\n' +
|
|
969
933
|
'If a project simply has no index, use your built-in tools (Read/Grep/Glob) for THAT ' +
|
|
970
|
-
"project (the user can run '
|
|
934
|
+
"project (the user can run 'lattice sensor init . --json' there to enable it) — you can still query " +
|
|
971
935
|
'other indexed projects by projectPath in the same session.');
|
|
972
936
|
}
|
|
973
937
|
return this.freshen(this.cg);
|
|
974
938
|
}
|
|
975
939
|
// Reject sensitive system directories before opening. Only validate a
|
|
976
940
|
// path that actually exists — a nested or not-yet-created sub-path of a
|
|
977
|
-
// real project must still be allowed to resolve UP to its .
|
|
941
|
+
// real project must still be allowed to resolve UP to its .lattice/sensor/
|
|
978
942
|
// root below (issue #238), so we don't run the existence-checking
|
|
979
943
|
// validator on paths that are meant to walk up.
|
|
980
944
|
if ((0, fs_1.existsSync)(projectPath)) {
|
|
@@ -983,22 +947,22 @@ class ToolHandler {
|
|
|
983
947
|
throw new PathRefusalError(pathError);
|
|
984
948
|
}
|
|
985
949
|
}
|
|
986
|
-
// Always RE-RESOLVE the nearest .
|
|
950
|
+
// Always RE-RESOLVE the nearest .lattice/sensor/ from the input path. The walk
|
|
987
951
|
// is cheap (a few existsSync up the tree) and is the only thing that
|
|
988
952
|
// notices a path whose index root CHANGED since it was first seen — most
|
|
989
|
-
// importantly a git worktree that gained its own .
|
|
953
|
+
// importantly a git worktree that gained its own .lattice/sensor/ after the
|
|
990
954
|
// (long-lived) server first resolved it up to the parent checkout. We used
|
|
991
955
|
// to short-circuit on a `projectCache[projectPath]` entry before resolving,
|
|
992
956
|
// which pinned that first resolution for the server's whole lifetime, so a
|
|
993
957
|
// worktree kept being served the parent checkout's index until restart
|
|
994
958
|
// (#926). The DB connection itself is still cached (by resolved root,
|
|
995
959
|
// below), so re-resolving costs only the stat walk, never a reopen.
|
|
996
|
-
const resolvedRoot = (0, directory_1.
|
|
960
|
+
const resolvedRoot = (0, directory_1.findNearestLatticeSensorRoot)(projectPath);
|
|
997
961
|
if (!resolvedRoot) {
|
|
998
|
-
throw new NotIndexedError(`The project at ${projectPath} isn't indexed with
|
|
999
|
-
'walking up from it), so
|
|
1000
|
-
"for that codebase instead, and don't call
|
|
1001
|
-
"Indexing is the user's decision — they can run '
|
|
962
|
+
throw new NotIndexedError(`The project at ${projectPath} isn't indexed with lattice sensor (no .lattice/sensor/ directory found ` +
|
|
963
|
+
'walking up from it), so lattice sensor cannot query it. Use your built-in tools (Read/Grep/Glob) ' +
|
|
964
|
+
"for that codebase instead, and don't call lattice sensor for it again this session. " +
|
|
965
|
+
"Indexing is the user's decision — they can run 'lattice sensor init . --json' in that project to enable it.");
|
|
1002
966
|
}
|
|
1003
967
|
// If the path resolves to the default project, reuse the already-open
|
|
1004
968
|
// default instance rather than opening a SECOND connection to the same DB.
|
|
@@ -1016,13 +980,13 @@ class ToolHandler {
|
|
|
1016
980
|
const cached = this.projectCache.get(resolvedRoot);
|
|
1017
981
|
if (cached)
|
|
1018
982
|
return this.freshen(cached);
|
|
1019
|
-
const cg =
|
|
983
|
+
const cg = loadLatticeSensor().openSync(resolvedRoot);
|
|
1020
984
|
this.projectCache.set(resolvedRoot, cg);
|
|
1021
985
|
return cg;
|
|
1022
986
|
}
|
|
1023
987
|
/**
|
|
1024
|
-
* Heal a long-lived connection whose `.
|
|
1025
|
-
* at the same path (a worktree recreated, or `rm -rf .
|
|
988
|
+
* Heal a long-lived connection whose `.lattice/sensor/` was removed and recreated
|
|
989
|
+
* at the same path (a worktree recreated, or `rm -rf .lattice/sensor` + re-init)
|
|
1026
990
|
* before handing it to a tool. Otherwise the daemon keeps serving the
|
|
1027
991
|
* pre-removal snapshot from its now-unlinked file handle until restart — and
|
|
1028
992
|
* because the daemon registry is keyed by path, a same-path recreate routes
|
|
@@ -1033,7 +997,7 @@ class ToolHandler {
|
|
|
1033
997
|
freshen(cg) {
|
|
1034
998
|
try {
|
|
1035
999
|
if (cg.reopenIfReplaced()) {
|
|
1036
|
-
process.stderr.write('[
|
|
1000
|
+
process.stderr.write('[LatticeSensor MCP] The index was replaced on disk (e.g. a git worktree ' +
|
|
1037
1001
|
'recreated at the same path); reopened the live database in place.\n');
|
|
1038
1002
|
}
|
|
1039
1003
|
}
|
|
@@ -1099,7 +1063,7 @@ class ToolHandler {
|
|
|
1099
1063
|
const startPath = projectPath ?? this.defaultProjectHint ?? process.cwd();
|
|
1100
1064
|
// The verdict depends on BOTH the start path AND the index root it resolves
|
|
1101
1065
|
// to, so the cache must be keyed on the pair. Resolve the index root first
|
|
1102
|
-
// (cheap —
|
|
1066
|
+
// (cheap — getLatticeSensor re-walks to the nearest .lattice/sensor/, no git), then
|
|
1103
1067
|
// key on `(startPath, indexRoot)`. The moment that root changes — most
|
|
1104
1068
|
// importantly when a git worktree gains its own index and the walk-up stops
|
|
1105
1069
|
// there instead of at the parent checkout — the key changes and the verdict
|
|
@@ -1108,7 +1072,7 @@ class ToolHandler {
|
|
|
1108
1072
|
// that first verdict until restart (#926).
|
|
1109
1073
|
let indexRoot;
|
|
1110
1074
|
try {
|
|
1111
|
-
indexRoot = this.
|
|
1075
|
+
indexRoot = this.getLatticeSensor(projectPath).getProjectRoot();
|
|
1112
1076
|
}
|
|
1113
1077
|
catch {
|
|
1114
1078
|
// No resolvable project (or any other resolution error) → nothing to warn.
|
|
@@ -1127,7 +1091,7 @@ class ToolHandler {
|
|
|
1127
1091
|
* notice when the resolved index belongs to a different git working tree than
|
|
1128
1092
|
* the caller's (issue #155). Without this, an agent in a nested worktree
|
|
1129
1093
|
* silently trusts main-branch results. No-op on error results and when there
|
|
1130
|
-
* is no mismatch. `
|
|
1094
|
+
* is no mismatch. `lattice_sensor_status` is excluded — it embeds its own verbose
|
|
1131
1095
|
* warning — so it stays out of this path.
|
|
1132
1096
|
*/
|
|
1133
1097
|
withWorktreeNotice(result, projectPath) {
|
|
@@ -1161,12 +1125,12 @@ class ToolHandler {
|
|
|
1161
1125
|
return result;
|
|
1162
1126
|
let cg;
|
|
1163
1127
|
try {
|
|
1164
|
-
cg = this.
|
|
1128
|
+
cg = this.getLatticeSensor(projectPath);
|
|
1165
1129
|
}
|
|
1166
1130
|
catch {
|
|
1167
1131
|
return result; // no default project — leave as is
|
|
1168
1132
|
}
|
|
1169
|
-
// Cross-project `projectPath` calls open a cached
|
|
1133
|
+
// Cross-project `projectPath` calls open a cached LatticeSensor WITHOUT a
|
|
1170
1134
|
// watcher (watchers are only attached to the default session project).
|
|
1171
1135
|
// When the cross-project path happens to be the same project as the
|
|
1172
1136
|
// default cg, the cached instance is the wrong one — its pendingFiles is
|
|
@@ -1188,7 +1152,7 @@ class ToolHandler {
|
|
|
1188
1152
|
// fire — but the index is now FROZEN and silently drifting stale. Surface
|
|
1189
1153
|
// one global notice instead, so the agent Reads for current content rather
|
|
1190
1154
|
// than trusting a response off a no-longer-updating index. (Cross-project
|
|
1191
|
-
// calls open a watcher-less
|
|
1155
|
+
// calls open a watcher-less LatticeSensor, so this is false there — correct: we
|
|
1192
1156
|
// only know degraded state for the default session project.)
|
|
1193
1157
|
let degraded = false;
|
|
1194
1158
|
try {
|
|
@@ -1211,7 +1175,7 @@ class ToolHandler {
|
|
|
1211
1175
|
const composed = `${formatDegradedBanner(reason)}\n\n${head.text}`;
|
|
1212
1176
|
return { ...result, content: [{ type: 'text', text: composed }, ...tail] };
|
|
1213
1177
|
}
|
|
1214
|
-
// Defensive: some test fakes inject a partial
|
|
1178
|
+
// Defensive: some test fakes inject a partial LatticeSensor stub without the
|
|
1215
1179
|
// newer pending-files API. Treat missing/throwing as "no pending files."
|
|
1216
1180
|
let pending = [];
|
|
1217
1181
|
try {
|
|
@@ -1230,7 +1194,7 @@ class ToolHandler {
|
|
|
1230
1194
|
const elsewhere = [];
|
|
1231
1195
|
for (const p of pending) {
|
|
1232
1196
|
// Substring match against the project-relative POSIX path — that's
|
|
1233
|
-
// exactly the format both the watcher and every
|
|
1197
|
+
// exactly the format both the watcher and every lattice sensor response
|
|
1234
1198
|
// emit, so a plain includes() is sufficient and avoids regex pitfalls.
|
|
1235
1199
|
if (text.includes(p.path))
|
|
1236
1200
|
inResponse.push(p);
|
|
@@ -1268,10 +1232,10 @@ class ToolHandler {
|
|
|
1268
1232
|
this.catchUpGate = null;
|
|
1269
1233
|
await this.awaitCatchUpGate(gate);
|
|
1270
1234
|
}
|
|
1271
|
-
// Honor the optional tool allowlist (
|
|
1235
|
+
// Honor the optional tool allowlist (LATTICE_SENSOR_MCP_TOOLS): a trimmed
|
|
1272
1236
|
// surface rejects ablated tools defensively even if a client cached them.
|
|
1273
1237
|
if (!this.isToolAllowed(toolName)) {
|
|
1274
|
-
return this.errorResult(`Tool ${toolName} is disabled via
|
|
1238
|
+
return this.errorResult(`Tool ${toolName} is disabled via LATTICE_SENSOR_MCP_TOOLS`);
|
|
1275
1239
|
}
|
|
1276
1240
|
// Cross-cutting input validation. All tools accept an optional
|
|
1277
1241
|
// `projectPath` and most accept either `query`, `task`, or
|
|
@@ -1281,7 +1245,7 @@ class ToolHandler {
|
|
|
1281
1245
|
if (typeof pathCheck === 'object' && pathCheck !== undefined) {
|
|
1282
1246
|
return pathCheck;
|
|
1283
1247
|
}
|
|
1284
|
-
// The `path` and `pattern` properties used by
|
|
1248
|
+
// The `path` and `pattern` properties used by lattice_sensor_files are
|
|
1285
1249
|
// also path-shaped — apply the same cap.
|
|
1286
1250
|
if (args.path !== undefined) {
|
|
1287
1251
|
const check = this.validateOptionalPath(args.path, 'path');
|
|
@@ -1293,12 +1257,12 @@ class ToolHandler {
|
|
|
1293
1257
|
if (typeof check === 'object' && check !== undefined)
|
|
1294
1258
|
return check;
|
|
1295
1259
|
}
|
|
1296
|
-
//
|
|
1260
|
+
// lattice_sensor_status reports watcher state (pending files, degraded mode,
|
|
1297
1261
|
// worktree warning) and embeds its own sections — it must run on the MAIN
|
|
1298
1262
|
// thread against the watched default instance, so it is NEVER off-loaded to
|
|
1299
1263
|
// a worker (whose read connection has no watcher). It also skips the
|
|
1300
1264
|
// auto-banner wrapper to avoid duplicating its own pending-files section.
|
|
1301
|
-
if (toolName === '
|
|
1265
|
+
if (toolName === 'lattice_sensor_status') {
|
|
1302
1266
|
return await this.handleStatus(args);
|
|
1303
1267
|
}
|
|
1304
1268
|
// Read tools: off-load the CPU-heavy dispatch to the worker pool when one
|
|
@@ -1333,8 +1297,8 @@ class ToolHandler {
|
|
|
1333
1297
|
return this.errorResult(err.message);
|
|
1334
1298
|
}
|
|
1335
1299
|
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
1336
|
-
'This is an internal
|
|
1337
|
-
'continue without
|
|
1300
|
+
'This is an internal lattice sensor error — retry the call once; if it persists, ' +
|
|
1301
|
+
'continue without lattice sensor for this task.');
|
|
1338
1302
|
}
|
|
1339
1303
|
}
|
|
1340
1304
|
/**
|
|
@@ -1362,36 +1326,36 @@ class ToolHandler {
|
|
|
1362
1326
|
return this.errorResult(err.message);
|
|
1363
1327
|
}
|
|
1364
1328
|
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
1365
|
-
'This is an internal
|
|
1366
|
-
'continue without
|
|
1329
|
+
'This is an internal lattice sensor error — retry the call once; if it persists, ' +
|
|
1330
|
+
'continue without lattice sensor for this task.');
|
|
1367
1331
|
}
|
|
1368
1332
|
}
|
|
1369
1333
|
/**
|
|
1370
1334
|
* Pure dispatch over the read tools — the switch, with no gate, no notices, no
|
|
1371
|
-
* allowlist/validation (the caller owns those). `
|
|
1335
|
+
* allowlist/validation (the caller owns those). `lattice_sensor_status` is handled
|
|
1372
1336
|
* on the main thread in {@link execute} and never reaches here. May throw
|
|
1373
1337
|
* NotIndexed/PathRefusal, which {@link executeReadTool} classifies.
|
|
1374
1338
|
*/
|
|
1375
1339
|
async dispatchTool(toolName, args) {
|
|
1376
1340
|
switch (toolName) {
|
|
1377
|
-
case '
|
|
1378
|
-
case '
|
|
1379
|
-
case '
|
|
1380
|
-
case '
|
|
1381
|
-
case '
|
|
1382
|
-
case '
|
|
1383
|
-
case '
|
|
1341
|
+
case 'lattice_sensor_search': return await this.handleSearch(args);
|
|
1342
|
+
case 'lattice_sensor_callers': return await this.handleCallers(args);
|
|
1343
|
+
case 'lattice_sensor_callees': return await this.handleCallees(args);
|
|
1344
|
+
case 'lattice_sensor_impact': return await this.handleImpact(args);
|
|
1345
|
+
case 'lattice_sensor_explore': return await this.handleExplore(args);
|
|
1346
|
+
case 'lattice_sensor_node': return await this.handleNode(args);
|
|
1347
|
+
case 'lattice_sensor_files': return await this.handleFiles(args);
|
|
1384
1348
|
default: return this.errorResult(`Unknown tool: ${toolName}`);
|
|
1385
1349
|
}
|
|
1386
1350
|
}
|
|
1387
1351
|
/**
|
|
1388
|
-
* Handle
|
|
1352
|
+
* Handle lattice_sensor_search
|
|
1389
1353
|
*/
|
|
1390
1354
|
async handleSearch(args) {
|
|
1391
1355
|
const query = this.validateString(args.query, 'query');
|
|
1392
1356
|
if (typeof query !== 'string')
|
|
1393
1357
|
return query;
|
|
1394
|
-
const cg = this.
|
|
1358
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
1395
1359
|
const rawKind = args.kind;
|
|
1396
1360
|
// The schema enum says 'type' (what agents naturally reach for); the
|
|
1397
1361
|
// NodeKind is 'type_alias'. Without the mapping, kind: "type" silently
|
|
@@ -1455,13 +1419,13 @@ class ToolHandler {
|
|
|
1455
1419
|
return `**${head.qualifiedName}** (${head.kind}) — ${head.filePath}${line}`;
|
|
1456
1420
|
}
|
|
1457
1421
|
/**
|
|
1458
|
-
* Handle
|
|
1422
|
+
* Handle lattice_sensor_callers
|
|
1459
1423
|
*/
|
|
1460
1424
|
async handleCallers(args) {
|
|
1461
1425
|
const symbol = this.validateString(args.symbol, 'symbol');
|
|
1462
1426
|
if (typeof symbol !== 'string')
|
|
1463
1427
|
return symbol;
|
|
1464
|
-
const cg = this.
|
|
1428
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
1465
1429
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
1466
1430
|
const fileFilter = typeof args.file === 'string' ? args.file : undefined;
|
|
1467
1431
|
const allMatches = this.findAllSymbols(cg, symbol);
|
|
@@ -1523,13 +1487,13 @@ class ToolHandler {
|
|
|
1523
1487
|
return this.textResult(this.truncateOutput(lines.join('\n') + filterNote));
|
|
1524
1488
|
}
|
|
1525
1489
|
/**
|
|
1526
|
-
* Handle
|
|
1490
|
+
* Handle lattice_sensor_callees
|
|
1527
1491
|
*/
|
|
1528
1492
|
async handleCallees(args) {
|
|
1529
1493
|
const symbol = this.validateString(args.symbol, 'symbol');
|
|
1530
1494
|
if (typeof symbol !== 'string')
|
|
1531
1495
|
return symbol;
|
|
1532
|
-
const cg = this.
|
|
1496
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
1533
1497
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
1534
1498
|
const fileFilter = typeof args.file === 'string' ? args.file : undefined;
|
|
1535
1499
|
const allMatches = this.findAllSymbols(cg, symbol);
|
|
@@ -1588,13 +1552,13 @@ class ToolHandler {
|
|
|
1588
1552
|
return this.textResult(this.truncateOutput(lines.join('\n') + filterNote));
|
|
1589
1553
|
}
|
|
1590
1554
|
/**
|
|
1591
|
-
* Handle
|
|
1555
|
+
* Handle lattice_sensor_impact
|
|
1592
1556
|
*/
|
|
1593
1557
|
async handleImpact(args) {
|
|
1594
1558
|
const symbol = this.validateString(args.symbol, 'symbol');
|
|
1595
1559
|
if (typeof symbol !== 'string')
|
|
1596
1560
|
return symbol;
|
|
1597
|
-
const cg = this.
|
|
1561
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
1598
1562
|
const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
|
|
1599
1563
|
const fileFilter = typeof args.file === 'string' ? args.file : undefined;
|
|
1600
1564
|
const allMatches = this.findAllSymbols(cg, symbol);
|
|
@@ -1735,7 +1699,7 @@ class ToolHandler {
|
|
|
1735
1699
|
return null;
|
|
1736
1700
|
}
|
|
1737
1701
|
/**
|
|
1738
|
-
* Flow-from-named-symbols: an agent's
|
|
1702
|
+
* Flow-from-named-symbols: an agent's lattice_sensor_explore query is a bag of
|
|
1739
1703
|
* symbol names that usually spans the flow it's investigating (e.g.
|
|
1740
1704
|
* "PmsProductController getList PmsProductService list PmsProductServiceImpl").
|
|
1741
1705
|
* Surface the longest call chain AMONG those named symbols — scoped to what the
|
|
@@ -2115,7 +2079,7 @@ class ToolHandler {
|
|
|
2115
2079
|
'',
|
|
2116
2080
|
...notes,
|
|
2117
2081
|
'',
|
|
2118
|
-
'> These sites choose their call target at runtime (registry / bus / reflection) — the site shown IS where the flow continues. To follow it, run
|
|
2082
|
+
'> These sites choose their call target at runtime (registry / bus / reflection) — the site shown IS where the flow continues. To follow it, run lattice_sensor_explore or lattice_sensor_node on a candidate; source for the sites above is included below.',
|
|
2119
2083
|
'',
|
|
2120
2084
|
].join('\n');
|
|
2121
2085
|
}
|
|
@@ -2129,7 +2093,7 @@ class ToolHandler {
|
|
|
2129
2093
|
* the concrete target is chosen at runtime from N implementations, so no single
|
|
2130
2094
|
* static edge is "the answer" — the implementations ARE the continuations. We
|
|
2131
2095
|
* announce the supertype, its TRUE implementer count, and a few concrete targets,
|
|
2132
|
-
* then steer to
|
|
2096
|
+
* then steer to lattice_sensor_explore. Graph-only, query-time, zero mutation; the
|
|
2133
2097
|
* caller fires it ONLY for an UNCOVERED named token, so a connected flow is silent.
|
|
2134
2098
|
*
|
|
2135
2099
|
* Robust to FTS sampling bias: the same-name family is a capped FTS sample that
|
|
@@ -2222,7 +2186,7 @@ class ToolHandler {
|
|
|
2222
2186
|
'',
|
|
2223
2187
|
...notes,
|
|
2224
2188
|
'',
|
|
2225
|
-
'> The method above is dispatched at runtime to one of the listed implementations (a registry / plugin / strategy interface) — there is no single static caller→callee edge; the implementations ARE the continuations. To follow one, run
|
|
2189
|
+
'> The method above is dispatched at runtime to one of the listed implementations (a registry / plugin / strategy interface) — there is no single static caller→callee edge; the implementations ARE the continuations. To follow one, run lattice_sensor_explore on a listed target.',
|
|
2226
2190
|
'',
|
|
2227
2191
|
].join('\n');
|
|
2228
2192
|
}
|
|
@@ -2375,7 +2339,7 @@ class ToolHandler {
|
|
|
2375
2339
|
* PageRank) from the query's matched SEED nodes over the call/reference graph.
|
|
2376
2340
|
*
|
|
2377
2341
|
* This is the ranking signal text search (FTS/bm25) CANNOT provide, and it's
|
|
2378
|
-
*
|
|
2342
|
+
* lattice sensor's home turf: relevance by STRUCTURE, not words. A file whose
|
|
2379
2343
|
* symbols are call-connected to the matched cluster accrues walk mass and
|
|
2380
2344
|
* ranks high; a lone TEXT match — e.g. `LensSwitcher.swift` matched the word
|
|
2381
2345
|
* "switch" from `switchOrganization`, but calls none of `setUser`/`fetchUser`
|
|
@@ -2453,11 +2417,11 @@ class ToolHandler {
|
|
|
2453
2417
|
return out;
|
|
2454
2418
|
}
|
|
2455
2419
|
/**
|
|
2456
|
-
* Handle
|
|
2420
|
+
* Handle lattice_sensor_explore — deep exploration in a single call
|
|
2457
2421
|
*
|
|
2458
2422
|
* Strategy: find relevant symbols via graph traversal, group by file,
|
|
2459
2423
|
* then read contiguous file sections covering all symbols per file.
|
|
2460
|
-
* This replaces multiple
|
|
2424
|
+
* This replaces multiple lattice_sensor_node + Read calls.
|
|
2461
2425
|
*
|
|
2462
2426
|
* Output size is adaptive to project file count via
|
|
2463
2427
|
* `getExploreOutputBudget` — see #185 for why a fixed 35k cap was a
|
|
@@ -2470,7 +2434,7 @@ class ToolHandler {
|
|
|
2470
2434
|
// One normalization point so the flow-builder, relevance search, and
|
|
2471
2435
|
// ranking all see the same canonical spelling (Erlang `mod:fn/arity`).
|
|
2472
2436
|
const query = normalizeQuerySpelling(rawQuery);
|
|
2473
|
-
const cg = this.
|
|
2437
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
2474
2438
|
const projectRoot = cg.getProjectRoot();
|
|
2475
2439
|
// Resolve adaptive output budget from project size. Falls back to the
|
|
2476
2440
|
// largest-tier defaults if stats aren't available, which preserves
|
|
@@ -2615,7 +2579,7 @@ class ToolHandler {
|
|
|
2615
2579
|
// 50+-overload name (tokio `poll`) ranks the wanted def (`Harness::poll`)
|
|
2616
2580
|
// below the FTS cut, so findAllSymbols would never see it and the
|
|
2617
2581
|
// type-token bias below couldn't pick the harness.rs one. (Same fix as
|
|
2618
|
-
//
|
|
2582
|
+
// lattice_sensor_node's findSymbolMatches.) Qualified tokens keep findAllSymbols.
|
|
2619
2583
|
const isQual = /[.\/]|::/.test(t);
|
|
2620
2584
|
const raw = isQual ? this.findAllSymbols(cg, t).nodes : cg.getNodesByName(t);
|
|
2621
2585
|
let cands = raw
|
|
@@ -2661,7 +2625,7 @@ class ToolHandler {
|
|
|
2661
2625
|
// only: the overloads whose file/class the query ALSO names (the agent
|
|
2662
2626
|
// told us which one it wants — DataRequest's, not Validation.swift's),
|
|
2663
2627
|
// capped; else fall back to the single most-substantive def. This is the
|
|
2664
|
-
// explore-side mirror of
|
|
2628
|
+
// explore-side mirror of lattice_sensor_node's overload disambiguation.
|
|
2665
2629
|
let picks;
|
|
2666
2630
|
let tierPicks; // subset that earns the named-first tier (#1064)
|
|
2667
2631
|
if (cands.length <= 3) {
|
|
@@ -2949,9 +2913,9 @@ class ToolHandler {
|
|
|
2949
2913
|
// neither entry nor central (a type/util file that matches "element"+x but isn't
|
|
2950
2914
|
// the flow) is NOT promoted, so it can't displace the graph-central answer file
|
|
2951
2915
|
// (hits=1) the way a blunt hits-only tier would. Single-layer repos with one
|
|
2952
|
-
// cluster are unaffected (no competing mass). Set
|
|
2916
|
+
// cluster are unaffected (no competing mass). Set LATTICE_SENSOR_RANK_NO_MULTITERM=1
|
|
2953
2917
|
// to disable.
|
|
2954
|
-
const MULTITERM_OFF = process.env.
|
|
2918
|
+
const MULTITERM_OFF = process.env.LATTICE_SENSOR_RANK_NO_MULTITERM === '1';
|
|
2955
2919
|
const isCorroborated = (fp) => !MULTITERM_OFF &&
|
|
2956
2920
|
(fileTermHits.get(fp) ?? 0) >= 2 &&
|
|
2957
2921
|
(entryFiles.has(fp) || centralFiles.has(fp));
|
|
@@ -3139,7 +3103,7 @@ class ToolHandler {
|
|
|
3139
3103
|
}
|
|
3140
3104
|
const fileLines = fileContent.split('\n');
|
|
3141
3105
|
const lang = group.nodes[0]?.language || '';
|
|
3142
|
-
// Adaptive sizing (
|
|
3106
|
+
// Adaptive sizing (LATTICE_SENSOR_ADAPTIVE_EXPLORE, default on): collapse a file
|
|
3143
3107
|
// to a per-symbol view when it's a redundant member of a polymorphic family.
|
|
3144
3108
|
// Engages iff ALL hold:
|
|
3145
3109
|
// 1. a flow spine exists,
|
|
@@ -3254,15 +3218,15 @@ class ToolHandler {
|
|
|
3254
3218
|
if (skel.length > 0) {
|
|
3255
3219
|
const names = [...new Set(group.nodes.filter(n => n.kind !== 'import' && n.kind !== 'export').map(n => n.name))]
|
|
3256
3220
|
.slice(0, budget.maxSymbolsInFileHeader).join(', ');
|
|
3257
|
-
// Steer the agent to
|
|
3221
|
+
// Steer the agent to lattice_sensor_explore for an elided body — NEVER to
|
|
3258
3222
|
// Read. The old "Read for more" / "Read for a full body" tags invited
|
|
3259
3223
|
// a Read of the very file just skeletonized; on a central, wanted file
|
|
3260
3224
|
// (Session.swift, DataRequest.swift) that fired an over-investigation
|
|
3261
3225
|
// spiral (the agent Read the skeletonized file, then kept digging).
|
|
3262
3226
|
// CLAUDE.md: explore output must never tell the agent to Read.
|
|
3263
3227
|
const tag = bodyIds.size > 0
|
|
3264
|
-
? 'focused (the methods you named in full, the rest as signatures —
|
|
3265
|
-
: 'skeleton (signatures only —
|
|
3228
|
+
? 'focused (the methods you named in full, the rest as signatures — lattice_sensor_explore a signature by name for its body; do NOT Read)'
|
|
3229
|
+
: 'skeleton (signatures only — lattice_sensor_explore a name for its full body; do NOT Read)';
|
|
3266
3230
|
lines.push(fileSectionHeader(filePath, `${names} · ${tag}`), '', '```' + lang, skel.join('\n'), '```', '');
|
|
3267
3231
|
totalChars += skel.join('\n').length + 120;
|
|
3268
3232
|
renderedFilePaths.push(filePath);
|
|
@@ -3637,11 +3601,11 @@ class ToolHandler {
|
|
|
3637
3601
|
if (budget.includeCompletenessSignal) {
|
|
3638
3602
|
lines.push('');
|
|
3639
3603
|
lines.push('---');
|
|
3640
|
-
lines.push(`> **Complete source for ${filesIncluded} files is included above — do NOT re-read them.** If your question also needs files/symbols listed under "Not shown above" (or any area this call didn't cover), make ANOTHER
|
|
3604
|
+
lines.push(`> **Complete source for ${filesIncluded} files is included above — do NOT re-read them.** If your question also needs files/symbols listed under "Not shown above" (or any area this call didn't cover), make ANOTHER lattice_sensor_explore targeting those names — it returns the same source with line numbers and is cheaper and more complete than reading. Reserve Read for a single specific line range explore can't surface.`);
|
|
3641
3605
|
}
|
|
3642
3606
|
else if (anyFileTrimmed) {
|
|
3643
3607
|
lines.push('');
|
|
3644
|
-
lines.push(`> Some file sections were trimmed for size. For a specific symbol you still need, run another \`
|
|
3608
|
+
lines.push(`> Some file sections were trimmed for size. For a specific symbol you still need, run another \`lattice_sensor_explore\` (or \`lattice_sensor_node\`) with its exact name — line-numbered source, cheaper and more complete than Read.`);
|
|
3645
3609
|
}
|
|
3646
3610
|
// Add explore budget note based on project size
|
|
3647
3611
|
if (budget.includeBudgetNote) {
|
|
@@ -3676,7 +3640,7 @@ class ToolHandler {
|
|
|
3676
3640
|
const lastSection = cut.lastIndexOf('\n' + FILE_SECTION_PREFIX);
|
|
3677
3641
|
const boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
|
|
3678
3642
|
const safe = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
3679
|
-
finalText = safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For any area not covered, run another
|
|
3643
|
+
finalText = safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For any area not covered, run another lattice_sensor_explore with the specific names — do NOT Read these files.)';
|
|
3680
3644
|
}
|
|
3681
3645
|
else {
|
|
3682
3646
|
finalText = output;
|
|
@@ -3702,10 +3666,10 @@ class ToolHandler {
|
|
|
3702
3666
|
return this.textResult(finalText);
|
|
3703
3667
|
}
|
|
3704
3668
|
/**
|
|
3705
|
-
* Handle
|
|
3669
|
+
* Handle lattice_sensor_node
|
|
3706
3670
|
*/
|
|
3707
3671
|
async handleNode(args) {
|
|
3708
|
-
const cg = this.
|
|
3672
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
3709
3673
|
// Default to false to minimize context usage
|
|
3710
3674
|
const includeCode = args.includeCode === true;
|
|
3711
3675
|
const fileHint = typeof args.file === 'string' && args.file.trim() ? args.file.trim() : undefined;
|
|
@@ -3761,7 +3725,7 @@ class ToolHandler {
|
|
|
3761
3725
|
// different types (Alamofire `didCompleteTask`/`task`/`validate`, gin
|
|
3762
3726
|
// `reset`). Returning ONE forces the agent to guess, and when it guesses
|
|
3763
3727
|
// wrong it READS the file to find the right overload — the dominant
|
|
3764
|
-
//
|
|
3728
|
+
// lattice_sensor_node read cause on Swift/Go. So return them ALL: pack as many
|
|
3765
3729
|
// FULL bodies as fit a char budget (the agent gets the one it needs in this
|
|
3766
3730
|
// one call, no follow-up parameter to learn), and list any remainder by
|
|
3767
3731
|
// file:line so a large overload set can't overflow the per-tool cap.
|
|
@@ -3806,7 +3770,7 @@ class ToolHandler {
|
|
|
3806
3770
|
out.push('', '**Other definitions**', ...shownList.map((n) => `- \`${n.name}\` (${n.kind}) — ${n.filePath}:${n.startLine}`));
|
|
3807
3771
|
if (listed.length > LIST_CAP)
|
|
3808
3772
|
out.push(`- … +${listed.length - LIST_CAP} more`);
|
|
3809
|
-
out.push('', `> Need one of these in full? Call
|
|
3773
|
+
out.push('', `> Need one of these in full? Call lattice_sensor_node again with \`file\` (e.g. \`"${listed[0].filePath.split('/').pop()}"\`) or \`line\` — do NOT Read it.`);
|
|
3810
3774
|
}
|
|
3811
3775
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
3812
3776
|
}
|
|
@@ -3828,7 +3792,7 @@ class ToolHandler {
|
|
|
3828
3792
|
const wantLower = normalize(fileArg).toLowerCase();
|
|
3829
3793
|
const allFiles = cg.getFiles();
|
|
3830
3794
|
if (allFiles.length === 0)
|
|
3831
|
-
return this.textResult('No files indexed. Run `
|
|
3795
|
+
return this.textResult('No files indexed. Run `lattice sensor index` first.');
|
|
3832
3796
|
let resolved = allFiles.find((f) => f.path.toLowerCase() === wantLower);
|
|
3833
3797
|
let candidates = [];
|
|
3834
3798
|
if (!resolved) {
|
|
@@ -3846,14 +3810,14 @@ class ToolHandler {
|
|
|
3846
3810
|
...candidates.slice(0, 25).map((f) => `- ${f.path}`)].join('\n'));
|
|
3847
3811
|
}
|
|
3848
3812
|
if (!resolved) {
|
|
3849
|
-
return this.textResult(`No indexed file matches "${fileArg}".
|
|
3813
|
+
return this.textResult(`No indexed file matches "${fileArg}". Lattice sensor indexes source files; configs/docs it doesn't parse won't appear — Read those directly.`);
|
|
3850
3814
|
}
|
|
3851
3815
|
const filePath = resolved.path;
|
|
3852
3816
|
const nodes = cg.getNodesInFile(filePath)
|
|
3853
3817
|
.filter((n) => n.kind !== 'file' && n.kind !== 'import' && n.kind !== 'export')
|
|
3854
3818
|
.sort((a, b) => a.startLine - b.startLine);
|
|
3855
3819
|
const dependents = cg.getFileDependents(filePath);
|
|
3856
|
-
// Compact, one-line blast radius (
|
|
3820
|
+
// Compact, one-line blast radius (lattice sensor's value-add over a plain Read).
|
|
3857
3821
|
const depSummary = dependents.length
|
|
3858
3822
|
? `used by ${dependents.length} file${dependents.length === 1 ? '' : 's'}: ${dependents.slice(0, 8).join(', ')}${dependents.length > 8 ? `, +${dependents.length - 8} more` : ''}`
|
|
3859
3823
|
: 'no other indexed file depends on it';
|
|
@@ -3884,7 +3848,7 @@ class ToolHandler {
|
|
|
3884
3848
|
const out = [`**${filePath}** — configuration/data file, ${depSummary}`, ''];
|
|
3885
3849
|
if (nodes.length)
|
|
3886
3850
|
out.push(...symbolMap('**Keys (values withheld for safety)**'));
|
|
3887
|
-
out.push('', '> Values may be secrets, so
|
|
3851
|
+
out.push('', '> Values may be secrets, so lattice sensor indexes keys only. Read the file directly if you need a value.');
|
|
3888
3852
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
3889
3853
|
}
|
|
3890
3854
|
// Read the current bytes from disk through the security chokepoint
|
|
@@ -3939,7 +3903,7 @@ class ToolHandler {
|
|
|
3939
3903
|
const complete = offset === 1 && shownEnd >= total;
|
|
3940
3904
|
const out = [header, '', ...numbered];
|
|
3941
3905
|
if (!complete) {
|
|
3942
|
-
out.push('', `(lines ${offset}–${shownEnd} of ${total} — pass \`offset\`/\`limit\` for another range, or \`
|
|
3906
|
+
out.push('', `(lines ${offset}–${shownEnd} of ${total} — pass \`offset\`/\`limit\` for another range, or \`lattice_sensor_node <symbol>\` for one symbol in full)`);
|
|
3943
3907
|
}
|
|
3944
3908
|
// Self-bounded to CHAR_BUDGET — do NOT route through truncateOutput (15k).
|
|
3945
3909
|
return this.textResult(out.join('\n'));
|
|
@@ -3964,9 +3928,9 @@ class ToolHandler {
|
|
|
3964
3928
|
}
|
|
3965
3929
|
/**
|
|
3966
3930
|
* Build the "trail" for a symbol: its direct callees (what it calls) and
|
|
3967
|
-
* callers (what calls it), each with file:line — so
|
|
3931
|
+
* callers (what calls it), each with file:line — so lattice_sensor_node doubles as
|
|
3968
3932
|
* the structural Grep→Read→expand primitive: a spot PLUS where to go next.
|
|
3969
|
-
* Capped to stay cheap. Walk the graph by calling
|
|
3933
|
+
* Capped to stay cheap. Walk the graph by calling lattice_sensor_node on a trail
|
|
3970
3934
|
* entry; no Read needed for covered hops. Empty edges on a non-leaf often mean
|
|
3971
3935
|
* dynamic dispatch the static graph couldn't resolve — that absence is itself
|
|
3972
3936
|
* a signal (read that one hop) rather than a dead end.
|
|
@@ -3993,7 +3957,7 @@ class ToolHandler {
|
|
|
3993
3957
|
const callers = collect(cg.getCallers(node.id));
|
|
3994
3958
|
if (callees.length === 0 && callers.length === 0)
|
|
3995
3959
|
return '';
|
|
3996
|
-
const lines = ['', '**Trail —
|
|
3960
|
+
const lines = ['', '**Trail — lattice_sensor_node any of these to follow it (no Read needed)**'];
|
|
3997
3961
|
if (callees.length > 0) {
|
|
3998
3962
|
lines.push(`**Calls →** ${callees.slice(0, TRAIL_CAP).map(fmt).join(', ')}${callees.length > TRAIL_CAP ? `, +${callees.length - TRAIL_CAP} more` : ''}`);
|
|
3999
3963
|
}
|
|
@@ -4003,10 +3967,10 @@ class ToolHandler {
|
|
|
4003
3967
|
return lines.join('\n');
|
|
4004
3968
|
}
|
|
4005
3969
|
/**
|
|
4006
|
-
* Handle
|
|
3970
|
+
* Handle lattice_sensor_status
|
|
4007
3971
|
*/
|
|
4008
3972
|
async handleStatus(args) {
|
|
4009
|
-
let cg = this.
|
|
3973
|
+
let cg = this.getLatticeSensor(args.projectPath);
|
|
4010
3974
|
// Same trick as withStalenessNotice — when an explicit projectPath
|
|
4011
3975
|
// resolves to the same project as the default session cg, prefer the
|
|
4012
3976
|
// default so getPendingFiles() (only populated by the default's watcher)
|
|
@@ -4027,7 +3991,7 @@ class ToolHandler {
|
|
|
4027
3991
|
// one-liner via withWorktreeNotice. Both share the cached detection.
|
|
4028
3992
|
const mismatch = this.worktreeMismatchFor(args.projectPath);
|
|
4029
3993
|
const lines = [
|
|
4030
|
-
'**
|
|
3994
|
+
'**Lattice Sensor Status**',
|
|
4031
3995
|
'',
|
|
4032
3996
|
];
|
|
4033
3997
|
if (mismatch) {
|
|
@@ -4040,7 +4004,7 @@ class ToolHandler {
|
|
|
4040
4004
|
// as plain `key: value` lines (not buried in prose) so a caller can parse
|
|
4041
4005
|
// them without depending on the surrounding Markdown.
|
|
4042
4006
|
const executionMode = this.executionMode;
|
|
4043
|
-
lines.push(`**Lattice sensor version:** ${version_1.
|
|
4007
|
+
lines.push('provider: lattice', 'sensor_owner: lattice', `**Lattice sensor version:** ${version_1.LatticeSensorPackageVersion}`, `mode: ${executionMode.mode}`, `reason: ${executionMode.reason}`, '', `**Files indexed:** ${stats.fileCount}`, `**Total nodes:** ${stats.nodeCount}`, `**Total edges:** ${stats.edgeCount}`, `**Database size:** ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`);
|
|
4044
4008
|
// Surface the active SQLite backend (node:sqlite, Node's built-in real
|
|
4045
4009
|
// SQLite — full WAL + FTS5, no native build).
|
|
4046
4010
|
lines.push(`**Backend:** node:sqlite (Node built-in) — full WAL + FTS5`);
|
|
@@ -4067,7 +4031,7 @@ class ToolHandler {
|
|
|
4067
4031
|
if (pendingRefs > 0) {
|
|
4068
4032
|
lines.push(`**Pending resolution:** ⚠ ${pendingRefs} references from an interrupted ` +
|
|
4069
4033
|
`index run — some caller/impact edges are missing until the next sync ` +
|
|
4070
|
-
`(any file change triggers it, or run \`
|
|
4034
|
+
`(any file change triggers it, or run \`lattice sensor sync . --json\`)`);
|
|
4071
4035
|
}
|
|
4072
4036
|
lines.push('', '**Nodes by Kind:**');
|
|
4073
4037
|
for (const [kind, count] of Object.entries(stats.nodesByKind)) {
|
|
@@ -4105,10 +4069,10 @@ class ToolHandler {
|
|
|
4105
4069
|
return this.textResult(lines.join('\n'));
|
|
4106
4070
|
}
|
|
4107
4071
|
/**
|
|
4108
|
-
* Handle
|
|
4072
|
+
* Handle lattice_sensor_files - get project file structure from the index
|
|
4109
4073
|
*/
|
|
4110
4074
|
async handleFiles(args) {
|
|
4111
|
-
const cg = this.
|
|
4075
|
+
const cg = this.getLatticeSensor(args.projectPath);
|
|
4112
4076
|
const pathFilter = args.path;
|
|
4113
4077
|
const pattern = args.pattern;
|
|
4114
4078
|
const format = args.format || 'tree';
|
|
@@ -4117,7 +4081,7 @@ class ToolHandler {
|
|
|
4117
4081
|
// Get all files from the index
|
|
4118
4082
|
const allFiles = cg.getFiles();
|
|
4119
4083
|
if (allFiles.length === 0) {
|
|
4120
|
-
return this.textResult('No files indexed. Run `
|
|
4084
|
+
return this.textResult('No files indexed. Run `lattice sensor index` first.');
|
|
4121
4085
|
}
|
|
4122
4086
|
// Filter by path prefix. Stored paths are project-relative POSIX (e.g.
|
|
4123
4087
|
// "src/foo.ts"), but agents commonly pass project-root variants like "/",
|
|
@@ -4328,7 +4292,7 @@ class ToolHandler {
|
|
|
4328
4292
|
return containerHints.every((hint) => segments.some((seg) => seg === hint || seg.replace(/\.[^.]+$/, '') === hint));
|
|
4329
4293
|
}
|
|
4330
4294
|
/**
|
|
4331
|
-
* Find ALL definitions matching a name, ranked, so
|
|
4295
|
+
* Find ALL definitions matching a name, ranked, so lattice_sensor_node can return
|
|
4332
4296
|
* every overload instead of guessing one (the wrong guess → a Read). Keepers
|
|
4333
4297
|
* rank before generated stubs (.pb.go etc.); stable within a group preserves
|
|
4334
4298
|
* FTS order. Returns [] when nothing matches; a qualified lookup that finds no
|
|
@@ -4548,10 +4512,10 @@ class ToolHandler {
|
|
|
4548
4512
|
lines.push('', node.docstring);
|
|
4549
4513
|
}
|
|
4550
4514
|
if (outline) {
|
|
4551
|
-
lines.push('', outline, '', `> Structural outline only. Read \`${node.filePath}\` or call
|
|
4515
|
+
lines.push('', outline, '', `> Structural outline only. Read \`${node.filePath}\` or call lattice_sensor_node on a specific member for its body.`);
|
|
4552
4516
|
}
|
|
4553
4517
|
else if (code) {
|
|
4554
|
-
// Line-numbered (cat -n style, like
|
|
4518
|
+
// Line-numbered (cat -n style, like lattice_sensor_explore and Read) so the
|
|
4555
4519
|
// agent can cite/edit exact lines without re-Reading the file for them.
|
|
4556
4520
|
const numbered = node.startLine ? numberSourceLines(code, node.startLine) : code;
|
|
4557
4521
|
lines.push('', '```' + node.language, numbered, '```');
|