homegraph 1.5.6 → 1.5.8
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/CHANGELOG.md +53 -0
- package/README.md +12 -6
- package/dist/bin/homegraph.js +16 -14
- package/dist/context/index.js +120 -15
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +11 -25
- package/dist/db/queries.d.ts +5 -1
- package/dist/db/queries.js +31 -2
- package/dist/db/sqlite-adapter.d.ts +6 -12
- package/dist/db/sqlite-adapter.js +13 -41
- package/dist/directory.d.ts +1 -1
- package/dist/directory.js +25 -1
- package/dist/extraction/grammars.d.ts +2 -0
- package/dist/extraction/grammars.js +7 -7
- package/dist/extraction/languages/arkts.d.ts +25 -3
- package/dist/extraction/languages/arkts.js +184 -22
- package/dist/graph/evidence-paths.d.ts +63 -0
- package/dist/graph/evidence-paths.js +245 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +55 -17
- package/dist/mcp/arkts-evidence-packs.d.ts +67 -0
- package/dist/mcp/arkts-evidence-packs.js +352 -0
- package/dist/mcp/daemon.js +1 -1
- package/dist/mcp/engine.d.ts +20 -7
- package/dist/mcp/engine.js +113 -106
- package/dist/mcp/evidence-rendering.d.ts +10 -0
- package/dist/mcp/evidence-rendering.js +71 -0
- package/dist/mcp/explore-repeat-guard.d.ts +8 -13
- package/dist/mcp/explore-repeat-guard.js +54 -114
- package/dist/mcp/explore-session-state.d.ts +26 -4
- package/dist/mcp/explore-session-state.js +50 -4
- package/dist/mcp/index-availability.d.ts +23 -0
- package/dist/mcp/index-availability.js +78 -0
- package/dist/mcp/index.js +5 -4
- package/dist/mcp/query-cache.d.ts +1 -1
- package/dist/mcp/query-cache.js +90 -3
- package/dist/mcp/query-worker.d.ts +1 -1
- package/dist/mcp/query-worker.js +1 -1
- package/dist/mcp/server-instructions.d.ts +4 -9
- package/dist/mcp/server-instructions.js +34 -61
- package/dist/mcp/session.js +12 -4
- package/dist/mcp/source-slice-identity.d.ts +3 -0
- package/dist/mcp/source-slice-identity.js +11 -0
- package/dist/mcp/tools.d.ts +43 -9
- package/dist/mcp/tools.js +859 -155
- package/dist/search/literal-evidence.d.ts +44 -0
- package/dist/search/literal-evidence.js +278 -0
- package/dist/search/query-plan-provider.d.ts +6 -0
- package/dist/search/query-plan-provider.js +289 -0
- package/dist/search/query-plan.d.ts +98 -0
- package/dist/search/query-plan.js +334 -0
- package/dist/search/query-utils.d.ts +6 -1
- package/dist/search/query-utils.js +64 -5
- package/dist/spec/llm/client.js +3 -15
- package/dist/spec/llm/openai-sdk.d.ts +14 -0
- package/dist/spec/llm/openai-sdk.js +37 -0
- package/dist/spec/llm/retry.js +8 -9
- package/dist/types.d.ts +17 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +24 -0
- package/package.json +3 -5
- package/dist/addons/dynamic-import.d.ts.map +0 -1
- package/dist/addons/dynamic-import.js.map +0 -1
- package/dist/addons/init-template.d.ts.map +0 -1
- package/dist/addons/init-template.js.map +0 -1
- package/dist/addons/loader.d.ts.map +0 -1
- package/dist/addons/loader.js.map +0 -1
- package/dist/addons/manager.d.ts.map +0 -1
- package/dist/addons/manager.js.map +0 -1
- package/dist/addons/paths.d.ts.map +0 -1
- package/dist/addons/paths.js.map +0 -1
- package/dist/addons/registry.d.ts.map +0 -1
- package/dist/addons/registry.js.map +0 -1
- package/dist/addons/semver.d.ts.map +0 -1
- package/dist/addons/semver.js.map +0 -1
- package/dist/addons/types.d.ts.map +0 -1
- package/dist/addons/types.js.map +0 -1
- package/dist/addons/validate.d.ts.map +0 -1
- package/dist/addons/validate.js.map +0 -1
- package/dist/arkui/index.d.ts.map +0 -1
- package/dist/arkui/index.js.map +0 -1
- package/dist/arkui/migrate-passage.d.ts.map +0 -1
- package/dist/arkui/migrate-passage.js.map +0 -1
- package/dist/arkui/migrate-semantics.d.ts.map +0 -1
- package/dist/arkui/migrate-semantics.js.map +0 -1
- package/dist/arkui/migrate-snapshot.d.ts.map +0 -1
- package/dist/arkui/migrate-snapshot.js.map +0 -1
- package/dist/bin/addon-commands.d.ts.map +0 -1
- package/dist/bin/addon-commands.js.map +0 -1
- package/dist/bin/command-supervision.d.ts.map +0 -1
- package/dist/bin/command-supervision.js.map +0 -1
- package/dist/bin/fatal-handler.d.ts.map +0 -1
- package/dist/bin/fatal-handler.js.map +0 -1
- package/dist/bin/homegraph.d.ts.map +0 -1
- package/dist/bin/homegraph.js.map +0 -1
- package/dist/bin/node-version-check.d.ts.map +0 -1
- package/dist/bin/node-version-check.js.map +0 -1
- package/dist/bin/uninstall.d.ts.map +0 -1
- package/dist/bin/uninstall.js.map +0 -1
- package/dist/context/formatter.d.ts.map +0 -1
- package/dist/context/formatter.js.map +0 -1
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js.map +0 -1
- package/dist/context/markers.d.ts.map +0 -1
- package/dist/context/markers.js.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/migrations.d.ts.map +0 -1
- package/dist/db/migrations.js.map +0 -1
- package/dist/db/queries.d.ts.map +0 -1
- package/dist/db/queries.js.map +0 -1
- package/dist/db/sqlite-adapter.d.ts.map +0 -1
- package/dist/db/sqlite-adapter.js.map +0 -1
- package/dist/db/wal-valve.d.ts.map +0 -1
- package/dist/db/wal-valve.js.map +0 -1
- package/dist/directory.d.ts.map +0 -1
- package/dist/directory.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/extraction/arkts-batch-worker.d.ts.map +0 -1
- package/dist/extraction/arkts-batch-worker.js.map +0 -1
- package/dist/extraction/astro-extractor.d.ts.map +0 -1
- package/dist/extraction/astro-extractor.js.map +0 -1
- package/dist/extraction/cfml-extractor.d.ts.map +0 -1
- package/dist/extraction/cfml-extractor.js.map +0 -1
- package/dist/extraction/context.d.ts.map +0 -1
- package/dist/extraction/context.js.map +0 -1
- package/dist/extraction/default-ignore.d.ts.map +0 -1
- package/dist/extraction/default-ignore.js.map +0 -1
- package/dist/extraction/dfm-extractor.d.ts.map +0 -1
- package/dist/extraction/dfm-extractor.js.map +0 -1
- package/dist/extraction/extraction-version.d.ts.map +0 -1
- package/dist/extraction/extraction-version.js.map +0 -1
- package/dist/extraction/function-ref.d.ts.map +0 -1
- package/dist/extraction/function-ref.js.map +0 -1
- package/dist/extraction/generated-detection.d.ts.map +0 -1
- package/dist/extraction/generated-detection.js.map +0 -1
- package/dist/extraction/grammars.d.ts.map +0 -1
- package/dist/extraction/grammars.js.map +0 -1
- package/dist/extraction/index.d.ts.map +0 -1
- package/dist/extraction/index.js.map +0 -1
- package/dist/extraction/languages/arkts.d.ts.map +0 -1
- package/dist/extraction/languages/arkts.js.map +0 -1
- package/dist/extraction/languages/c-cpp.d.ts.map +0 -1
- package/dist/extraction/languages/c-cpp.js.map +0 -1
- package/dist/extraction/languages/cfquery.d.ts.map +0 -1
- package/dist/extraction/languages/cfquery.js.map +0 -1
- package/dist/extraction/languages/cfscript.d.ts.map +0 -1
- package/dist/extraction/languages/cfscript.js.map +0 -1
- package/dist/extraction/languages/cobol.d.ts.map +0 -1
- package/dist/extraction/languages/cobol.js.map +0 -1
- package/dist/extraction/languages/csharp.d.ts.map +0 -1
- package/dist/extraction/languages/csharp.js.map +0 -1
- package/dist/extraction/languages/dart.d.ts.map +0 -1
- package/dist/extraction/languages/dart.js.map +0 -1
- package/dist/extraction/languages/erlang.d.ts.map +0 -1
- package/dist/extraction/languages/erlang.js.map +0 -1
- package/dist/extraction/languages/go.d.ts.map +0 -1
- package/dist/extraction/languages/go.js.map +0 -1
- package/dist/extraction/languages/index.d.ts.map +0 -1
- package/dist/extraction/languages/index.js.map +0 -1
- package/dist/extraction/languages/java.d.ts.map +0 -1
- package/dist/extraction/languages/java.js.map +0 -1
- package/dist/extraction/languages/javascript.d.ts.map +0 -1
- package/dist/extraction/languages/javascript.js.map +0 -1
- package/dist/extraction/languages/kotlin.d.ts.map +0 -1
- package/dist/extraction/languages/kotlin.js.map +0 -1
- package/dist/extraction/languages/lua.d.ts.map +0 -1
- package/dist/extraction/languages/lua.js.map +0 -1
- package/dist/extraction/languages/luau.d.ts.map +0 -1
- package/dist/extraction/languages/luau.js.map +0 -1
- package/dist/extraction/languages/nix.d.ts.map +0 -1
- package/dist/extraction/languages/nix.js.map +0 -1
- package/dist/extraction/languages/objc.d.ts.map +0 -1
- package/dist/extraction/languages/objc.js.map +0 -1
- package/dist/extraction/languages/pascal.d.ts.map +0 -1
- package/dist/extraction/languages/pascal.js.map +0 -1
- package/dist/extraction/languages/php.d.ts.map +0 -1
- package/dist/extraction/languages/php.js.map +0 -1
- package/dist/extraction/languages/python.d.ts.map +0 -1
- package/dist/extraction/languages/python.js.map +0 -1
- package/dist/extraction/languages/r.d.ts.map +0 -1
- package/dist/extraction/languages/r.js.map +0 -1
- package/dist/extraction/languages/ruby.d.ts.map +0 -1
- package/dist/extraction/languages/ruby.js.map +0 -1
- package/dist/extraction/languages/rust.d.ts.map +0 -1
- package/dist/extraction/languages/rust.js.map +0 -1
- package/dist/extraction/languages/scala.d.ts.map +0 -1
- package/dist/extraction/languages/scala.js.map +0 -1
- package/dist/extraction/languages/solidity.d.ts.map +0 -1
- package/dist/extraction/languages/solidity.js.map +0 -1
- package/dist/extraction/languages/swift.d.ts.map +0 -1
- package/dist/extraction/languages/swift.js.map +0 -1
- package/dist/extraction/languages/terraform.d.ts.map +0 -1
- package/dist/extraction/languages/terraform.js.map +0 -1
- package/dist/extraction/languages/typescript.d.ts.map +0 -1
- package/dist/extraction/languages/typescript.js.map +0 -1
- package/dist/extraction/languages/vbnet.d.ts.map +0 -1
- package/dist/extraction/languages/vbnet.js.map +0 -1
- package/dist/extraction/liquid-extractor.d.ts.map +0 -1
- package/dist/extraction/liquid-extractor.js.map +0 -1
- package/dist/extraction/mybatis-extractor.d.ts.map +0 -1
- package/dist/extraction/mybatis-extractor.js.map +0 -1
- package/dist/extraction/parse-pool.d.ts.map +0 -1
- package/dist/extraction/parse-pool.js.map +0 -1
- package/dist/extraction/parse-worker.d.ts.map +0 -1
- package/dist/extraction/parse-worker.js.map +0 -1
- package/dist/extraction/razor-extractor.d.ts.map +0 -1
- package/dist/extraction/razor-extractor.js.map +0 -1
- package/dist/extraction/store-worker.d.ts.map +0 -1
- package/dist/extraction/store-worker.js.map +0 -1
- package/dist/extraction/store-writer.d.ts.map +0 -1
- package/dist/extraction/store-writer.js.map +0 -1
- package/dist/extraction/svelte-extractor.d.ts.map +0 -1
- package/dist/extraction/svelte-extractor.js.map +0 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +0 -1
- package/dist/extraction/tree-sitter-helpers.js.map +0 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +0 -1
- package/dist/extraction/tree-sitter-types.js.map +0 -1
- package/dist/extraction/tree-sitter.d.ts.map +0 -1
- package/dist/extraction/tree-sitter.js.map +0 -1
- package/dist/extraction/vue-extractor.d.ts.map +0 -1
- package/dist/extraction/vue-extractor.js.map +0 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm-runtime-flags.d.ts.map +0 -1
- package/dist/extraction/wasm-runtime-flags.js.map +0 -1
- package/dist/graph/index.d.ts.map +0 -1
- package/dist/graph/index.js.map +0 -1
- package/dist/graph/queries.d.ts.map +0 -1
- package/dist/graph/queries.js.map +0 -1
- package/dist/graph/traversal.d.ts.map +0 -1
- package/dist/graph/traversal.js.map +0 -1
- package/dist/graph-sources.d.ts.map +0 -1
- package/dist/graph-sources.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/installer/config-writer.d.ts.map +0 -1
- package/dist/installer/config-writer.js.map +0 -1
- package/dist/installer/index.d.ts.map +0 -1
- package/dist/installer/index.js.map +0 -1
- package/dist/installer/instructions-template.d.ts.map +0 -1
- package/dist/installer/instructions-template.js.map +0 -1
- package/dist/installer/targets/antigravity.d.ts.map +0 -1
- package/dist/installer/targets/antigravity.js.map +0 -1
- package/dist/installer/targets/claude.d.ts.map +0 -1
- package/dist/installer/targets/claude.js.map +0 -1
- package/dist/installer/targets/codebuddy.d.ts.map +0 -1
- package/dist/installer/targets/codebuddy.js.map +0 -1
- package/dist/installer/targets/codex.d.ts.map +0 -1
- package/dist/installer/targets/codex.js.map +0 -1
- package/dist/installer/targets/cursor.d.ts.map +0 -1
- package/dist/installer/targets/cursor.js.map +0 -1
- package/dist/installer/targets/deveco.d.ts.map +0 -1
- package/dist/installer/targets/deveco.js.map +0 -1
- package/dist/installer/targets/gemini.d.ts.map +0 -1
- package/dist/installer/targets/gemini.js.map +0 -1
- package/dist/installer/targets/hermes.d.ts.map +0 -1
- package/dist/installer/targets/hermes.js.map +0 -1
- package/dist/installer/targets/kiro.d.ts.map +0 -1
- package/dist/installer/targets/kiro.js.map +0 -1
- package/dist/installer/targets/opencode.d.ts.map +0 -1
- package/dist/installer/targets/opencode.js.map +0 -1
- package/dist/installer/targets/registry.d.ts.map +0 -1
- package/dist/installer/targets/registry.js.map +0 -1
- package/dist/installer/targets/shared.d.ts.map +0 -1
- package/dist/installer/targets/shared.js.map +0 -1
- package/dist/installer/targets/toml.d.ts.map +0 -1
- package/dist/installer/targets/toml.js.map +0 -1
- package/dist/installer/targets/types.d.ts.map +0 -1
- package/dist/installer/targets/types.js.map +0 -1
- package/dist/mcp/daemon-manager.d.ts.map +0 -1
- package/dist/mcp/daemon-manager.js.map +0 -1
- package/dist/mcp/daemon-paths.d.ts.map +0 -1
- package/dist/mcp/daemon-paths.js.map +0 -1
- package/dist/mcp/daemon-registry.d.ts.map +0 -1
- package/dist/mcp/daemon-registry.js.map +0 -1
- package/dist/mcp/daemon.d.ts.map +0 -1
- package/dist/mcp/daemon.js.map +0 -1
- package/dist/mcp/diff-impact.d.ts.map +0 -1
- package/dist/mcp/diff-impact.js.map +0 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +0 -1
- package/dist/mcp/dynamic-boundaries.js.map +0 -1
- package/dist/mcp/early-ppid.d.ts.map +0 -1
- package/dist/mcp/early-ppid.js.map +0 -1
- package/dist/mcp/engine.d.ts.map +0 -1
- package/dist/mcp/engine.js.map +0 -1
- package/dist/mcp/explore-dedup.d.ts.map +0 -1
- package/dist/mcp/explore-dedup.js.map +0 -1
- package/dist/mcp/explore-repeat-guard.d.ts.map +0 -1
- package/dist/mcp/explore-repeat-guard.js.map +0 -1
- package/dist/mcp/explore-session-state.d.ts.map +0 -1
- package/dist/mcp/explore-session-state.js.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +0 -1
- package/dist/mcp/liveness-watchdog.js.map +0 -1
- package/dist/mcp/memory-budget.d.ts.map +0 -1
- package/dist/mcp/memory-budget.js.map +0 -1
- package/dist/mcp/ppid-watchdog.d.ts.map +0 -1
- package/dist/mcp/ppid-watchdog.js.map +0 -1
- package/dist/mcp/proxy.d.ts.map +0 -1
- package/dist/mcp/proxy.js.map +0 -1
- package/dist/mcp/query-cache.d.ts.map +0 -1
- package/dist/mcp/query-cache.js.map +0 -1
- package/dist/mcp/query-pool.d.ts.map +0 -1
- package/dist/mcp/query-pool.js.map +0 -1
- package/dist/mcp/query-worker.d.ts.map +0 -1
- package/dist/mcp/query-worker.js.map +0 -1
- package/dist/mcp/server-instructions.d.ts.map +0 -1
- package/dist/mcp/server-instructions.js.map +0 -1
- package/dist/mcp/session.d.ts.map +0 -1
- package/dist/mcp/session.js.map +0 -1
- package/dist/mcp/startup-handshake.d.ts.map +0 -1
- package/dist/mcp/startup-handshake.js.map +0 -1
- package/dist/mcp/stdin-teardown.d.ts.map +0 -1
- package/dist/mcp/stdin-teardown.js.map +0 -1
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js.map +0 -1
- package/dist/mcp/transport.d.ts.map +0 -1
- package/dist/mcp/transport.js.map +0 -1
- package/dist/mcp/version.d.ts.map +0 -1
- package/dist/mcp/version.js.map +0 -1
- package/dist/project-config.d.ts.map +0 -1
- package/dist/project-config.js.map +0 -1
- package/dist/project-map/index.d.ts.map +0 -1
- package/dist/project-map/index.js.map +0 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +0 -1
- package/dist/resolution/c-fnptr-synthesizer.js.map +0 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +0 -1
- package/dist/resolution/callback-synthesizer.js.map +0 -1
- package/dist/resolution/cooperative-yield.d.ts.map +0 -1
- package/dist/resolution/cooperative-yield.js.map +0 -1
- package/dist/resolution/frameworks/arkts-entry.d.ts.map +0 -1
- package/dist/resolution/frameworks/arkts-entry.js.map +0 -1
- package/dist/resolution/frameworks/arkts-napi.d.ts.map +0 -1
- package/dist/resolution/frameworks/arkts-napi.js.map +0 -1
- package/dist/resolution/frameworks/astro.d.ts.map +0 -1
- package/dist/resolution/frameworks/astro.js.map +0 -1
- package/dist/resolution/frameworks/cargo-workspace.d.ts.map +0 -1
- package/dist/resolution/frameworks/cargo-workspace.js.map +0 -1
- package/dist/resolution/frameworks/cics.d.ts.map +0 -1
- package/dist/resolution/frameworks/cics.js.map +0 -1
- package/dist/resolution/frameworks/csharp.d.ts.map +0 -1
- package/dist/resolution/frameworks/csharp.js.map +0 -1
- package/dist/resolution/frameworks/drupal.d.ts.map +0 -1
- package/dist/resolution/frameworks/drupal.js.map +0 -1
- package/dist/resolution/frameworks/expo-modules.d.ts.map +0 -1
- package/dist/resolution/frameworks/expo-modules.js.map +0 -1
- package/dist/resolution/frameworks/express.d.ts.map +0 -1
- package/dist/resolution/frameworks/express.js.map +0 -1
- package/dist/resolution/frameworks/fabric.d.ts.map +0 -1
- package/dist/resolution/frameworks/fabric.js.map +0 -1
- package/dist/resolution/frameworks/go.d.ts.map +0 -1
- package/dist/resolution/frameworks/go.js.map +0 -1
- package/dist/resolution/frameworks/goframe.d.ts.map +0 -1
- package/dist/resolution/frameworks/goframe.js.map +0 -1
- package/dist/resolution/frameworks/index.d.ts.map +0 -1
- package/dist/resolution/frameworks/index.js.map +0 -1
- package/dist/resolution/frameworks/java.d.ts.map +0 -1
- package/dist/resolution/frameworks/java.js.map +0 -1
- package/dist/resolution/frameworks/laravel.d.ts.map +0 -1
- package/dist/resolution/frameworks/laravel.js.map +0 -1
- package/dist/resolution/frameworks/nestjs.d.ts.map +0 -1
- package/dist/resolution/frameworks/nestjs.js.map +0 -1
- package/dist/resolution/frameworks/play.d.ts.map +0 -1
- package/dist/resolution/frameworks/play.js.map +0 -1
- package/dist/resolution/frameworks/python.d.ts.map +0 -1
- package/dist/resolution/frameworks/python.js.map +0 -1
- package/dist/resolution/frameworks/react-native.d.ts.map +0 -1
- package/dist/resolution/frameworks/react-native.js.map +0 -1
- package/dist/resolution/frameworks/react.d.ts.map +0 -1
- package/dist/resolution/frameworks/react.js.map +0 -1
- package/dist/resolution/frameworks/ruby.d.ts.map +0 -1
- package/dist/resolution/frameworks/ruby.js.map +0 -1
- package/dist/resolution/frameworks/rust.d.ts.map +0 -1
- package/dist/resolution/frameworks/rust.js.map +0 -1
- package/dist/resolution/frameworks/svelte.d.ts.map +0 -1
- package/dist/resolution/frameworks/svelte.js.map +0 -1
- package/dist/resolution/frameworks/swift-objc.d.ts.map +0 -1
- package/dist/resolution/frameworks/swift-objc.js.map +0 -1
- package/dist/resolution/frameworks/swift.d.ts.map +0 -1
- package/dist/resolution/frameworks/swift.js.map +0 -1
- package/dist/resolution/frameworks/terraform.d.ts.map +0 -1
- package/dist/resolution/frameworks/terraform.js.map +0 -1
- package/dist/resolution/frameworks/vue.d.ts.map +0 -1
- package/dist/resolution/frameworks/vue.js.map +0 -1
- package/dist/resolution/go-module.d.ts.map +0 -1
- package/dist/resolution/go-module.js.map +0 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +0 -1
- package/dist/resolution/goframe-synthesizer.js.map +0 -1
- package/dist/resolution/import-resolver.d.ts.map +0 -1
- package/dist/resolution/import-resolver.js.map +0 -1
- package/dist/resolution/index.d.ts.map +0 -1
- package/dist/resolution/index.js.map +0 -1
- package/dist/resolution/lru-cache.d.ts.map +0 -1
- package/dist/resolution/lru-cache.js.map +0 -1
- package/dist/resolution/memory-budget.d.ts.map +0 -1
- package/dist/resolution/memory-budget.js.map +0 -1
- package/dist/resolution/name-matcher.d.ts.map +0 -1
- package/dist/resolution/name-matcher.js.map +0 -1
- package/dist/resolution/path-aliases.d.ts.map +0 -1
- package/dist/resolution/path-aliases.js.map +0 -1
- package/dist/resolution/resolver-pool.d.ts.map +0 -1
- package/dist/resolution/resolver-pool.js.map +0 -1
- package/dist/resolution/resolver-worker.d.ts.map +0 -1
- package/dist/resolution/resolver-worker.js.map +0 -1
- package/dist/resolution/strip-comments.d.ts.map +0 -1
- package/dist/resolution/strip-comments.js.map +0 -1
- package/dist/resolution/swift-objc-bridge.d.ts.map +0 -1
- package/dist/resolution/swift-objc-bridge.js.map +0 -1
- package/dist/resolution/types.d.ts.map +0 -1
- package/dist/resolution/types.js.map +0 -1
- package/dist/resolution/workspace-packages.d.ts.map +0 -1
- package/dist/resolution/workspace-packages.js.map +0 -1
- package/dist/search/identifier-segments.d.ts.map +0 -1
- package/dist/search/identifier-segments.js.map +0 -1
- package/dist/search/query-parser.d.ts.map +0 -1
- package/dist/search/query-parser.js.map +0 -1
- package/dist/search/query-utils.d.ts.map +0 -1
- package/dist/search/query-utils.js.map +0 -1
- package/dist/spec/build/diff-parser.d.ts.map +0 -1
- package/dist/spec/build/diff-parser.js.map +0 -1
- package/dist/spec/build/pipeline.d.ts.map +0 -1
- package/dist/spec/build/pipeline.js.map +0 -1
- package/dist/spec/build/scan.d.ts.map +0 -1
- package/dist/spec/build/scan.js.map +0 -1
- package/dist/spec/build/scope-resolver.d.ts.map +0 -1
- package/dist/spec/build/scope-resolver.js.map +0 -1
- package/dist/spec/build/spec-extractor.d.ts.map +0 -1
- package/dist/spec/build/spec-extractor.js.map +0 -1
- package/dist/spec/config.d.ts.map +0 -1
- package/dist/spec/config.js.map +0 -1
- package/dist/spec/db/commit-node.d.ts.map +0 -1
- package/dist/spec/db/commit-node.js.map +0 -1
- package/dist/spec/db/fragment-node.d.ts.map +0 -1
- package/dist/spec/db/fragment-node.js.map +0 -1
- package/dist/spec/db/fts.d.ts.map +0 -1
- package/dist/spec/db/fts.js.map +0 -1
- package/dist/spec/db/index.d.ts.map +0 -1
- package/dist/spec/db/index.js.map +0 -1
- package/dist/spec/db/persist.d.ts.map +0 -1
- package/dist/spec/db/persist.js.map +0 -1
- package/dist/spec/db/relations.d.ts.map +0 -1
- package/dist/spec/db/relations.js.map +0 -1
- package/dist/spec/db/schema.d.ts.map +0 -1
- package/dist/spec/db/schema.js.map +0 -1
- package/dist/spec/db/spec-node.d.ts.map +0 -1
- package/dist/spec/db/spec-node.js.map +0 -1
- package/dist/spec/db/sql-utils.d.ts.map +0 -1
- package/dist/spec/db/sql-utils.js.map +0 -1
- package/dist/spec/evolve/cluster-context.d.ts.map +0 -1
- package/dist/spec/evolve/cluster-context.js.map +0 -1
- package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +0 -1
- package/dist/spec/evolve/commit-spec-analyzer.js.map +0 -1
- package/dist/spec/evolve/commit-spec-persister.d.ts.map +0 -1
- package/dist/spec/evolve/commit-spec-persister.js.map +0 -1
- package/dist/spec/evolve/impact-locator.d.ts.map +0 -1
- package/dist/spec/evolve/impact-locator.js.map +0 -1
- package/dist/spec/evolve/pipeline.d.ts.map +0 -1
- package/dist/spec/evolve/pipeline.js.map +0 -1
- package/dist/spec/evolve/spec-rewriter.d.ts.map +0 -1
- package/dist/spec/evolve/spec-rewriter.js.map +0 -1
- package/dist/spec/git/commits.d.ts.map +0 -1
- package/dist/spec/git/commits.js.map +0 -1
- package/dist/spec/git/exec.d.ts.map +0 -1
- package/dist/spec/git/exec.js.map +0 -1
- package/dist/spec/git/index.d.ts.map +0 -1
- package/dist/spec/git/index.js.map +0 -1
- package/dist/spec/graph/index.d.ts.map +0 -1
- package/dist/spec/graph/index.js.map +0 -1
- package/dist/spec/graph/queries.d.ts.map +0 -1
- package/dist/spec/graph/queries.js.map +0 -1
- package/dist/spec/llm/agent-client.d.ts.map +0 -1
- package/dist/spec/llm/agent-client.js.map +0 -1
- package/dist/spec/llm/agents/claude-code.d.ts.map +0 -1
- package/dist/spec/llm/agents/claude-code.js.map +0 -1
- package/dist/spec/llm/agents/codex.d.ts.map +0 -1
- package/dist/spec/llm/agents/codex.js.map +0 -1
- package/dist/spec/llm/agents/detect-utils.d.ts.map +0 -1
- package/dist/spec/llm/agents/detect-utils.js.map +0 -1
- package/dist/spec/llm/agents/deveco-code.d.ts.map +0 -1
- package/dist/spec/llm/agents/deveco-code.js.map +0 -1
- package/dist/spec/llm/agents/index.d.ts.map +0 -1
- package/dist/spec/llm/agents/index.js.map +0 -1
- package/dist/spec/llm/agents/types.d.ts.map +0 -1
- package/dist/spec/llm/agents/types.js.map +0 -1
- package/dist/spec/llm/client.d.ts.map +0 -1
- package/dist/spec/llm/client.js.map +0 -1
- package/dist/spec/llm/factory.d.ts.map +0 -1
- package/dist/spec/llm/factory.js.map +0 -1
- package/dist/spec/llm/prompts.d.ts.map +0 -1
- package/dist/spec/llm/prompts.js.map +0 -1
- package/dist/spec/llm/retry.d.ts.map +0 -1
- package/dist/spec/llm/retry.js.map +0 -1
- package/dist/spec/mine/addon/adapter.d.ts.map +0 -1
- package/dist/spec/mine/addon/adapter.js.map +0 -1
- package/dist/spec/mine/addon/render.d.ts.map +0 -1
- package/dist/spec/mine/addon/render.js.map +0 -1
- package/dist/spec/mine/addon/types.d.ts.map +0 -1
- package/dist/spec/mine/addon/types.js.map +0 -1
- package/dist/spec/mine/clustering/features.d.ts.map +0 -1
- package/dist/spec/mine/clustering/features.js.map +0 -1
- package/dist/spec/mine/clustering/index.d.ts.map +0 -1
- package/dist/spec/mine/clustering/index.js.map +0 -1
- package/dist/spec/mine/clustering/leiden.d.ts.map +0 -1
- package/dist/spec/mine/clustering/leiden.js.map +0 -1
- package/dist/spec/mine/clustering/text-similarity.d.ts.map +0 -1
- package/dist/spec/mine/clustering/text-similarity.js.map +0 -1
- package/dist/spec/mine/generator.d.ts.map +0 -1
- package/dist/spec/mine/generator.js.map +0 -1
- package/dist/spec/mine/persist.d.ts.map +0 -1
- package/dist/spec/mine/persist.js.map +0 -1
- package/dist/spec/mine/pipeline.d.ts.map +0 -1
- package/dist/spec/mine/pipeline.js.map +0 -1
- package/dist/spec/mine/scanner.d.ts.map +0 -1
- package/dist/spec/mine/scanner.js.map +0 -1
- package/dist/spec/types.d.ts.map +0 -1
- package/dist/spec/types.js.map +0 -1
- package/dist/spec/ui/index.d.ts.map +0 -1
- package/dist/spec/ui/index.js.map +0 -1
- package/dist/spec/ui/progress-handler.d.ts.map +0 -1
- package/dist/spec/ui/progress-handler.js.map +0 -1
- package/dist/spec/ui/progress.d.ts.map +0 -1
- package/dist/spec/ui/progress.js.map +0 -1
- package/dist/spec/utils/fs.d.ts.map +0 -1
- package/dist/spec/utils/fs.js.map +0 -1
- package/dist/spec/utils/index.d.ts.map +0 -1
- package/dist/spec/utils/index.js.map +0 -1
- package/dist/spec/utils/meta.d.ts.map +0 -1
- package/dist/spec/utils/meta.js.map +0 -1
- package/dist/spec/utils/truncate.d.ts.map +0 -1
- package/dist/spec/utils/truncate.js.map +0 -1
- package/dist/sync/git-hooks.d.ts.map +0 -1
- package/dist/sync/git-hooks.js.map +0 -1
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/sync/index.js.map +0 -1
- package/dist/sync/watch-policy.d.ts.map +0 -1
- package/dist/sync/watch-policy.js.map +0 -1
- package/dist/sync/watcher.d.ts.map +0 -1
- package/dist/sync/watcher.js.map +0 -1
- package/dist/sync/worktree.d.ts.map +0 -1
- package/dist/sync/worktree.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui/glyphs.d.ts.map +0 -1
- package/dist/ui/glyphs.js.map +0 -1
- package/dist/ui/shimmer-progress.d.ts.map +0 -1
- package/dist/ui/shimmer-progress.js.map +0 -1
- package/dist/ui/shimmer-worker.d.ts.map +0 -1
- package/dist/ui/shimmer-worker.js.map +0 -1
- package/dist/ui/types.d.ts.map +0 -1
- package/dist/ui/types.js.map +0 -1
- package/dist/upgrade/index.d.ts.map +0 -1
- package/dist/upgrade/index.js.map +0 -1
- package/dist/upgrade/remove-binary.d.ts.map +0 -1
- package/dist/upgrade/remove-binary.js.map +0 -1
- package/dist/upgrade/update-check.d.ts.map +0 -1
- package/dist/upgrade/update-check.js.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js.map +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -21,7 +21,13 @@ exports.queryAsBareSymbolInventory = queryAsBareSymbolInventory;
|
|
|
21
21
|
exports.hasPositiveAnswerNowDirective = hasPositiveAnswerNowDirective;
|
|
22
22
|
exports.reconcilePartialAnswerNow = reconcilePartialAnswerNow;
|
|
23
23
|
const query_pool_1 = require("./query-pool");
|
|
24
|
+
const source_slice_identity_1 = require("./source-slice-identity");
|
|
25
|
+
const evidence_rendering_1 = require("./evidence-rendering");
|
|
26
|
+
const arkts_evidence_packs_1 = require("./arkts-evidence-packs");
|
|
27
|
+
const evidence_paths_1 = require("../graph/evidence-paths");
|
|
28
|
+
const query_plan_1 = require("../search/query-plan");
|
|
24
29
|
const memory_budget_1 = require("./memory-budget");
|
|
30
|
+
const index_availability_1 = require("./index-availability");
|
|
25
31
|
const directory_1 = require("../directory");
|
|
26
32
|
// Lazy-load the heavy HomeGraph chain off the MCP startup path — see the same
|
|
27
33
|
// helper in engine.ts. ToolHandler must load to answer tools/list (static
|
|
@@ -99,6 +105,20 @@ const MAX_OUTPUT_LENGTH = 15000;
|
|
|
99
105
|
* far beyond any realistic legitimate query.
|
|
100
106
|
*/
|
|
101
107
|
const MAX_INPUT_LENGTH = 10_000;
|
|
108
|
+
// Server-owned, structured-clone-safe request context. Never accept these from MCP clients.
|
|
109
|
+
const QUERY_PLAN_ARG = '__homegraphQueryPlan';
|
|
110
|
+
const QUERY_DEADLINE_ARG = '__homegraphQueryDeadlineAt';
|
|
111
|
+
const QUERY_STARTED_ARG = '__homegraphQueryStartedAt';
|
|
112
|
+
const QUERY_INDEX_STATE_ARG = '__homegraphQueryIndexState';
|
|
113
|
+
const QUERY_FAST_ATTEMPTED_ARG = '__homegraphQueryFastAttempted';
|
|
114
|
+
function readQueryPlan(args) {
|
|
115
|
+
const plan = args[QUERY_PLAN_ARG];
|
|
116
|
+
return plan?.version === query_plan_1.QUERY_PLAN_VERSION && typeof plan.originalQuery === 'string'
|
|
117
|
+
&& typeof plan.canonicalQuery === 'string' && Array.isArray(plan.steps) ? plan : undefined;
|
|
118
|
+
}
|
|
119
|
+
function planFeature(plan, name, query, fallback) {
|
|
120
|
+
return plan?.features[name] ?? fallback(query);
|
|
121
|
+
}
|
|
102
122
|
/** Example values for success-shaped bad-arg guidance (keyed by arg name). */
|
|
103
123
|
const BAD_ARG_EXAMPLES = {
|
|
104
124
|
query: 'authenticate login',
|
|
@@ -641,7 +661,7 @@ exports.tools = [
|
|
|
641
661
|
{
|
|
642
662
|
name: 'homegraph_search',
|
|
643
663
|
description: 'LAST RESORT spelling lookup — locations only, no source. Required: `query` (e.g. "signIn"). ' +
|
|
644
|
-
'
|
|
664
|
+
'Use ordinary scoped search/read when names or paths are known; use graph tools only for missing structural evidence. ' +
|
|
645
665
|
'DO NOT call for topic file-lists, concept compares, or SDK/@kit feature catalogs (those return Skip guidance). ' +
|
|
646
666
|
'Also skip literal string/pattern greps — use Grep instead. ' +
|
|
647
667
|
'Bare-name search may return a compact explore result instead of locations.',
|
|
@@ -671,7 +691,7 @@ exports.tools = [
|
|
|
671
691
|
{
|
|
672
692
|
name: 'homegraph_callers',
|
|
673
693
|
description: 'Compact caller list for one NAMED in-repo symbol (no bodies). Required: `symbol` (e.g. "authenticate"). ' +
|
|
674
|
-
'Cheaper than explore when you only need who-calls-X. For
|
|
694
|
+
'Cheaper than explore when you only need who-calls-X. For an unresolved cross-symbol flow, consider homegraph_explore. ' +
|
|
675
695
|
'DO NOT call for SDK catalogs, topic file-lists, concept compares, or hypothetics.',
|
|
676
696
|
inputSchema: {
|
|
677
697
|
type: 'object',
|
|
@@ -698,7 +718,7 @@ exports.tools = [
|
|
|
698
718
|
{
|
|
699
719
|
name: 'homegraph_callees',
|
|
700
720
|
description: 'Compact callee list for one NAMED in-repo symbol (no bodies). Required: `symbol` (e.g. "authenticate"). ' +
|
|
701
|
-
'Cheaper than explore when you only need what-X-calls. For
|
|
721
|
+
'Cheaper than explore when you only need what-X-calls. For an unresolved cross-symbol flow, consider homegraph_explore. ' +
|
|
702
722
|
'DO NOT use for out-of-repo SDK catalogs or counterfactual analysis.',
|
|
703
723
|
inputSchema: {
|
|
704
724
|
type: 'object',
|
|
@@ -799,9 +819,9 @@ exports.tools = [
|
|
|
799
819
|
'Cheaper than explore when you already know the name and only need one body. ' +
|
|
800
820
|
'FILE: `file` only → line-numbered source + dependents. ' +
|
|
801
821
|
'SYMBOL: body via includeCode + short trail; overloads return every body. ' +
|
|
802
|
-
'
|
|
803
|
-
'
|
|
804
|
-
'Treat returned source as already Read.',
|
|
822
|
+
'Reuse complete unchanged source ranges already returned by any tool; refresh missing, truncated or edited ranges. ' +
|
|
823
|
+
'Avoid repeated node calls over the same evidence; use explore only for an unresolved cross-symbol flow. ' +
|
|
824
|
+
'Treat complete returned source ranges as already Read, not an entire file inferred from an excerpt.',
|
|
805
825
|
inputSchema: {
|
|
806
826
|
type: 'object',
|
|
807
827
|
properties: {
|
|
@@ -843,95 +863,97 @@ exports.tools = [
|
|
|
843
863
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
844
864
|
},
|
|
845
865
|
{
|
|
846
|
-
name: '
|
|
847
|
-
description: '
|
|
848
|
-
'
|
|
849
|
-
'
|
|
850
|
-
'
|
|
851
|
-
'named Type/Component/Page/Dialog, Type.member, click→handler, inheritance/subtypes, declaration/attribute sites, ' +
|
|
852
|
-
'ALL_CAPS constant / field-mutex usages, path-module NAPI/exports or inter-deps, and in-repo @kit/@ohos *usages/dependencies* ' +
|
|
853
|
-
'(which files import a named export — not the SDK feature catalog). PascalCase names optional when domain keywords suffice. ' +
|
|
854
|
-
'Prefer callers/node when one named symbol is already enough. ' +
|
|
855
|
-
'DO NOT call for topic file-lists with no Type/file, literal copy hunts / pure existence compares with no anchors, ' +
|
|
856
|
-
'official-docs-only asks, empty-project-from-scratch scaffolds, git history, or media/binary asset inventories — those return Skip. ' +
|
|
857
|
-
'@kit / OHOS API questions ARE in scope when the SDK API graph is available — call explore (usages or API symbols). ' +
|
|
858
|
-
'Literal string/pattern hunts → Grep; media assets → Glob; git history → git. ' +
|
|
859
|
-
'One explore; then answer or edit from Source + trail — do not re-grep/node/read the same symbols. ' +
|
|
860
|
-
'Overlapping paraphrase explores are refused (name a new Type/file/@kit to continue). ' +
|
|
861
|
-
'Busy/partial → retry ONCE with the named Next anchor or ONE narrow Grep — not a Grep/node storm '
|
|
862
|
-
+ '(session refuses further explore / depth fan-out after Partial).',
|
|
866
|
+
name: 'homegraph_usages',
|
|
867
|
+
description: 'Optional focused tool for an unresolved WHERE-USED question about one named API, `.member`, ALL_CAPS constant, field, or mutex. ' +
|
|
868
|
+
'Choose this instead of homegraph_explore when the requested answer is usage/reference locations. ' +
|
|
869
|
+
'Required: `query`. Returns usage files/lines only; it does not build a general flow or source dump. ' +
|
|
870
|
+
'`homegraph_explore` auto-routes equivalent high-confidence queries here only for compatibility.',
|
|
863
871
|
inputSchema: {
|
|
864
872
|
type: 'object',
|
|
865
873
|
properties: {
|
|
866
874
|
query: {
|
|
867
875
|
type: 'string',
|
|
868
|
-
description: 'Required.
|
|
869
|
-
'For named flows, include Type / Type.member / component names. For @kit, ask usages (depend/import sites), not SDK catalogs.',
|
|
870
|
-
},
|
|
871
|
-
maxFiles: {
|
|
872
|
-
type: 'number',
|
|
873
|
-
description: 'Maximum number of files to include source code from (default: 12)',
|
|
874
|
-
default: 12,
|
|
876
|
+
description: 'Required. Exact API/member/constant/field name, with or without where-used wording.',
|
|
875
877
|
},
|
|
876
878
|
projectPath: projectPathProperty,
|
|
877
879
|
},
|
|
878
880
|
required: ['query'],
|
|
879
881
|
},
|
|
880
|
-
annotations: READ_ONLY_ANNOTATIONS,
|
|
882
|
+
annotations: { ...READ_ONLY_ANNOTATIONS, title: 'HomeGraph Where-used Inventory' },
|
|
881
883
|
},
|
|
882
884
|
{
|
|
883
|
-
name: '
|
|
884
|
-
description: '
|
|
885
|
-
'
|
|
886
|
-
'
|
|
885
|
+
name: 'homegraph_modules',
|
|
886
|
+
description: 'Optional focused tool for an unresolved DEPENDENCY/CYCLE question about named path modules or `*common` / `*service` / `*component` / `*constants` modules. ' +
|
|
887
|
+
'Choose this instead of homegraph_explore when the requested answer is module topology. ' +
|
|
888
|
+
'Required: `query`. It does not build a general code flow or scan unrelated survey families. ' +
|
|
889
|
+
'`homegraph_explore` auto-routes equivalent high-confidence dependency questions here only for compatibility.',
|
|
887
890
|
inputSchema: {
|
|
888
891
|
type: 'object',
|
|
889
892
|
properties: {
|
|
890
893
|
query: {
|
|
891
894
|
type: 'string',
|
|
892
|
-
description: 'Required.
|
|
895
|
+
description: 'Required. Dependency/cycle question containing the exact module names or paths.',
|
|
893
896
|
},
|
|
894
897
|
projectPath: projectPathProperty,
|
|
895
898
|
},
|
|
896
899
|
required: ['query'],
|
|
897
900
|
},
|
|
898
|
-
annotations: READ_ONLY_ANNOTATIONS,
|
|
901
|
+
annotations: { ...READ_ONLY_ANNOTATIONS, title: 'HomeGraph Module Dependencies' },
|
|
899
902
|
},
|
|
900
903
|
{
|
|
901
|
-
name: '
|
|
902
|
-
description: '
|
|
903
|
-
'
|
|
904
|
-
'
|
|
904
|
+
name: 'homegraph_native',
|
|
905
|
+
description: 'Optional focused tool for an unresolved NAPI/NATIVE EXPORT or registration question about one named path or Type. ' +
|
|
906
|
+
'Choose this instead of homegraph_explore when the requested answer is the ArkTS↔native export surface. Required: `query`. ' +
|
|
907
|
+
'Returns indexed export descriptors/registration sites without a general domain file dump. ' +
|
|
908
|
+
'`homegraph_explore` auto-routes equivalent high-confidence NAPI/export questions here only for compatibility.',
|
|
905
909
|
inputSchema: {
|
|
906
910
|
type: 'object',
|
|
907
911
|
properties: {
|
|
908
912
|
query: {
|
|
909
913
|
type: 'string',
|
|
910
|
-
description: 'Required.
|
|
914
|
+
description: 'Required. NAPI/native export question containing the exact path or Type name.',
|
|
911
915
|
},
|
|
912
916
|
projectPath: projectPathProperty,
|
|
913
917
|
},
|
|
914
918
|
required: ['query'],
|
|
915
919
|
},
|
|
916
|
-
annotations: READ_ONLY_ANNOTATIONS,
|
|
920
|
+
annotations: { ...READ_ONLY_ANNOTATIONS, title: 'HomeGraph Native Exports' },
|
|
917
921
|
},
|
|
918
922
|
{
|
|
919
|
-
name: '
|
|
920
|
-
description: '
|
|
921
|
-
'
|
|
922
|
-
'
|
|
923
|
+
name: 'homegraph_explore',
|
|
924
|
+
description: 'Optional graph evidence for a concrete unresolved cross-symbol mechanism in THIS repo. Required: `query`. ' +
|
|
925
|
+
'Use ordinary bash/search/read for paths, symbols, literal strings and local changes; continue editing when that evidence suffices. ' +
|
|
926
|
+
'Do not call for routine pre-edit orientation or merely because implementation is difficult. ' +
|
|
927
|
+
'For a missing usage, dependency/cycle or native-registration relation, use ' +
|
|
928
|
+
'homegraph_usages, homegraph_modules, or homegraph_native instead. ' +
|
|
929
|
+
'Returns call paths and compact line-numbered source. ArkTS symbol evidence uses complete declarations and bounded directed paths with intermediate source dependencies; explicit Gaps and stop reasons name omitted or unverified evidence. Qualify ambiguous symbols by owning type or file. State the missing relation with known anchors, requested action, scope and constraints; taskContext can carry the full task. ' +
|
|
930
|
+
'Reuse unchanged complete ranges; refresh missing, edited or truncated evidence. ' +
|
|
931
|
+
'No new evidence → change to a targeted source inspection, not a paraphrased explore. ' +
|
|
932
|
+
'Partial/busy → at most one focused recovery for the named gap; budgets are ceilings, not required calls. ' +
|
|
933
|
+
'Retrieval completion is not task completion: continue implementation and required validation.',
|
|
923
934
|
inputSchema: {
|
|
924
935
|
type: 'object',
|
|
925
936
|
properties: {
|
|
926
937
|
query: {
|
|
927
938
|
type: 'string',
|
|
928
|
-
description: 'Required.
|
|
939
|
+
description: 'Required. For pre-edit orientation or how/mechanism: pass the user task or domain keywords (page/module/feature words). ' +
|
|
940
|
+
'For named flows, include Type / Type.member / component names. For @kit mechanism/flow, include module/export tokens; ' +
|
|
941
|
+
'use homegraph_usages for a narrow import/usage inventory, not SDK catalogs.',
|
|
942
|
+
},
|
|
943
|
+
taskContext: {
|
|
944
|
+
type: 'string',
|
|
945
|
+
description: 'Optional original task, including requested changes, product/module scope, exclusions and acceptance requirements. Not source evidence; bounded to 4000 characters.',
|
|
946
|
+
},
|
|
947
|
+
maxFiles: {
|
|
948
|
+
type: 'number',
|
|
949
|
+
description: 'Maximum number of files to include source code from (default: 12)',
|
|
950
|
+
default: 12,
|
|
929
951
|
},
|
|
930
952
|
projectPath: projectPathProperty,
|
|
931
953
|
},
|
|
932
954
|
required: ['query'],
|
|
933
955
|
},
|
|
934
|
-
annotations: READ_ONLY_ANNOTATIONS,
|
|
956
|
+
annotations: { ...READ_ONLY_ANNOTATIONS, title: 'HomeGraph General Explore' },
|
|
935
957
|
},
|
|
936
958
|
{
|
|
937
959
|
name: 'homegraph_status',
|
|
@@ -987,7 +1009,7 @@ exports.tools = [
|
|
|
987
1009
|
{
|
|
988
1010
|
name: 'homegraph_files',
|
|
989
1011
|
description: 'Indexed directory tree (paths and symbol counts only — NO source). ' +
|
|
990
|
-
'
|
|
1012
|
+
'Optional coarse folder inventory; prefer ordinary file listing for known paths. Use graph tools only for missing structural evidence.',
|
|
991
1013
|
inputSchema: {
|
|
992
1014
|
type: 'object',
|
|
993
1015
|
properties: {
|
|
@@ -1277,10 +1299,15 @@ function reconcilePartialAnswerNow(text) {
|
|
|
1277
1299
|
if (!/\*\*Partial locator\*\*/i.test(text))
|
|
1278
1300
|
return text;
|
|
1279
1301
|
let changed = false;
|
|
1302
|
+
let fenced = false;
|
|
1280
1303
|
const out = text
|
|
1281
1304
|
.split('\n')
|
|
1282
1305
|
.map((line) => {
|
|
1283
|
-
if (
|
|
1306
|
+
if (/^\s*```/.test(line)) {
|
|
1307
|
+
fenced = !fenced;
|
|
1308
|
+
return line;
|
|
1309
|
+
}
|
|
1310
|
+
if (fenced || !/ANSWER NOW/i.test(line))
|
|
1284
1311
|
return line;
|
|
1285
1312
|
const stripped = stripPositiveAnswerNowFromLine(line);
|
|
1286
1313
|
if (stripped.changed)
|
|
@@ -1291,7 +1318,7 @@ function reconcilePartialAnswerNow(text) {
|
|
|
1291
1318
|
.join('\n');
|
|
1292
1319
|
if (!changed)
|
|
1293
1320
|
return text;
|
|
1294
|
-
return `${out}\n\n> This survey is a **Partial locator**: the sections above are anchors, not a closed answer.
|
|
1321
|
+
return `${out}\n\n> This survey is a **Partial locator**: the sections above are anchors, not a closed answer. Inspect missing source with a focused lookup, continue the requested edits, and verify the resulting code.`;
|
|
1295
1322
|
}
|
|
1296
1323
|
class ToolHandler {
|
|
1297
1324
|
cg;
|
|
@@ -1450,7 +1477,6 @@ class ToolHandler {
|
|
|
1450
1477
|
return withRequiredProjectPath(visible);
|
|
1451
1478
|
try {
|
|
1452
1479
|
const stats = this.cg.getStats();
|
|
1453
|
-
const budget = getExploreBudget(stats.fileCount);
|
|
1454
1480
|
// Tiny-repo tool gating: on projects under TINY_REPO_FILE_THRESHOLD
|
|
1455
1481
|
// files, only expose the core trio (search, node, explore) — one
|
|
1456
1482
|
// below even the 4-tool default: at this scale callers, too, reduces
|
|
@@ -1482,14 +1508,16 @@ class ToolHandler {
|
|
|
1482
1508
|
'homegraph_diff_impact',
|
|
1483
1509
|
'homegraph_project',
|
|
1484
1510
|
]);
|
|
1485
|
-
|
|
1511
|
+
// An explicit host selection overrides the default size-based surface.
|
|
1512
|
+
// Otherwise small ArkTS repos silently lose requested specialized tools.
|
|
1513
|
+
if (!allow && stats.fileCount < TINY_REPO_FILE_THRESHOLD) {
|
|
1486
1514
|
visible = visible.filter(t => TINY_REPO_CORE_TOOLS.has(t.name));
|
|
1487
1515
|
}
|
|
1488
1516
|
return visible.map(tool => {
|
|
1489
1517
|
if (tool.name === 'homegraph_explore') {
|
|
1490
1518
|
return {
|
|
1491
1519
|
...tool,
|
|
1492
|
-
description: `${tool.description}
|
|
1520
|
+
description: `${tool.description} ${this.exploreSuffix(stats.fileCount)}`,
|
|
1493
1521
|
};
|
|
1494
1522
|
}
|
|
1495
1523
|
return tool;
|
|
@@ -1499,6 +1527,53 @@ class ToolHandler {
|
|
|
1499
1527
|
return visible;
|
|
1500
1528
|
}
|
|
1501
1529
|
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Per-repo suffix appended to homegraph_explore's description (Spec 0027).
|
|
1532
|
+
*
|
|
1533
|
+
* fileCount === 0 must not read as "this project is empty": hosts snapshot
|
|
1534
|
+
* tools/list once at connect (#964), and an auto-init that is still building
|
|
1535
|
+
* reports 0 at that instant. Field evidence (DevEco Code bench, 3 sessions,
|
|
1536
|
+
* zero homegraph calls): agents read "make at most 1 calls for this project
|
|
1537
|
+
* (0 files indexed)" as terminal — "homegraph isn't indexed for this
|
|
1538
|
+
* project" — and permanently fell back to Read/Grep even though the index
|
|
1539
|
+
* completed seconds later. Mirror maybeDeepToolPhaseGate's success-shaped
|
|
1540
|
+
* guidance so the frozen description and the live call-time response tell
|
|
1541
|
+
* the same story: failed build → say so (indexing is the user's call, so
|
|
1542
|
+
* the agent relays it); still building → point at homegraph_project and
|
|
1543
|
+
* retry; genuinely empty → honest empty note. Only fileCount > 0 carries a
|
|
1544
|
+
* call budget — there is nothing to budget over an unbuilt index.
|
|
1545
|
+
*/
|
|
1546
|
+
exploreSuffix(fileCount) {
|
|
1547
|
+
if (fileCount > 0) {
|
|
1548
|
+
return `Budget: make at most ${getExploreBudget(fileCount)} calls for this project (${fileCount.toLocaleString()} files indexed).`;
|
|
1549
|
+
}
|
|
1550
|
+
const cg = this.cg;
|
|
1551
|
+
if (cg) {
|
|
1552
|
+
try {
|
|
1553
|
+
// Failed check comes first: a failed full build rolls build_phase back
|
|
1554
|
+
// to 'fast' (startBackgroundFullBuild catch), so the building branch
|
|
1555
|
+
// below would otherwise mask the failure.
|
|
1556
|
+
if (cg.getQueryBuilder().getMetadata('index_state') === 'failed') {
|
|
1557
|
+
return 'The last full index build for this project failed, so results will be empty. Tell the user to re-run `homegraph index`.';
|
|
1558
|
+
}
|
|
1559
|
+
const state = (0, index_availability_1.resolveProductIndexState)(cg);
|
|
1560
|
+
if (state === 'empty') {
|
|
1561
|
+
return (0, index_availability_1.productIndexGuidance)('empty');
|
|
1562
|
+
}
|
|
1563
|
+
if (state === 'fast' || state === 'syncing') {
|
|
1564
|
+
// Keep the Spec 0027 "still building → homegraph_project" story for
|
|
1565
|
+
// frozen tools/list descriptions (hosts snapshot once at connect).
|
|
1566
|
+
return state === 'syncing'
|
|
1567
|
+
? (0, index_availability_1.productIndexGuidance)('syncing')
|
|
1568
|
+
: "HomeGraph is still building this project's index. A call right now returns build-progress guidance; use `homegraph_project` for the module/file map, then retry once indexing finishes.";
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
catch {
|
|
1572
|
+
/* metadata is advisory — fall through to the empty note */
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return 'No files are indexed for this project; results will be empty until an index is built.';
|
|
1576
|
+
}
|
|
1502
1577
|
/**
|
|
1503
1578
|
* Get HomeGraph instance for a project
|
|
1504
1579
|
*
|
|
@@ -1876,6 +1951,10 @@ class ToolHandler {
|
|
|
1876
1951
|
* it (the CLI does) and explore behaves exactly as before, untracked.
|
|
1877
1952
|
*/
|
|
1878
1953
|
async execute(toolName, args, sessionState) {
|
|
1954
|
+
const requestStartedAt = Date.now();
|
|
1955
|
+
args = { ...args };
|
|
1956
|
+
for (const key of [QUERY_PLAN_ARG, QUERY_DEADLINE_ARG, QUERY_STARTED_ARG, QUERY_INDEX_STATE_ARG, QUERY_FAST_ATTEMPTED_ARG, '_hgEvidenceMaxChars'])
|
|
1957
|
+
delete args[key];
|
|
1879
1958
|
try {
|
|
1880
1959
|
// Block the first tool call on the engine's post-open reconcile so we
|
|
1881
1960
|
// never serve rows for files deleted/edited while no MCP server was
|
|
@@ -1916,13 +1995,37 @@ class ToolHandler {
|
|
|
1916
1995
|
return check;
|
|
1917
1996
|
}
|
|
1918
1997
|
const projectPath = args.projectPath;
|
|
1998
|
+
if (toolName === 'homegraph_explore' && process.env.HOMEGRAPH_QUERY_PLANNER !== 'off') {
|
|
1999
|
+
const query = this.validateString(args.query, 'query');
|
|
2000
|
+
if (typeof query !== 'string')
|
|
2001
|
+
return query;
|
|
2002
|
+
const cg = this.getHomeGraph(projectPath);
|
|
2003
|
+
const gated = this.maybeDeepToolPhaseGate(cg, toolName);
|
|
2004
|
+
if (gated)
|
|
2005
|
+
return gated;
|
|
2006
|
+
// Refused repeats must not spend a model call or re-serve cached evidence.
|
|
2007
|
+
if (sessionState) {
|
|
2008
|
+
const repeat = (0, explore_repeat_guard_1.decideExploreRepeat)(sessionState.forProject(cg.getProjectRoot()), query);
|
|
2009
|
+
if (this.shouldRefuseRepeatedEvidence(repeat, cg.getProjectRoot())) {
|
|
2010
|
+
return this.textResult((0, explore_repeat_guard_1.formatExploreRepeatRefuse)(repeat, query));
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
args[QUERY_STARTED_ARG] = requestStartedAt;
|
|
2014
|
+
args[QUERY_DEADLINE_ARG] = Date.now() + (0, query_pool_1.resolveToolDeadlineMs)();
|
|
2015
|
+
args[QUERY_PLAN_ARG] = await (0, query_plan_1.planQuery)(query, {
|
|
2016
|
+
deadlineAt: args[QUERY_DEADLINE_ARG],
|
|
2017
|
+
taskContext: (0, query_plan_1.mergeQueryPlanTaskContext)(process.env.HOMEGRAPH_QUERY_TASK_CONTEXT, typeof args.taskContext === 'string' ? args.taskContext : undefined),
|
|
2018
|
+
validateAnchor: (anchor) => this.isExactPlanAnchor(cg, anchor),
|
|
2019
|
+
});
|
|
2020
|
+
args[QUERY_INDEX_STATE_ARG] = `${cg.getBuildPhase()}:${cg.getStats().nodeCount}:${cg.getLastIndexedAt() ?? 0}`;
|
|
2021
|
+
}
|
|
1919
2022
|
// Wrong question shapes → short Skip (before cache / graph work).
|
|
1920
2023
|
if (toolName === 'homegraph_explore' || toolName === 'homegraph_search') {
|
|
1921
2024
|
const qEarly = typeof args.query === 'string' ? args.query : '';
|
|
1922
2025
|
if (qEarly) {
|
|
1923
2026
|
const deferKind = (0, query_utils_1.queryShouldDeferToBuiltinTools)(qEarly);
|
|
1924
|
-
if (deferKind) {
|
|
1925
|
-
return this.textResult((0, query_utils_1.homegraphDeferGuidance)(deferKind, qEarly));
|
|
2027
|
+
if (deferKind && readQueryPlan(args)?.source !== 'llm') {
|
|
2028
|
+
return this.withQueryPlanDiagnostics(this.textResult((0, query_utils_1.homegraphDeferGuidance)(deferKind, qEarly)), args);
|
|
1926
2029
|
}
|
|
1927
2030
|
}
|
|
1928
2031
|
}
|
|
@@ -1939,7 +2042,9 @@ class ToolHandler {
|
|
|
1939
2042
|
// Session-tracked explore must not hit the MCP query cache: a cache hit
|
|
1940
2043
|
// would re-serve the first call's full source and defeat CG-18 dedup.
|
|
1941
2044
|
const skipCacheForSession = toolName === 'homegraph_explore' && !!sessionState;
|
|
1942
|
-
const
|
|
2045
|
+
const requestPlan = readQueryPlan(args);
|
|
2046
|
+
const cacheEnabled = !skipCacheForSession && requestPlan?.source !== 'llm'
|
|
2047
|
+
&& !requestPlan?.telemetry.fallbackReason && (0, query_cache_1.isMcpQueryCacheEnabled)() && (0, query_cache_1.isCacheableMcpTool)(toolName);
|
|
1943
2048
|
let cacheKey;
|
|
1944
2049
|
let cacheQueries;
|
|
1945
2050
|
let cacheIndex;
|
|
@@ -1958,8 +2063,12 @@ class ToolHandler {
|
|
|
1958
2063
|
}
|
|
1959
2064
|
cacheKey = (0, query_cache_1.buildMcpQueryCacheKey)(toolName, args, fileCount);
|
|
1960
2065
|
const cached = cacheIndex.getEntry(cacheQueries, cacheKey);
|
|
1961
|
-
|
|
1962
|
-
|
|
2066
|
+
const packMeta = cached?._meta?.homegraphEvidencePacks;
|
|
2067
|
+
const cachedFiles = cached?._meta?.homegraphEvidence?.files;
|
|
2068
|
+
if (cached && (!packMeta || (packMeta.status === 'complete'
|
|
2069
|
+
&& this.areEvidenceFilesCurrent(cachedFiles ?? [], cacheCg.getProjectRoot())))) {
|
|
2070
|
+
const diagnosed = this.withQueryPlanDiagnostics(cached, args, true);
|
|
2071
|
+
const withWorktree = this.withWorktreeNotice(diagnosed, projectPath);
|
|
1963
2072
|
return this.withStalenessNotice(withWorktree, projectPath);
|
|
1964
2073
|
}
|
|
1965
2074
|
}
|
|
@@ -1993,35 +2102,41 @@ class ToolHandler {
|
|
|
1993
2102
|
// main connection. Serving them here — before the query-pool offload —
|
|
1994
2103
|
// avoids cold-worker / wedged-daemon paths that otherwise surface as empty
|
|
1995
2104
|
// MCP client `-32001` (the handler itself is fine; the transport times out).
|
|
1996
|
-
if (toolName === 'homegraph_explore' || toolName === 'homegraph_search')
|
|
2105
|
+
if ((toolName === 'homegraph_explore' || toolName === 'homegraph_search')
|
|
2106
|
+
&& (!requestPlan || (requestPlan.source === 'rules' && requestPlan.steps.length === 1
|
|
2107
|
+
&& !requestPlan.telemetry.requestCount))) {
|
|
1997
2108
|
const q = typeof args.query === 'string' ? args.query : '';
|
|
1998
2109
|
if (q) {
|
|
1999
2110
|
try {
|
|
2000
2111
|
const cgFast = this.getHomeGraph(projectPath);
|
|
2001
2112
|
const rootFast = cgFast.getProjectRoot();
|
|
2002
|
-
const fast = (
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
?? (toolName === 'homegraph_explore' || toolName === 'homegraph_search'
|
|
2006
|
-
? this.tryFastInventoryExplore(cgFast, q, rootFast)
|
|
2007
|
-
: null)
|
|
2008
|
-
?? (toolName === 'homegraph_explore' || toolName === 'homegraph_search'
|
|
2009
|
-
? this.tryLightMechanismExplore(cgFast, q, rootFast)
|
|
2113
|
+
const fast = requestPlan ? this.tryPlannedFastPath(cgFast, requestPlan, rootFast) :
|
|
2114
|
+
(toolName === 'homegraph_explore'
|
|
2115
|
+
? this.trySpecializedExploreRoute(cgFast, q, rootFast)
|
|
2010
2116
|
: null)
|
|
2011
|
-
|
|
2117
|
+
?? (toolName === 'homegraph_explore' || toolName === 'homegraph_search'
|
|
2118
|
+
? this.tryFastInventoryExplore(cgFast, q, rootFast)
|
|
2119
|
+
: null)
|
|
2120
|
+
?? (toolName === 'homegraph_explore' || toolName === 'homegraph_search'
|
|
2121
|
+
? this.tryLightMechanismExplore(cgFast, q, rootFast)
|
|
2122
|
+
: null)
|
|
2123
|
+
?? this.tryCompactLocalSymbolExplore(cgFast, q, rootFast);
|
|
2012
2124
|
if (fast) {
|
|
2013
2125
|
// Fast explore must still file Partial/ANSWER meta into the session
|
|
2014
2126
|
// so explore + depth fuses see it (textResult-only paths used to skip).
|
|
2015
2127
|
let served = fast;
|
|
2016
|
-
if (toolName === 'homegraph_explore' && sessionState) {
|
|
2017
|
-
served = this.takeExploreEmission(this.ensureExploreEmission(fast, rootFast, q), sessionState);
|
|
2128
|
+
if ((toolName === 'homegraph_explore' && sessionState) || fast._meta?.homegraphEvidencePacks) {
|
|
2129
|
+
served = this.takeExploreEmission(this.ensureExploreEmission(fast, rootFast, q), toolName === 'homegraph_explore' ? sessionState : undefined);
|
|
2018
2130
|
}
|
|
2131
|
+
served = this.withQueryPlanDiagnostics(served, args);
|
|
2019
2132
|
if (cacheEnabled && cacheKey && cacheQueries && cacheIndex && !served.isError) {
|
|
2020
2133
|
cacheIndex.setEntry(cacheQueries, cacheKey, toolName, served);
|
|
2021
2134
|
}
|
|
2022
2135
|
const withWorktree = this.withWorktreeNotice(served, projectPath);
|
|
2023
2136
|
return this.withStalenessNotice(withWorktree, projectPath);
|
|
2024
2137
|
}
|
|
2138
|
+
if (requestPlan)
|
|
2139
|
+
args[QUERY_FAST_ATTEMPTED_ARG] = true;
|
|
2025
2140
|
}
|
|
2026
2141
|
catch {
|
|
2027
2142
|
// Not indexed / path issue — fall through to normal dispatch.
|
|
@@ -2037,7 +2152,7 @@ class ToolHandler {
|
|
|
2037
2152
|
// (a frozen main loop prevents setTimeout deadlines from firing → empty
|
|
2038
2153
|
// `-32001`). Fast-path surveys run inside the worker via executeReadTool.
|
|
2039
2154
|
const raw = await this.runReadToolWithDeadline(toolName, dispatchArgs);
|
|
2040
|
-
const result = this.takeExploreEmission(raw, sessionState);
|
|
2155
|
+
const result = this.withQueryPlanDiagnostics(this.takeExploreEmission(raw, sessionState), args);
|
|
2041
2156
|
if (sessionState
|
|
2042
2157
|
&& !result.isError
|
|
2043
2158
|
&& (toolName === 'homegraph_node'
|
|
@@ -2065,7 +2180,11 @@ class ToolHandler {
|
|
|
2065
2180
|
if (err instanceof PathRefusalError) {
|
|
2066
2181
|
return this.errorResult(err.message);
|
|
2067
2182
|
}
|
|
2068
|
-
|
|
2183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2184
|
+
if ((0, index_availability_1.isSqliteBusyMessage)(msg)) {
|
|
2185
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
2186
|
+
}
|
|
2187
|
+
return this.errorResult(`Tool execution failed: ${msg}. ` +
|
|
2069
2188
|
'This is an internal homegraph error — retry the call once; if it persists, ' +
|
|
2070
2189
|
'continue without homegraph for this task.');
|
|
2071
2190
|
}
|
|
@@ -2113,16 +2232,71 @@ class ToolHandler {
|
|
|
2113
2232
|
return null;
|
|
2114
2233
|
}
|
|
2115
2234
|
}
|
|
2235
|
+
/** A repeated query is duplicate evidence only while its source is unchanged. */
|
|
2236
|
+
shouldRefuseRepeatedEvidence(decision, projectRoot) {
|
|
2237
|
+
if (!decision.refuse)
|
|
2238
|
+
return false;
|
|
2239
|
+
// Source changes do not reset the total retrieval budget.
|
|
2240
|
+
if (decision.reason !== 'overlap')
|
|
2241
|
+
return true;
|
|
2242
|
+
const files = decision.matched?.files.filter((file) => file.bytes > 0 && file.ranges.length > 0) ?? [];
|
|
2243
|
+
return this.areEvidenceFilesCurrent(files, projectRoot);
|
|
2244
|
+
}
|
|
2245
|
+
/** Same bounded fingerprint check for repeat protection and cached source packs. */
|
|
2246
|
+
areEvidenceFilesCurrent(files, projectRoot) {
|
|
2247
|
+
if (files.length === 0 || files.length > 24)
|
|
2248
|
+
return false;
|
|
2249
|
+
let remainingBytes = 2 * 1024 * 1024;
|
|
2250
|
+
for (const file of files) {
|
|
2251
|
+
if (!file.fingerprint)
|
|
2252
|
+
return false;
|
|
2253
|
+
const absolute = (0, utils_1.validatePathWithinRoot)(projectRoot, file.path);
|
|
2254
|
+
if (!absolute)
|
|
2255
|
+
return false;
|
|
2256
|
+
let descriptor;
|
|
2257
|
+
try {
|
|
2258
|
+
const stat = (0, fs_1.statSync)(absolute);
|
|
2259
|
+
const limit = Math.min(1024 * 1024, remainingBytes);
|
|
2260
|
+
if (!stat.isFile() || stat.size > limit)
|
|
2261
|
+
return false;
|
|
2262
|
+
// Fixed-size reads also remain bounded if the file grows after stat.
|
|
2263
|
+
descriptor = (0, fs_1.openSync)(absolute, 'r');
|
|
2264
|
+
const buffer = Buffer.alloc(stat.size + 1);
|
|
2265
|
+
let bytes = 0;
|
|
2266
|
+
while (bytes < buffer.length) {
|
|
2267
|
+
const read = (0, fs_1.readSync)(descriptor, buffer, bytes, buffer.length - bytes, bytes);
|
|
2268
|
+
if (read === 0)
|
|
2269
|
+
break;
|
|
2270
|
+
bytes += read;
|
|
2271
|
+
}
|
|
2272
|
+
if (bytes !== stat.size)
|
|
2273
|
+
return false;
|
|
2274
|
+
remainingBytes -= bytes;
|
|
2275
|
+
if ((0, explore_dedup_1.fileFingerprint)(buffer.subarray(0, bytes).toString('utf8')) !== file.fingerprint)
|
|
2276
|
+
return false;
|
|
2277
|
+
}
|
|
2278
|
+
catch {
|
|
2279
|
+
return false;
|
|
2280
|
+
}
|
|
2281
|
+
finally {
|
|
2282
|
+
if (descriptor !== undefined) {
|
|
2283
|
+
try {
|
|
2284
|
+
(0, fs_1.closeSync)(descriptor);
|
|
2285
|
+
}
|
|
2286
|
+
catch { /* bookkeeping only */ }
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return true;
|
|
2291
|
+
}
|
|
2116
2292
|
/** Count a successful depth-tool call when the latest explore was Partial. */
|
|
2117
2293
|
noteDepthToolUse(args, sessionState) {
|
|
2118
2294
|
try {
|
|
2119
2295
|
const cg = this.getHomeGraph(args.projectPath);
|
|
2120
2296
|
const root = cg.getProjectRoot();
|
|
2121
2297
|
const prior = sessionState.forProject(root);
|
|
2122
|
-
const last = prior?.calls.length
|
|
2123
|
-
|
|
2124
|
-
: undefined;
|
|
2125
|
-
if (!last?.partial)
|
|
2298
|
+
const last = prior?.calls[prior.calls.length - 1];
|
|
2299
|
+
if (!last || (0, explore_session_state_1.inferExploreEvidenceStatus)(last) === 'complete')
|
|
2126
2300
|
return;
|
|
2127
2301
|
sessionState.recordDepthTool(root);
|
|
2128
2302
|
}
|
|
@@ -2142,17 +2316,22 @@ class ToolHandler {
|
|
|
2142
2316
|
const emission = result?.[explore_session_state_1.EXPLORE_EMISSION_KEY];
|
|
2143
2317
|
if (emission === undefined)
|
|
2144
2318
|
return result;
|
|
2319
|
+
emission.evidenceStatus = (0, explore_session_state_1.inferExploreEvidenceStatus)(emission);
|
|
2320
|
+
emission.partial = emission.evidenceStatus !== 'complete';
|
|
2321
|
+
result._meta = { ...result._meta, homegraphEvidence: { status: emission.evidenceStatus,
|
|
2322
|
+
files: emission.files, locatedNodes: emission.locatedNodes, coveredObligations: emission.coveredObligations,
|
|
2323
|
+
uncoveredObligations: emission.uncoveredObligations } };
|
|
2145
2324
|
delete result[explore_session_state_1.EXPLORE_EMISSION_KEY];
|
|
2146
2325
|
if (sessionState) {
|
|
2147
2326
|
try {
|
|
2148
2327
|
const prior = sessionState.forProject(emission.projectRoot);
|
|
2149
|
-
const priorPartials = (prior?.calls ?? []).filter((c) =>
|
|
2328
|
+
const priorPartials = (prior?.calls ?? []).filter((c) => (0, explore_session_state_1.inferExploreEvidenceStatus)(c) !== 'complete');
|
|
2150
2329
|
const metaPartial = emission.partial === true
|
|
2151
2330
|
|| (emission.partial === undefined
|
|
2152
2331
|
&& (0, explore_repeat_guard_1.inferExplorePartialMeta)(result.content?.[0]?.text ?? '').partial);
|
|
2153
2332
|
if (metaPartial && priorPartials.length >= 1) {
|
|
2154
2333
|
const text = result.content?.[0]?.text ?? '';
|
|
2155
|
-
if (text && !/Second Partial
|
|
2334
|
+
if (text && !/Second Partial/i.test(text)) {
|
|
2156
2335
|
const stop = (0, explore_repeat_guard_1.formatSecondPartialStopFooter)(emission.nextAnchor);
|
|
2157
2336
|
result = {
|
|
2158
2337
|
...result,
|
|
@@ -2185,13 +2364,19 @@ class ToolHandler {
|
|
|
2185
2364
|
* the success-shaped reply never flushed → empty client `-32001`.
|
|
2186
2365
|
*/
|
|
2187
2366
|
async runReadToolWithDeadline(toolName, args) {
|
|
2188
|
-
const deadlineMs =
|
|
2367
|
+
const deadlineMs = typeof args[QUERY_DEADLINE_ARG] === 'number'
|
|
2368
|
+
? Math.max(0, Math.min((0, query_pool_1.resolveToolDeadlineMs)(), args[QUERY_DEADLINE_ARG] - Date.now()))
|
|
2369
|
+
: (0, query_pool_1.resolveToolDeadlineMs)();
|
|
2370
|
+
if (deadlineMs <= 0)
|
|
2371
|
+
return this.deadlineBusyResult((0, query_pool_1.resolveToolDeadlineMs)(), args);
|
|
2189
2372
|
const light = toolName === 'homegraph_search'
|
|
2190
2373
|
|| toolName === 'homegraph_node'
|
|
2191
2374
|
|| toolName === 'homegraph_callers'
|
|
2192
2375
|
|| toolName === 'homegraph_callees'
|
|
2193
2376
|
|| toolName === 'homegraph_files'
|
|
2194
|
-
|| toolName === 'homegraph_project'
|
|
2377
|
+
|| toolName === 'homegraph_project'
|
|
2378
|
+
// Project maps may be built on demand: never run them on a read worker.
|
|
2379
|
+
|| (readQueryPlan(args)?.steps.some((step) => step.intent === 'overview') ?? false);
|
|
2195
2380
|
const work = () => {
|
|
2196
2381
|
if (!light && this.queryPool && this.queryPool.healthy) {
|
|
2197
2382
|
return this.queryPool.run(toolName, args, {
|
|
@@ -2246,6 +2431,14 @@ class ToolHandler {
|
|
|
2246
2431
|
*/
|
|
2247
2432
|
async executeReadTool(toolName, args) {
|
|
2248
2433
|
try {
|
|
2434
|
+
if (toolName !== 'homegraph_project' && toolName !== 'homegraph_status') {
|
|
2435
|
+
const gate = this.maybeDeepToolPhaseGate(this.getHomeGraph(args.projectPath), toolName);
|
|
2436
|
+
if (gate)
|
|
2437
|
+
return gate;
|
|
2438
|
+
}
|
|
2439
|
+
const plan = toolName === 'homegraph_explore' ? readQueryPlan(args) : undefined;
|
|
2440
|
+
if (plan)
|
|
2441
|
+
return await this.executeQueryPlan(args, plan);
|
|
2249
2442
|
// Compact inventory / one-symbol surveys — safe on the worker (keeps the
|
|
2250
2443
|
// daemon main loop free). Never run these unprotected on the MCP transport
|
|
2251
2444
|
// thread: they can block long enough for the client to emit empty `-32001`.
|
|
@@ -2264,7 +2457,11 @@ class ToolHandler {
|
|
|
2264
2457
|
if (err instanceof PathRefusalError) {
|
|
2265
2458
|
return this.errorResult(err.message);
|
|
2266
2459
|
}
|
|
2267
|
-
|
|
2460
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2461
|
+
if ((0, index_availability_1.isSqliteBusyMessage)(msg)) {
|
|
2462
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
2463
|
+
}
|
|
2464
|
+
return this.errorResult(`Tool execution failed: ${msg}. ` +
|
|
2268
2465
|
'This is an internal homegraph error — retry the call once; if it persists, ' +
|
|
2269
2466
|
'continue without homegraph for this task.');
|
|
2270
2467
|
}
|
|
@@ -2307,34 +2504,31 @@ class ToolHandler {
|
|
|
2307
2504
|
}
|
|
2308
2505
|
}
|
|
2309
2506
|
/**
|
|
2310
|
-
* Gate symbol/graph tools
|
|
2311
|
-
*
|
|
2312
|
-
* never isError — so agents keep using HomeGraph.
|
|
2507
|
+
* Gate symbol/graph tools by product index state (Spec 0032).
|
|
2508
|
+
* Success-shaped guidance — never isError — so agents keep using HomeGraph.
|
|
2313
2509
|
*/
|
|
2314
2510
|
maybeDeepToolPhaseGate(cg, _toolName) {
|
|
2315
|
-
const
|
|
2316
|
-
if (
|
|
2511
|
+
const state = (0, index_availability_1.resolveProductIndexState)(cg);
|
|
2512
|
+
if (state === 'full')
|
|
2317
2513
|
return null;
|
|
2514
|
+
if (state === 'syncing') {
|
|
2515
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
2516
|
+
}
|
|
2517
|
+
if (state === 'empty') {
|
|
2518
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('empty'));
|
|
2519
|
+
}
|
|
2520
|
+
// fast: allow deep tools once some symbols exist (partial index).
|
|
2318
2521
|
try {
|
|
2319
2522
|
if (cg.getStats().nodeCount > 0)
|
|
2320
2523
|
return null;
|
|
2321
2524
|
}
|
|
2322
|
-
catch {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
`Full symbol index is still building (phase=${phase}).`,
|
|
2328
|
-
'Use `homegraph_project` for the module/file map now, then retry this tool once indexing finishes.',
|
|
2329
|
-
].join('\n'));
|
|
2330
|
-
}
|
|
2331
|
-
if (phase === 'building_fast' || phase === 'none') {
|
|
2332
|
-
return this.textResult([
|
|
2333
|
-
'HomeGraph is still preparing the project map.',
|
|
2334
|
-
'Retry in a few seconds, or call `homegraph_project` once the fast build completes.',
|
|
2335
|
-
].join('\n'));
|
|
2525
|
+
catch (err) {
|
|
2526
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2527
|
+
if ((0, index_availability_1.isSqliteBusyMessage)(msg)) {
|
|
2528
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
2529
|
+
}
|
|
2336
2530
|
}
|
|
2337
|
-
return
|
|
2531
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('fast'));
|
|
2338
2532
|
}
|
|
2339
2533
|
async handleSearch(args) {
|
|
2340
2534
|
const query = this.validateString(args.query, 'query');
|
|
@@ -5131,9 +5325,273 @@ class ToolHandler {
|
|
|
5131
5325
|
lines.push('');
|
|
5132
5326
|
return { section: lines.join('\n'), hitCount: locs.length + edges.length + extendsHits.length };
|
|
5133
5327
|
}
|
|
5134
|
-
/**
|
|
5135
|
-
|
|
5136
|
-
|
|
5328
|
+
/** Exact validation for model-proposed names: a fuzzy hit is not proof. */
|
|
5329
|
+
isExactPlanAnchor(cg, anchor) {
|
|
5330
|
+
if (!anchor || anchor.length > 256)
|
|
5331
|
+
return false;
|
|
5332
|
+
try {
|
|
5333
|
+
const matches = cg.searchNodes(anchor, { limit: 12 });
|
|
5334
|
+
return matches.some(({ node }) => node.name === anchor || node.qualifiedName === anchor
|
|
5335
|
+
|| node.filePath.replace(/\\/g, '/') === anchor.replace(/\\/g, '/'));
|
|
5336
|
+
}
|
|
5337
|
+
catch {
|
|
5338
|
+
return false;
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
/** Bind only declarations returned by this step, not input or global fuzzy hits. */
|
|
5342
|
+
locatedPlanBindings(cg, result, inherited) {
|
|
5343
|
+
const seen = new Set(inherited.map((binding) => binding.id));
|
|
5344
|
+
const declarations = new Set(inherited.map((binding) => `${binding.filePath}:${binding.startLine}:${binding.name}`));
|
|
5345
|
+
const located = [];
|
|
5346
|
+
for (const receipt of (result[explore_session_state_1.EXPLORE_EMISSION_KEY]?.locatedNodes ?? []).slice(0, 32)) {
|
|
5347
|
+
if (seen.has(receipt.id) || declarations.has(`${receipt.filePath}:${receipt.startLine}:${receipt.name}`))
|
|
5348
|
+
continue;
|
|
5349
|
+
const node = cg.getNode(receipt.id);
|
|
5350
|
+
if (!node || ['file', 'import', 'export', 'parameter'].includes(node.kind)
|
|
5351
|
+
|| node.name === 'constructor' || node.name.startsWith('%AM') || node.filePath.includes('@dummy')
|
|
5352
|
+
|| node.name !== receipt.name || node.filePath !== receipt.filePath || node.startLine !== receipt.startLine
|
|
5353
|
+
|| node.qualifiedName !== receipt.qualifiedName)
|
|
5354
|
+
continue;
|
|
5355
|
+
seen.add(node.id);
|
|
5356
|
+
declarations.add(`${node.filePath}:${node.startLine}:${node.name}`);
|
|
5357
|
+
located.push({ id: node.id, name: node.name, qualifiedName: node.qualifiedName,
|
|
5358
|
+
filePath: node.filePath, startLine: node.startLine });
|
|
5359
|
+
if (located.length >= 8)
|
|
5360
|
+
break;
|
|
5361
|
+
}
|
|
5362
|
+
return located;
|
|
5363
|
+
}
|
|
5364
|
+
withQueryPlanDiagnostics(result, args, cacheHit = false) {
|
|
5365
|
+
const plan = readQueryPlan(args);
|
|
5366
|
+
if (!plan)
|
|
5367
|
+
return result;
|
|
5368
|
+
const prior = result._meta?.homegraphQueryPlan;
|
|
5369
|
+
const started = typeof args[QUERY_STARTED_ARG] === 'number' ? args[QUERY_STARTED_ARG] : Date.now();
|
|
5370
|
+
return { ...result, _meta: { ...result._meta, homegraphQueryPlan: {
|
|
5371
|
+
...prior, version: plan.version, source: plan.source, intent: plan.intent, route: plan.route,
|
|
5372
|
+
confidence: plan.confidence,
|
|
5373
|
+
plannerSeeds: { anchors: plan.anchors, searchTerms: plan.searchTerms, literalTexts: plan.literalTexts,
|
|
5374
|
+
sourceScope: plan.sourceScope, relation: plan.relation },
|
|
5375
|
+
hasTaskContext: !!plan.taskContext,
|
|
5376
|
+
matchedFeatures: Object.entries(plan.features).filter(([, matched]) => matched).map(([name]) => name).slice(0, 12),
|
|
5377
|
+
planningMs: plan.telemetry.durationMs, durationMs: Math.max(plan.telemetry.durationMs, Date.now() - started),
|
|
5378
|
+
modelRequests: plan.telemetry.requestCount ?? 0,
|
|
5379
|
+
planningEligible: plan.telemetry.decision?.eligible,
|
|
5380
|
+
planningReason: plan.telemetry.decision?.reason,
|
|
5381
|
+
skip_reason: plan.telemetry.decision?.eligible === false ? plan.telemetry.decision.reason : undefined,
|
|
5382
|
+
ruleRoute: plan.telemetry.decision?.ruleRoute,
|
|
5383
|
+
inputTokens: plan.telemetry.inputTokens ?? (plan.telemetry.requestCount ? null : 0),
|
|
5384
|
+
outputTokens: plan.telemetry.outputTokens ?? (plan.telemetry.requestCount ? null : 0),
|
|
5385
|
+
fallbackReason: plan.telemetry.fallbackReason, cacheHit,
|
|
5386
|
+
steps: cacheHit ? [] : prior?.steps ?? [{ id: plan.steps[0]?.id, intent: plan.intent,
|
|
5387
|
+
status: result.isError ? 'failed'
|
|
5388
|
+
: (0, explore_repeat_guard_1.inferExplorePartialMeta)(result.content[0]?.text ?? '').partial ? 'partial'
|
|
5389
|
+
: /Status: (?:no_indexed_evidence|not_surveyed)|No relevant code|Skip HomeGraph/i.test(result.content[0]?.text ?? '')
|
|
5390
|
+
? 'no_evidence' : 'evidence', resolvedAnchors: [] }],
|
|
5391
|
+
} } };
|
|
5392
|
+
}
|
|
5393
|
+
/** Select once; legacy section builders consume the same canonical query/features. */
|
|
5394
|
+
tryPlannedFastPath(cg, plan, root, evidenceMaxChars) {
|
|
5395
|
+
const query = plan.canonicalQuery;
|
|
5396
|
+
if (plan.route === 'usages' || plan.route === 'modules' || plan.route === 'native') {
|
|
5397
|
+
return this.runSpecializedExploreRoute(plan.route, cg, query, root, plan);
|
|
5398
|
+
}
|
|
5399
|
+
// These legacy paths re-extract seeds from text and cannot consume bound
|
|
5400
|
+
// node identity / step hints. Model general/flow plans use full explore;
|
|
5401
|
+
// rule/default and specialized routes retain their existing fast behavior.
|
|
5402
|
+
if (plan.source === 'llm' && (plan.intent === 'general' || plan.intent === 'flow'))
|
|
5403
|
+
return null;
|
|
5404
|
+
return this.tryFastInventoryExplore(cg, query, root, plan)
|
|
5405
|
+
?? this.tryLightMechanismExplore(cg, query, root, plan, evidenceMaxChars)
|
|
5406
|
+
?? this.tryCompactLocalSymbolExplore(cg, query, root, plan, evidenceMaxChars);
|
|
5407
|
+
}
|
|
5408
|
+
/** Internal execution only: no recursive MCP calls, model requests or new deadline. */
|
|
5409
|
+
async executePlannedStep(args, plan) {
|
|
5410
|
+
const cg = this.getHomeGraph(args.projectPath);
|
|
5411
|
+
if (plan.intent === 'overview') {
|
|
5412
|
+
return this.handleProject({ projectPath: args.projectPath });
|
|
5413
|
+
}
|
|
5414
|
+
const gate = this.maybeDeepToolPhaseGate(cg, 'homegraph_explore');
|
|
5415
|
+
if (gate)
|
|
5416
|
+
return gate;
|
|
5417
|
+
const query = plan.canonicalQuery;
|
|
5418
|
+
const defer = (0, query_utils_1.queryShouldDeferToBuiltinTools)(query);
|
|
5419
|
+
if (defer && plan.source === 'rules')
|
|
5420
|
+
return this.textResult((0, query_utils_1.homegraphDeferGuidance)(defer, query));
|
|
5421
|
+
// A typed relationship still needs a real target. Discover source from the
|
|
5422
|
+
// same bounded hints first; do not turn an unanchored usage request into an
|
|
5423
|
+
// empty survey or claim that its reference obligation has been covered.
|
|
5424
|
+
if (plan.source === 'llm' && plan.route === 'usages'
|
|
5425
|
+
&& !plan.anchors.length && !plan.bindings?.length) {
|
|
5426
|
+
const result = await this.handleExplore({ ...args, query, [QUERY_PLAN_ARG]: plan });
|
|
5427
|
+
const served = this.ensureExploreEmission(result, cg.getProjectRoot(), plan.originalQuery);
|
|
5428
|
+
const emission = served[explore_session_state_1.EXPLORE_EMISSION_KEY];
|
|
5429
|
+
emission.partial = true;
|
|
5430
|
+
emission.evidenceStatus = emission.sourceBytes > 0 ? 'partial' : 'empty';
|
|
5431
|
+
emission.coveredObligations = [];
|
|
5432
|
+
emission.uncoveredObligations = [plan.relation ?? 'incoming_references'];
|
|
5433
|
+
if (served.content[0]?.type === 'text')
|
|
5434
|
+
served.content[0].text =
|
|
5435
|
+
'**Reference target discovery — partial**\nSource candidates follow. Verify the relevant target before surveying its references; the reference obligation remains open.\n\n'
|
|
5436
|
+
+ served.content[0].text;
|
|
5437
|
+
return served;
|
|
5438
|
+
}
|
|
5439
|
+
const fast = args[QUERY_FAST_ATTEMPTED_ARG] === true && plan.source === 'rules'
|
|
5440
|
+
? null : this.tryPlannedFastPath(cg, plan, cg.getProjectRoot(), args._hgEvidenceMaxChars);
|
|
5441
|
+
if (fast)
|
|
5442
|
+
return fast;
|
|
5443
|
+
return this.handleExplore({ ...args, query, [QUERY_PLAN_ARG]: plan });
|
|
5444
|
+
}
|
|
5445
|
+
async executeQueryPlan(args, plan) {
|
|
5446
|
+
const cg = this.getHomeGraph(args.projectPath);
|
|
5447
|
+
const root = cg.getProjectRoot();
|
|
5448
|
+
const deadline = typeof args[QUERY_DEADLINE_ARG] === 'number'
|
|
5449
|
+
? args[QUERY_DEADLINE_ARG] : Date.now() + (0, query_pool_1.resolveToolDeadlineMs)();
|
|
5450
|
+
const prior = (0, explore_session_state_1.viewForProject)((0, explore_session_state_1.readExploreSessionView)(args), root);
|
|
5451
|
+
const repeat = (0, explore_repeat_guard_1.decideExploreRepeat)(prior, plan.originalQuery);
|
|
5452
|
+
if (this.shouldRefuseRepeatedEvidence(repeat, root)) {
|
|
5453
|
+
return this.textResult((0, explore_repeat_guard_1.formatExploreRepeatRefuse)(repeat, plan.originalQuery));
|
|
5454
|
+
}
|
|
5455
|
+
const multi = plan.steps.length > 1;
|
|
5456
|
+
let outputBudget = Math.min(MAX_OUTPUT_LENGTH, getExploreOutputBudget(cg.getStats().fileCount).maxOutputChars);
|
|
5457
|
+
if (!Number.isFinite(outputBudget))
|
|
5458
|
+
outputBudget = MAX_OUTPUT_LENGTH;
|
|
5459
|
+
const constraints = [plan.originalQuery, plan.taskContext].filter(Boolean).join('\n');
|
|
5460
|
+
const constraintLimit = Math.min(2000, Math.floor(outputBudget / 5));
|
|
5461
|
+
const constraintNotice = constraints.length > constraintLimit
|
|
5462
|
+
? constraints.slice(0, constraintLimit) + '\n[Constraint display shortened; the original task remains authoritative.]'
|
|
5463
|
+
: constraints;
|
|
5464
|
+
const preamble = [
|
|
5465
|
+
'**Planned exploration — Partial locator**',
|
|
5466
|
+
'> **Partial locator** — scoped subquestion evidence, not proof that every requirement in the original question is covered.',
|
|
5467
|
+
'**Task constraints (not search seeds or verified source evidence)**\n' + constraintNotice,
|
|
5468
|
+
].join('\n\n');
|
|
5469
|
+
const pieces = [];
|
|
5470
|
+
const diagnostics = [];
|
|
5471
|
+
const bindings = new Map();
|
|
5472
|
+
const files = [];
|
|
5473
|
+
const seenQueries = new Set();
|
|
5474
|
+
let single;
|
|
5475
|
+
let used = multi ? preamble.length + 400 : 400;
|
|
5476
|
+
for (const step of plan.steps.slice(0, 3)) {
|
|
5477
|
+
const started = Date.now();
|
|
5478
|
+
const diagnostic = { id: step.id, intent: step.intent, status: 'pending', resolvedAnchors: [],
|
|
5479
|
+
locatedNodes: [], durationMs: 0 };
|
|
5480
|
+
diagnostics.push(diagnostic);
|
|
5481
|
+
if (Date.now() >= deadline || used >= outputBudget) {
|
|
5482
|
+
diagnostic.status = 'budget_exhausted';
|
|
5483
|
+
pieces.push(`**Step ${step.id}: ${step.intent}** — not executed: shared request budget exhausted.`);
|
|
5484
|
+
continue;
|
|
5485
|
+
}
|
|
5486
|
+
if (step.dependsOn.some((id) => !bindings.get(id)?.length)) {
|
|
5487
|
+
diagnostic.status = 'dependency_unresolved';
|
|
5488
|
+
pieces.push(`**Step ${step.id}: ${step.intent}** — not executed: predecessor supplied no resolved symbol anchors.`);
|
|
5489
|
+
continue;
|
|
5490
|
+
}
|
|
5491
|
+
const resolved = step.dependsOn.flatMap((id) => bindings.get(id) ?? []);
|
|
5492
|
+
// Keep rule-only single queries byte-compatible; only model subplans are adapted.
|
|
5493
|
+
const compiled = plan.source === 'rules' && !multi ? plan : {
|
|
5494
|
+
...(0, query_plan_1.compileQueryPlanStep)(plan, step, resolved.map((node) => node.qualifiedName || node.name)), bindings: resolved,
|
|
5495
|
+
};
|
|
5496
|
+
if (seenQueries.has(`${compiled.intent}:${compiled.canonicalQuery}`)) {
|
|
5497
|
+
diagnostic.status = 'duplicate_skipped';
|
|
5498
|
+
pieces.push(`**Step ${step.id}: ${step.intent}** — duplicate query skipped.`);
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5501
|
+
seenQueries.add(`${compiled.intent}:${compiled.canonicalQuery}`);
|
|
5502
|
+
try {
|
|
5503
|
+
const stepCap = Math.max(0, Math.floor((outputBudget - used) / Math.max(1, plan.steps.length - diagnostics.length + 1)) - 100);
|
|
5504
|
+
const result = await this.executePlannedStep({ ...args, [QUERY_DEADLINE_ARG]: deadline,
|
|
5505
|
+
...(multi ? { _hgEvidenceMaxChars: Math.max(0, stepCap - Math.min(1600, Math.floor(stepCap / 2)) - 2) } : {}),
|
|
5506
|
+
}, compiled);
|
|
5507
|
+
const body = result.content.map((part) => part.text).join('\n');
|
|
5508
|
+
const candidates = result.isError ? [] : this.locatedPlanBindings(cg, result, resolved);
|
|
5509
|
+
const childEmission = result[explore_session_state_1.EXPLORE_EMISSION_KEY];
|
|
5510
|
+
const hasLocalEvidence = candidates.length > 0 || (childEmission?.sourceBytes ?? 0) > 0;
|
|
5511
|
+
diagnostic.status = result.isError ? 'failed'
|
|
5512
|
+
: childEmission?.evidenceStatus === 'complete' ? 'evidence'
|
|
5513
|
+
: hasLocalEvidence ? (childEmission?.partial ? 'partial' : 'evidence')
|
|
5514
|
+
: /HarmonyOS SDK API|ohos-sdk:/.test(body) ? 'sdk_only' : 'no_evidence';
|
|
5515
|
+
if (!multi) {
|
|
5516
|
+
diagnostic.locatedNodes = candidates;
|
|
5517
|
+
diagnostic.resolvedAnchors = candidates.map((node) => node.name);
|
|
5518
|
+
single = result;
|
|
5519
|
+
break;
|
|
5520
|
+
}
|
|
5521
|
+
// A child cannot declare the original multi-part question answered.
|
|
5522
|
+
let fenced = false;
|
|
5523
|
+
const neutralBody = body.split('\n').filter((line) => {
|
|
5524
|
+
if (/^\s*```/.test(line)) {
|
|
5525
|
+
fenced = !fenced;
|
|
5526
|
+
return true;
|
|
5527
|
+
}
|
|
5528
|
+
return fenced || !/ANSWER NOW|Compact local explore complete|Do \*\*not\*\* (?:Read|Grep)|ONE tighter|ONE narrow/i.test(line);
|
|
5529
|
+
}).join('\n');
|
|
5530
|
+
const cap = Math.max(0, Math.floor((outputBudget - used) / Math.max(1, plan.steps.length - diagnostics.length + 1)) - 100);
|
|
5531
|
+
// Keep the grounded location receipt before source trimming. Only the
|
|
5532
|
+
// identities visibly delivered here may be consumed by the next step.
|
|
5533
|
+
let receipt = '';
|
|
5534
|
+
const visibleBindings = [];
|
|
5535
|
+
const inline = (value) => value.replace(/[`\r\n]/g, ' ');
|
|
5536
|
+
for (const candidate of candidates) {
|
|
5537
|
+
const next = (receipt ? '' : '**Located source candidates — relevance still needs verification**\n')
|
|
5538
|
+
+ `- \`${inline(candidate.qualifiedName || candidate.name)}\` — \`${inline(candidate.filePath)}:${candidate.startLine}\`\n`;
|
|
5539
|
+
if (receipt.length + next.length > Math.min(1600, Math.floor(cap / 2)))
|
|
5540
|
+
break;
|
|
5541
|
+
receipt += next;
|
|
5542
|
+
visibleBindings.push(candidate);
|
|
5543
|
+
}
|
|
5544
|
+
diagnostic.locatedNodes = visibleBindings;
|
|
5545
|
+
diagnostic.resolvedAnchors = visibleBindings.map((node) => node.name);
|
|
5546
|
+
bindings.set(step.id, visibleBindings);
|
|
5547
|
+
const bodyCap = Math.max(0, cap - receipt.length - 2);
|
|
5548
|
+
const trimmed = neutralBody.length > bodyCap;
|
|
5549
|
+
const atomic = !!result._meta?.homegraphEvidencePacks;
|
|
5550
|
+
const kept = atomic && trimmed
|
|
5551
|
+
? '[Partial source: complete evidence pack omitted by the shared output budget.]'.slice(0, bodyCap)
|
|
5552
|
+
: atomic ? neutralBody : (0, evidence_rendering_1.trimEvidenceAtLine)(neutralBody, bodyCap);
|
|
5553
|
+
if (atomic && trimmed) {
|
|
5554
|
+
receipt = '';
|
|
5555
|
+
diagnostic.locatedNodes = [];
|
|
5556
|
+
diagnostic.resolvedAnchors = [];
|
|
5557
|
+
bindings.set(step.id, []);
|
|
5558
|
+
}
|
|
5559
|
+
if (trimmed)
|
|
5560
|
+
diagnostic.status = 'partial';
|
|
5561
|
+
else
|
|
5562
|
+
files.push(...(result[explore_session_state_1.EXPLORE_EMISSION_KEY]?.files ?? []));
|
|
5563
|
+
const piece = `**Step ${step.id}: ${step.intent}** (${diagnostic.status})\n${receipt}\n${kept}`;
|
|
5564
|
+
pieces.push(piece);
|
|
5565
|
+
used += piece.length;
|
|
5566
|
+
}
|
|
5567
|
+
catch (error) {
|
|
5568
|
+
if (error instanceof PathRefusalError)
|
|
5569
|
+
throw error;
|
|
5570
|
+
diagnostic.status = 'failed';
|
|
5571
|
+
pieces.push(`**Step ${step.id}: ${step.intent}** — retrieval failed; other step evidence is retained.`);
|
|
5572
|
+
}
|
|
5573
|
+
finally {
|
|
5574
|
+
diagnostic.durationMs = Date.now() - started;
|
|
5575
|
+
}
|
|
5576
|
+
// Yield between synchronous graph stages so deadlines and MCP I/O can run.
|
|
5577
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
5578
|
+
}
|
|
5579
|
+
const result = single ?? this.textResult([preamble, ...pieces].join('\n\n').slice(0, outputBudget));
|
|
5580
|
+
const served = this.ensureExploreEmission(result, root, plan.originalQuery);
|
|
5581
|
+
const emission = served[explore_session_state_1.EXPLORE_EMISSION_KEY];
|
|
5582
|
+
emission.query = plan.originalQuery;
|
|
5583
|
+
if (multi) {
|
|
5584
|
+
emission.partial = true;
|
|
5585
|
+
emission.evidenceStatus = 'partial';
|
|
5586
|
+
emission.coveredObligations = diagnostics.filter((step) => step.status === 'evidence').map((step) => step.id);
|
|
5587
|
+
emission.uncoveredObligations = diagnostics.filter((step) => step.status !== 'evidence').map((step) => step.id);
|
|
5588
|
+
emission.files = files;
|
|
5589
|
+
emission.sourceBytes = files.reduce((sum, file) => sum + file.bytes, 0);
|
|
5590
|
+
}
|
|
5591
|
+
served._meta = { ...served._meta, homegraphQueryPlan: { steps: diagnostics } };
|
|
5592
|
+
return served;
|
|
5593
|
+
}
|
|
5594
|
+
/** Main-thread fast path for inventory surveys — skips the worker queue. */
|
|
5137
5595
|
tryFastPathResult(toolName, args) {
|
|
5138
5596
|
const query = args.query;
|
|
5139
5597
|
if (typeof query !== 'string')
|
|
@@ -5181,11 +5639,13 @@ class ToolHandler {
|
|
|
5181
5639
|
: null;
|
|
5182
5640
|
}
|
|
5183
5641
|
/** Run exactly one bounded survey family and expose the selected route. */
|
|
5184
|
-
runSpecializedExploreRoute(route, cg, query, projectRoot) {
|
|
5642
|
+
runSpecializedExploreRoute(route, cg, query, projectRoot, plan) {
|
|
5185
5643
|
let section = '';
|
|
5186
5644
|
let status = 'no_indexed_evidence';
|
|
5187
5645
|
let coverage = '';
|
|
5188
5646
|
if (route === 'modules') {
|
|
5647
|
+
if (plan?.relation === 'module_imports')
|
|
5648
|
+
return this.renderModuleImports(cg, plan);
|
|
5189
5649
|
const manifestResult = this.buildFocusedModuleManifestSection(projectRoot, query);
|
|
5190
5650
|
const graphResult = manifestResult.manifestCount === 0
|
|
5191
5651
|
? this.buildModuleDependencySurveySection(cg, query)
|
|
@@ -5203,10 +5663,10 @@ class ToolHandler {
|
|
|
5203
5663
|
coverage = 'named path/Type NAPI export registrations only; no domain file dump was built';
|
|
5204
5664
|
}
|
|
5205
5665
|
else {
|
|
5206
|
-
const apiUsage = (
|
|
5666
|
+
const apiUsage = planFeature(plan, 'shouldBuildApiUsageSurvey', query, query_utils_1.shouldBuildApiUsageSurvey)
|
|
5207
5667
|
? this.buildApiUsageSection(cg, query, projectRoot)
|
|
5208
5668
|
: { section: '', fileCount: 0 };
|
|
5209
|
-
const memberUsage = (
|
|
5669
|
+
const memberUsage = planFeature(plan, 'shouldBuildMemberSurvey', query, query_utils_1.shouldBuildMemberSurvey)
|
|
5210
5670
|
&& !((0, query_utils_1.queryAsFieldUsageSurvey)(query) && apiUsage.fileCount > 0)
|
|
5211
5671
|
? this.buildMemberSurveySection(cg, query, projectRoot)
|
|
5212
5672
|
: '';
|
|
@@ -5224,13 +5684,43 @@ class ToolHandler {
|
|
|
5224
5684
|
|| (status === 'not_surveyed'
|
|
5225
5685
|
? '- No survey ran: this query carries no symbol name to scan. Re-run naming the symbol(s), or use `homegraph_explore`.'
|
|
5226
5686
|
: '- No matching evidence was found in the current index for this focused survey.');
|
|
5227
|
-
|
|
5687
|
+
const text = this.truncateOutput([
|
|
5228
5688
|
`**HomeGraph specialized route: ${route}**`,
|
|
5229
5689
|
`Status: ${status}`,
|
|
5230
5690
|
`Coverage: ${coverage}.`,
|
|
5231
5691
|
'',
|
|
5232
5692
|
body,
|
|
5233
|
-
].join('\n'))
|
|
5693
|
+
].join('\n'));
|
|
5694
|
+
return this.exploreResult(text, { projectRoot, query, files: [], sourceBytes: 0,
|
|
5695
|
+
responseBytes: text.length, evidenceStatus: status === 'complete' ? 'complete' : 'empty',
|
|
5696
|
+
partial: status !== 'complete', coveredObligations: status === 'complete' ? [plan?.relation ?? route] : [],
|
|
5697
|
+
uncoveredObligations: status !== 'complete' ? [plan?.relation ?? route] : [] });
|
|
5698
|
+
}
|
|
5699
|
+
/** Directed file import witnesses for an explicit module-import relation. */
|
|
5700
|
+
renderModuleImports(cg, plan) {
|
|
5701
|
+
const scope = [...new Set([...plan.anchors, ...(plan.bindings ?? []).map(node => node.filePath)])]
|
|
5702
|
+
.map(value => value.replace(/\\/g, '/')).filter(Boolean);
|
|
5703
|
+
const paths = cg.getFiles().map(file => file.path).filter(file => scope.some(anchor => file === anchor || file.startsWith(anchor + '/') || file.split('/').includes(anchor)));
|
|
5704
|
+
const rows = [];
|
|
5705
|
+
let scanned = 0;
|
|
5706
|
+
for (const file of paths.slice(0, 120)) {
|
|
5707
|
+
scanned++;
|
|
5708
|
+
for (const target of cg.getFileDependencies(file)) {
|
|
5709
|
+
rows.push(`- \`${file}\` imports → \`${target}\``);
|
|
5710
|
+
if (rows.length >= 40)
|
|
5711
|
+
break;
|
|
5712
|
+
}
|
|
5713
|
+
if (rows.length >= 40)
|
|
5714
|
+
break;
|
|
5715
|
+
}
|
|
5716
|
+
const partial = scanned < paths.length || rows.length >= 40;
|
|
5717
|
+
const text = ['**Directed module imports**',
|
|
5718
|
+
rows.length ? rows.join('\n') : 'No indexed import edges found for the supplied module paths.',
|
|
5719
|
+
`Coverage: ${scanned} of ${paths.length} scoped files scanned; cycle analysis was not requested.`].join('\n\n');
|
|
5720
|
+
return this.exploreResult(text, { projectRoot: cg.getProjectRoot(), query: plan.originalQuery,
|
|
5721
|
+
files: [], sourceBytes: 0, responseBytes: text.length,
|
|
5722
|
+
evidenceStatus: !rows.length ? 'empty' : partial ? 'partial' : 'complete',
|
|
5723
|
+
partial: partial || !rows.length });
|
|
5234
5724
|
}
|
|
5235
5725
|
/** Usage sites for a bare symbol bag, including non-call textual references. */
|
|
5236
5726
|
buildBareSymbolUsageSection(cg, query, projectRoot) {
|
|
@@ -5392,8 +5882,8 @@ class ToolHandler {
|
|
|
5392
5882
|
/**
|
|
5393
5883
|
* Fast inventory-only explore — skips findRelevantContext for survey/caller/dependency queries.
|
|
5394
5884
|
*/
|
|
5395
|
-
tryFastInventoryExplore(cg, query, projectRoot) {
|
|
5396
|
-
if (!(
|
|
5885
|
+
tryFastInventoryExplore(cg, query, projectRoot, plan) {
|
|
5886
|
+
if (!planFeature(plan, 'shouldTryFastInventoryExplore', query, query_utils_1.shouldTryFastInventoryExplore))
|
|
5397
5887
|
return null;
|
|
5398
5888
|
// Multi-Type dependency asks: inventory-only early exit (avoids fat compact / busy timeout).
|
|
5399
5889
|
if ((0, query_utils_1.queryAsMultiTypeDependencySurvey)(query)) {
|
|
@@ -5759,9 +6249,29 @@ class ToolHandler {
|
|
|
5759
6249
|
}
|
|
5760
6250
|
return finishCompact('Inventory sections above are complete for this query. **ANSWER NOW.**');
|
|
5761
6251
|
}
|
|
5762
|
-
/**
|
|
5763
|
-
|
|
5764
|
-
|
|
6252
|
+
/** Pack already-located ArkTS evidence before legacy windowing can cut it. */
|
|
6253
|
+
tryArktsEvidenceExplore(cg, query, projectRoot, nodes, focusIds, maxChars, maxFiles, plan) {
|
|
6254
|
+
if (process.env.HOMEGRAPH_ARKTS_EVIDENCE_PACKS === '0')
|
|
6255
|
+
return null;
|
|
6256
|
+
if (!nodes.some(n => /\.ets$/i.test(n.filePath) && !n.filePath.startsWith('ohos-sdk:')))
|
|
6257
|
+
return null;
|
|
6258
|
+
const budget = getExploreOutputBudget(cg.getStats().fileCount);
|
|
6259
|
+
const queryPaths = process.env.HOMEGRAPH_ARKTS_QUERY_PATHS !== '0';
|
|
6260
|
+
if (queryPaths)
|
|
6261
|
+
nodes = (0, evidence_paths_1.completeEvidencePathCandidates)(query, nodes, (name, limit) => cg.getQueryBuilder().getNodesByQualifiedNameExact(name, limit), plan);
|
|
6262
|
+
const pathSearch = !queryPaths ? undefined : (0, evidence_paths_1.searchEvidencePaths)({
|
|
6263
|
+
getNode: id => cg.getNode(id),
|
|
6264
|
+
getEdges: (id, direction, kinds, limit, preferred) => cg.getQueryBuilder().getEvidenceEdges(id, direction, kinds, limit, preferred),
|
|
6265
|
+
}, (0, evidence_paths_1.resolveEvidencePathGoal)(query, nodes, focusIds, plan));
|
|
6266
|
+
const result = (0, arkts_evidence_packs_1.buildArktsEvidencePacks)(cg, { projectRoot, query, nodes, focusIds,
|
|
6267
|
+
maxChars: Math.min(budget.maxOutputChars, maxChars ?? budget.maxOutputChars), maxFiles, pathSearch });
|
|
6268
|
+
if (!result)
|
|
6269
|
+
return null;
|
|
6270
|
+
// The pack renderer already supplies neutral guidance and exact source bytes.
|
|
6271
|
+
return { content: [{ type: 'text', text: result.text }], [explore_session_state_1.EXPLORE_EMISSION_KEY]: result.emission,
|
|
6272
|
+
_meta: { homegraphEvidencePacks: result.metadata } };
|
|
6273
|
+
}
|
|
6274
|
+
/** Render a compact symbol-bounded slice on the legacy mechanism path. */
|
|
5765
6275
|
renderLightMechanismSource(projectRoot, filePath, nodes, maxChars) {
|
|
5766
6276
|
const absPath = (0, utils_1.validatePathWithinRoot)(projectRoot, filePath);
|
|
5767
6277
|
if (!absPath || !(0, fs_1.existsSync)(absPath))
|
|
@@ -5797,8 +6307,8 @@ class ToolHandler {
|
|
|
5797
6307
|
* findRelevantContext. Fast enough for MCP budget; complete enough to avoid
|
|
5798
6308
|
* agent grep/read loops (token savings).
|
|
5799
6309
|
*/
|
|
5800
|
-
tryLightMechanismExplore(cg, query, projectRoot) {
|
|
5801
|
-
if (!(
|
|
6310
|
+
tryLightMechanismExplore(cg, query, projectRoot, plan, evidenceMaxChars) {
|
|
6311
|
+
if (!planFeature(plan, 'shouldTryLightMechanismExplore', query, query_utils_1.shouldTryLightMechanismExplore))
|
|
5802
6312
|
return null;
|
|
5803
6313
|
const STRUCTURE_KINDS = new Set(['class', 'struct', 'interface', 'component', 'method', 'function']);
|
|
5804
6314
|
const isTestPath = (p) => /(^|\/)(tests?|spec)\//i.test(p) || /\.(test|spec)\./i.test(p);
|
|
@@ -5968,6 +6478,9 @@ class ToolHandler {
|
|
|
5968
6478
|
const seeds = (0, query_utils_1.extractMechanismEntrySeeds)(query);
|
|
5969
6479
|
const flow = this.buildFlowFromNamedSymbols(cg, `${query} ${seeds.join(' ')}`);
|
|
5970
6480
|
const hasFlowPath = flow.pathNodeIds.size > 1;
|
|
6481
|
+
const evidence = this.tryArktsEvidenceExplore(cg, query, projectRoot, [...fileNodes.values()].flat().concat([...flow.pathNodeIds].flatMap(id => { const n = cg.getNode(id); return n ? [n] : []; })), new Set([...seedIds, ...flow.pathNodeIds]), evidenceMaxChars, undefined, plan);
|
|
6482
|
+
if (evidence)
|
|
6483
|
+
return evidence;
|
|
5971
6484
|
const managerSection = this.formatDomainRoleInventory(managerHits.length > 0
|
|
5972
6485
|
? managerHits
|
|
5973
6486
|
: [...seedIds].map((id) => cg.getNode(id)).filter((n) => !!n && (0, query_utils_1.isDomainRoleSymbol)(n.name, n.filePath, domainPathTokens)));
|
|
@@ -6435,7 +6948,7 @@ class ToolHandler {
|
|
|
6435
6948
|
* Compact explore for local-symbol behavior questions — skips findRelevantContext
|
|
6436
6949
|
* and caps to 1–2 defining files (avoids the ~24K related-file dump).
|
|
6437
6950
|
*/
|
|
6438
|
-
tryCompactLocalSymbolExplore(cg, query, projectRoot) {
|
|
6951
|
+
tryCompactLocalSymbolExplore(cg, query, projectRoot, plan, evidenceMaxChars) {
|
|
6439
6952
|
// Inventory runs *before* this on the call sites. Do not refuse compact
|
|
6440
6953
|
// merely because inventory *intent* matched — empty inventory must fall
|
|
6441
6954
|
// through here (bare callbacks like OnSurfaceChangedCB).
|
|
@@ -6449,8 +6962,13 @@ class ToolHandler {
|
|
|
6449
6962
|
// Light-mechanism owns domain howtos. Bare "how/如何" NL must NOT veto compact
|
|
6450
6963
|
// when a local/flag/lifecycle shape already owns the query (flag impact was
|
|
6451
6964
|
// falling through to a 20k Dynamic-dispatch dump).
|
|
6452
|
-
if ((
|
|
6965
|
+
if (planFeature(plan, 'shouldTryLightMechanismExplore', query, query_utils_1.shouldTryLightMechanismExplore))
|
|
6453
6966
|
return null;
|
|
6967
|
+
// Explicit `…/Foo.ets` path asks need file-scoped full explore, not compact
|
|
6968
|
+
// trails seeded from path-segment homonyms (`/order/` → `order` property).
|
|
6969
|
+
if ((0, query_utils_1.shouldLimitToQueryNamedFile)(query, false, (0, query_utils_1.queryNamesMultipleExploreAnchors)(query))) {
|
|
6970
|
+
return null;
|
|
6971
|
+
}
|
|
6454
6972
|
if ((0, query_utils_1.queryAsMechanismSurvey)(query)
|
|
6455
6973
|
&& !(0, query_utils_1.queryAsLocalSymbolDetail)(query)
|
|
6456
6974
|
&& !(0, query_utils_1.queryAsAssignedFlagImpactSurvey)(query)
|
|
@@ -6610,6 +7128,9 @@ class ToolHandler {
|
|
|
6610
7128
|
}
|
|
6611
7129
|
if (seedIds.size === 0)
|
|
6612
7130
|
return null;
|
|
7131
|
+
const evidence = this.tryArktsEvidenceExplore(cg, query, projectRoot, [...fileNodes.values()].flat(), seedIds, evidenceMaxChars, undefined, plan);
|
|
7132
|
+
if (evidence)
|
|
7133
|
+
return evidence;
|
|
6613
7134
|
const pathAffinity = (seedPath, otherPath) => {
|
|
6614
7135
|
const a = seedPath.replace(/\\/g, '/').split('/');
|
|
6615
7136
|
const b = otherPath.replace(/\\/g, '/').split('/');
|
|
@@ -9071,8 +9592,10 @@ class ToolHandler {
|
|
|
9071
9592
|
// One normalization point so the flow-builder, relevance search, and
|
|
9072
9593
|
// ranking all see the same canonical spelling (Erlang `mod:fn/arity`).
|
|
9073
9594
|
const query = normalizeQuerySpelling(rawQuery);
|
|
9595
|
+
const plan = readQueryPlan(args);
|
|
9596
|
+
const feature = (name, fallback) => planFeature(plan, name, query, fallback);
|
|
9074
9597
|
const deferKind = (0, query_utils_1.queryShouldDeferToBuiltinTools)(query);
|
|
9075
|
-
if (deferKind) {
|
|
9598
|
+
if (deferKind && plan?.source !== 'llm') {
|
|
9076
9599
|
return this.textResult((0, query_utils_1.homegraphDeferGuidance)(deferKind, query));
|
|
9077
9600
|
}
|
|
9078
9601
|
const cg = this.getHomeGraph(args.projectPath);
|
|
@@ -9080,7 +9603,7 @@ class ToolHandler {
|
|
|
9080
9603
|
// Same-bag / call-budget refuse (session view injected by execute).
|
|
9081
9604
|
const sessionPrior = (0, explore_session_state_1.viewForProject)((0, explore_session_state_1.readExploreSessionView)(args), projectRoot);
|
|
9082
9605
|
const repeat = (0, explore_repeat_guard_1.decideExploreRepeat)(sessionPrior, query);
|
|
9083
|
-
if (repeat
|
|
9606
|
+
if (!plan && this.shouldRefuseRepeatedEvidence(repeat, projectRoot)) {
|
|
9084
9607
|
const text = (0, explore_repeat_guard_1.formatExploreRepeatRefuse)(repeat, query);
|
|
9085
9608
|
return this.exploreResult(text, {
|
|
9086
9609
|
projectRoot,
|
|
@@ -9090,7 +9613,8 @@ class ToolHandler {
|
|
|
9090
9613
|
responseBytes: text.length,
|
|
9091
9614
|
});
|
|
9092
9615
|
}
|
|
9093
|
-
|
|
9616
|
+
// A planned step already tried fast paths once, before entering full explore.
|
|
9617
|
+
const compactLocal = plan ? null : this.tryFastInventoryExplore(cg, query, projectRoot)
|
|
9094
9618
|
?? this.tryLightMechanismExplore(cg, query, projectRoot)
|
|
9095
9619
|
?? this.tryCompactLocalSymbolExplore(cg, query, projectRoot);
|
|
9096
9620
|
if (compactLocal)
|
|
@@ -9110,19 +9634,53 @@ class ToolHandler {
|
|
|
9110
9634
|
const explicitMaxFiles = typeof args.maxFiles === 'number' && !Number.isNaN(args.maxFiles);
|
|
9111
9635
|
let maxFiles = (0, utils_1.clamp)(args.maxFiles || budget.defaultMaxFiles, 1, 20);
|
|
9112
9636
|
const queryFileBasenames = (0, query_utils_1.extractFileBasenamesFromQuery)(query);
|
|
9113
|
-
const interpretationQuery = (
|
|
9114
|
-
const testOnlyInterpretation = (
|
|
9115
|
-
const crossModuleFlow = (
|
|
9637
|
+
const interpretationQuery = feature('queryAsInterpretationSurvey', query_utils_1.queryAsInterpretationSurvey);
|
|
9638
|
+
const testOnlyInterpretation = feature('queryAsTestOnlyInterpretation', query_utils_1.queryAsTestOnlyInterpretation);
|
|
9639
|
+
const crossModuleFlow = feature('queryAsCrossModuleFlowSurvey', query_utils_1.queryAsCrossModuleFlowSurvey) || plan?.intent === 'flow';
|
|
9116
9640
|
// Step 1: Find relevant context with generous parameters.
|
|
9117
9641
|
const contextOpts = interpretationQuery && queryFileBasenames.length === 1
|
|
9118
9642
|
? { searchLimit: 6, traversalDepth: 2, maxNodes: 60, minScore: 0.25 }
|
|
9119
9643
|
: { searchLimit: 8, traversalDepth: 3, maxNodes: 200, minScore: 0.2 };
|
|
9120
9644
|
const contextQuery = interpretationQuery && queryFileBasenames.length === 1
|
|
9121
9645
|
? `${queryFileBasenames[0]} ${query}`
|
|
9122
|
-
:
|
|
9123
|
-
|
|
9646
|
+
: queryFileBasenames.length === 1
|
|
9647
|
+
? `${queryFileBasenames[0]} ${query}`
|
|
9648
|
+
: query;
|
|
9649
|
+
const subgraph = await cg.findRelevantContext(contextQuery, {
|
|
9650
|
+
...contextOpts,
|
|
9651
|
+
...(plan && (plan.source === 'llm' || plan.literalTexts?.length) ? { retrievalHints: {
|
|
9652
|
+
symbols: plan.anchors.filter((anchor) => !(plan.bindings ?? []).some((node) => anchor === node.name || anchor === node.qualifiedName)),
|
|
9653
|
+
searchTerms: plan.searchTerms, literalTexts: plan.literalTexts, sourceScope: plan.sourceScope, nodeIds: (plan.bindings ?? []).map((node) => node.id),
|
|
9654
|
+
} } : {}),
|
|
9655
|
+
});
|
|
9656
|
+
// Path-first: always seed nodes from an explicit `Foo.ets` basename so a
|
|
9657
|
+
// CJK-only ask + path (or a shared prop like showSearchIcon) cannot leave
|
|
9658
|
+
// the named file out of the subgraph / digests.
|
|
9659
|
+
if (queryFileBasenames.length > 0 && plan?.sourceScope !== 'sdk') {
|
|
9660
|
+
for (const base of queryFileBasenames.slice(0, 3)) {
|
|
9661
|
+
let hits = [];
|
|
9662
|
+
try {
|
|
9663
|
+
hits = cg.searchNodes(base, { limit: 50 });
|
|
9664
|
+
}
|
|
9665
|
+
catch {
|
|
9666
|
+
continue;
|
|
9667
|
+
}
|
|
9668
|
+
for (const r of hits) {
|
|
9669
|
+
if (!(0, query_utils_1.fileMatchesQueryBasename)(r.node.filePath, [base]))
|
|
9670
|
+
continue;
|
|
9671
|
+
if (!subgraph.nodes.has(r.node.id)) {
|
|
9672
|
+
subgraph.nodes.set(r.node.id, r.node);
|
|
9673
|
+
subgraph.roots.push(r.node.id);
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
const literalSource = this.renderLiteralSource(cg, subgraph);
|
|
9124
9679
|
if (subgraph.nodes.size === 0) {
|
|
9125
|
-
|
|
9680
|
+
const text = literalSource.text || `No relevant code found for "${query}"`;
|
|
9681
|
+
return this.exploreResult(text, { projectRoot, query, files: literalSource.files,
|
|
9682
|
+
sourceBytes: literalSource.files.reduce((sum, file) => sum + file.bytes, 0), responseBytes: text.length,
|
|
9683
|
+
locatedNodes: literalSource.nodes, partial: true, evidenceStatus: literalSource.text ? 'partial' : 'empty' });
|
|
9126
9684
|
}
|
|
9127
9685
|
// Seed import nodes for @kit.* / *Kit names (and named symbols like taskpool).
|
|
9128
9686
|
const importTerms = (0, query_utils_1.extractImportSearchTerms)(query);
|
|
@@ -9261,8 +9819,8 @@ class ToolHandler {
|
|
|
9261
9819
|
namedParts.push(m[3]);
|
|
9262
9820
|
}
|
|
9263
9821
|
const tokens = [...new Set([
|
|
9264
|
-
...namedParts,
|
|
9265
|
-
...query.split(/[\s,()[\]]+/)
|
|
9822
|
+
...(plan?.source === 'llm' ? plan.anchors : namedParts),
|
|
9823
|
+
...(plan?.source === 'llm' ? plan.anchors.join(' ') : query).split(/[\s,()[\]]+/)
|
|
9266
9824
|
.map((t) => t.replace(FILE_EXT, '').trim())
|
|
9267
9825
|
.filter((t) => t.length >= 3 && /^[A-Za-z_$][\w$]*(?:(?:::|\.)[\w$]+)*$/.test(t)),
|
|
9268
9826
|
])].slice(0, 16);
|
|
@@ -9289,7 +9847,8 @@ class ToolHandler {
|
|
|
9289
9847
|
const isQual = /[.\/]|::/.test(t);
|
|
9290
9848
|
const raw = isQual ? this.findAllSymbols(cg, t).nodes : cg.getNodesByName(t);
|
|
9291
9849
|
let cands = raw
|
|
9292
|
-
.filter((n) => SEED_KINDS.has(n.kind) && !isTestPath(n.filePath)
|
|
9850
|
+
.filter((n) => SEED_KINDS.has(n.kind) && !isTestPath(n.filePath)
|
|
9851
|
+
&& !(plan?.sourceScope === 'local' && (0, arkts_1.isOhosApiFilePath)(n.filePath)))
|
|
9293
9852
|
.sort((a, b) => {
|
|
9294
9853
|
// Prefer callables over types when both share a name, then body size.
|
|
9295
9854
|
const ac = CALLABLE.has(a.kind) ? 1 : 0;
|
|
@@ -9504,6 +10063,8 @@ class ToolHandler {
|
|
|
9504
10063
|
}
|
|
9505
10064
|
fileGroups.set(node.filePath, group);
|
|
9506
10065
|
}
|
|
10066
|
+
for (const group of fileGroups.values())
|
|
10067
|
+
group.nodes = (0, evidence_rendering_1.canonicalSourceDeclarations)(group.nodes);
|
|
9507
10068
|
if (testOnlyInterpretation) {
|
|
9508
10069
|
for (const [, group] of fileGroups) {
|
|
9509
10070
|
const fp = group.nodes[0]?.filePath ?? '';
|
|
@@ -9699,6 +10260,15 @@ class ToolHandler {
|
|
|
9699
10260
|
const sortedFiles = relevantFiles.sort((a, b) => {
|
|
9700
10261
|
const aPath = a[0].toLowerCase();
|
|
9701
10262
|
const bPath = b[0].toLowerCase();
|
|
10263
|
+
if (plan?.sourceScope === 'local') {
|
|
10264
|
+
const sdkOrder = Number((0, arkts_1.isOhosApiFilePath)(a[0])) - Number((0, arkts_1.isOhosApiFilePath)(b[0]));
|
|
10265
|
+
if (sdkOrder)
|
|
10266
|
+
return sdkOrder;
|
|
10267
|
+
}
|
|
10268
|
+
const literalOrder = Number((subgraph.literalEvidence?.hits ?? []).some((hit) => hit.filePath === b[0]))
|
|
10269
|
+
- Number((subgraph.literalEvidence?.hits ?? []).some((hit) => hit.filePath === a[0]));
|
|
10270
|
+
if (literalOrder)
|
|
10271
|
+
return literalOrder;
|
|
9702
10272
|
// Query-named file (LocationController.ets in the question) before partial
|
|
9703
10273
|
// substring matches (control.ets matching "Controller" inside LocationController).
|
|
9704
10274
|
const aExactBase = (0, query_utils_1.fileMatchesQueryBasename)(a[0], queryFileBasenames) ? 1 : 0;
|
|
@@ -9758,6 +10328,8 @@ class ToolHandler {
|
|
|
9758
10328
|
'',
|
|
9759
10329
|
];
|
|
9760
10330
|
const summaryLineIdx = 2;
|
|
10331
|
+
if (literalSource.text)
|
|
10332
|
+
lines.push(literalSource.text);
|
|
9761
10333
|
if (testOnlyInterpretation) {
|
|
9762
10334
|
lines.push('> **Test-file scope only** — answer from the named `.test.ets` file below; ' +
|
|
9763
10335
|
'production handlers are out of scope unless explicitly referenced in the test.');
|
|
@@ -9782,30 +10354,30 @@ class ToolHandler {
|
|
|
9782
10354
|
: { section: '', symbolCount: 0 };
|
|
9783
10355
|
if (kitUsageResult.section)
|
|
9784
10356
|
lines.push(kitUsageResult.section);
|
|
9785
|
-
const domainFileResult = (
|
|
10357
|
+
const domainFileResult = feature('shouldBuildDomainFileSurvey', query_utils_1.shouldBuildDomainFileSurvey)
|
|
9786
10358
|
? this.buildDomainFileSurveySection(cg, query)
|
|
9787
10359
|
: { section: '', fileCount: 0 };
|
|
9788
10360
|
if (domainFileResult.section)
|
|
9789
10361
|
lines.push(domainFileResult.section);
|
|
9790
|
-
const apiUsageResult = (
|
|
9791
|
-
&& !(
|
|
10362
|
+
const apiUsageResult = feature('shouldBuildApiUsageSurvey', query_utils_1.shouldBuildApiUsageSurvey)
|
|
10363
|
+
&& !feature('shouldBuildKitModuleUsageSurvey', query_utils_1.shouldBuildKitModuleUsageSurvey)
|
|
9792
10364
|
? this.buildApiUsageSection(cg, query, projectRoot)
|
|
9793
10365
|
: { section: '', fileCount: 0 };
|
|
9794
10366
|
if (apiUsageResult.section)
|
|
9795
10367
|
lines.push(apiUsageResult.section);
|
|
9796
|
-
const dataSourceResult = (
|
|
10368
|
+
const dataSourceResult = feature('queryAsDataSourceSurvey', query_utils_1.queryAsDataSourceSurvey)
|
|
9797
10369
|
? this.buildDataSourceSection(cg, query)
|
|
9798
10370
|
: { section: '', edgeCount: 0, sdkImportCount: 0, strongCount: 0 };
|
|
9799
10371
|
if (dataSourceResult.section)
|
|
9800
10372
|
lines.push(dataSourceResult.section);
|
|
9801
|
-
const eventDispatchResult = (
|
|
10373
|
+
const eventDispatchResult = feature('queryAsEventDispatchSurvey', query_utils_1.queryAsEventDispatchSurvey)
|
|
9802
10374
|
? this.buildEventDispatchSection(cg, query, projectRoot)
|
|
9803
10375
|
: { section: '', hitCount: 0, eventCount: 0, handlerCount: 0, memberCount: 0, complete: false };
|
|
9804
10376
|
if (eventDispatchResult.section)
|
|
9805
10377
|
lines.push(eventDispatchResult.section);
|
|
9806
10378
|
const importInventoryFilter = (0, query_utils_1.hasImportInventoryFilter)(query);
|
|
9807
10379
|
const multiAnchor = (0, query_utils_1.queryNamesMultipleExploreAnchors)(query) || crossModuleFlow;
|
|
9808
|
-
const mechanismSurvey = (
|
|
10380
|
+
const mechanismSurvey = feature('queryAsMechanismSurvey', query_utils_1.queryAsMechanismSurvey);
|
|
9809
10381
|
// Flow path — computed before omit-source so graph connectivity drives the decision,
|
|
9810
10382
|
// not question-text keyword matching. Mechanism/cross-module surveys augment the
|
|
9811
10383
|
// query with seeded entry symbol names so buildFlowFromNamedSymbols can connect them.
|
|
@@ -9830,6 +10402,13 @@ class ToolHandler {
|
|
|
9830
10402
|
}
|
|
9831
10403
|
const flow = this.buildFlowFromNamedSymbols(cg, flowQuery);
|
|
9832
10404
|
const hasFlowPath = flow.pathNodeIds.size > 0;
|
|
10405
|
+
// Keep literal/resource and specialized inventory rendering intact. This
|
|
10406
|
+
// first batch changes symbol-grounded structural evidence only.
|
|
10407
|
+
if ((hasFlowPath || flow.text.length > 0 || plan?.intent === 'flow') && !subgraph.literalEvidence?.hits.length) {
|
|
10408
|
+
const evidence = this.tryArktsEvidenceExplore(cg, query, projectRoot, [...subgraph.nodes.values()].concat([...flow.pathNodeIds].flatMap(id => { const n = cg.getNode(id); return n ? [n] : []; })), new Set([...subgraph.roots, ...flow.namedNodeIds, ...flow.pathNodeIds]), args._hgEvidenceMaxChars, explicitMaxFiles ? maxFiles : undefined, plan);
|
|
10409
|
+
if (evidence)
|
|
10410
|
+
return evidence;
|
|
10411
|
+
}
|
|
9833
10412
|
budget = tightenExploreBudgetForQuery(budget, query, { hasFlowPath });
|
|
9834
10413
|
// Honor an explicit maxFiles from the caller — budget.defaultMaxFiles is only
|
|
9835
10414
|
// a default when the agent didn't ask for more (adaptive sibling tests pass 12).
|
|
@@ -10036,6 +10615,17 @@ class ToolHandler {
|
|
|
10036
10615
|
const priorCalls = (0, explore_session_state_1.viewForProject)((0, explore_session_state_1.readExploreSessionView)(args), projectRoot);
|
|
10037
10616
|
const dedupEnabled = (0, explore_dedup_1.exploreDedupEnabled)() && (priorCalls?.calls.length ?? 0) > 0;
|
|
10038
10617
|
const emittedByFile = new Map();
|
|
10618
|
+
// Session emissions may include prior-call coverage or skeleton envelopes.
|
|
10619
|
+
// Dependency receipts instead require *fresh*, actually printed source and
|
|
10620
|
+
// a whole surviving file section (never the tail cut by the hard ceiling).
|
|
10621
|
+
const freshRangesByFile = new Map();
|
|
10622
|
+
const sourceEndByFile = new Map();
|
|
10623
|
+
const noteFreshSource = (fp, ranges) => {
|
|
10624
|
+
if (plan?.source !== 'llm' || !ranges.length)
|
|
10625
|
+
return;
|
|
10626
|
+
freshRangesByFile.set(fp, [...(freshRangesByFile.get(fp) ?? []), ...ranges]);
|
|
10627
|
+
sourceEndByFile.set(fp, lines.join('\n').length);
|
|
10628
|
+
};
|
|
10039
10629
|
const noteEmitted = (fp, ranges, bytes, fingerprint) => {
|
|
10040
10630
|
const existing = emittedByFile.get(fp);
|
|
10041
10631
|
if (existing) {
|
|
@@ -10207,6 +10797,7 @@ class ToolHandler {
|
|
|
10207
10797
|
}
|
|
10208
10798
|
if (body.length > 0) {
|
|
10209
10799
|
lines.push('```' + lang, body, '```', '');
|
|
10800
|
+
noteFreshSource(filePath, ranges);
|
|
10210
10801
|
totalChars += body.length + lang.length + 11;
|
|
10211
10802
|
noteEmitted(filePath, [...ranges, ...opts.covered], body.length, fingerprint);
|
|
10212
10803
|
renderedFilePaths.push(filePath);
|
|
@@ -10293,6 +10884,7 @@ class ToolHandler {
|
|
|
10293
10884
|
// signature line (capped, with a "+N more" tail so the structure map of a
|
|
10294
10885
|
// god-file doesn't itself bloat the budget).
|
|
10295
10886
|
const skel = [];
|
|
10887
|
+
const freshSkelRanges = [];
|
|
10296
10888
|
let coveredUntil = 0; // skip symbols already inside an emitted body
|
|
10297
10889
|
let sigCount = 0, sigDropped = 0;
|
|
10298
10890
|
const SIG_MAX = Math.max(12, budget.maxSymbolsInFileHeader * 2);
|
|
@@ -10303,6 +10895,7 @@ class ToolHandler {
|
|
|
10303
10895
|
const end = n.endLine;
|
|
10304
10896
|
const body = fileLines.slice(n.startLine - 1, end).join('\n');
|
|
10305
10897
|
skel.push(exploreLineNumbersEnabled() ? numberSourceLines(body, n.startLine) : body);
|
|
10898
|
+
freshSkelRanges.push({ start: n.startLine, end });
|
|
10306
10899
|
coveredUntil = end;
|
|
10307
10900
|
}
|
|
10308
10901
|
else {
|
|
@@ -10325,6 +10918,7 @@ class ToolHandler {
|
|
|
10325
10918
|
if (sig) {
|
|
10326
10919
|
skel.push(exploreLineNumbersEnabled() ? `${lineNo}\t${sig}` : sig);
|
|
10327
10920
|
sigCount++;
|
|
10921
|
+
freshSkelRanges.push({ start: lineNo, end: lineNo });
|
|
10328
10922
|
}
|
|
10329
10923
|
}
|
|
10330
10924
|
}
|
|
@@ -10362,6 +10956,7 @@ class ToolHandler {
|
|
|
10362
10956
|
return n ? { start: n.startLine, end: n.endLine || n.startLine } : null;
|
|
10363
10957
|
}).filter((r) => !!r);
|
|
10364
10958
|
lines.push(skelHeader, '', '```' + lang, skelBody, '```', '');
|
|
10959
|
+
noteFreshSource(filePath, freshSkelRanges);
|
|
10365
10960
|
totalChars += skelBody.length + 120;
|
|
10366
10961
|
noteEmitted(filePath, bodyRanges.length > 0 ? bodyRanges : [wholeRange], skelBody.length, fingerprint);
|
|
10367
10962
|
renderedFilePaths.push(filePath);
|
|
@@ -10394,7 +10989,15 @@ class ToolHandler {
|
|
|
10394
10989
|
? Math.min(Math.max(0, budget.maxOutputChars - totalChars - 200), Math.round(budget.maxCharsPerFile * 1.5))
|
|
10395
10990
|
: budget.maxCharsPerFile * 3;
|
|
10396
10991
|
if (fileLines.length <= WHOLE_FILE_MAX_LINES && fileContent.length <= WHOLE_FILE_MAX_CHARS) {
|
|
10397
|
-
|
|
10992
|
+
let sourceStart = 0;
|
|
10993
|
+
if (fileLines[0]?.trim().startsWith('/*')) {
|
|
10994
|
+
const endComment = fileLines.findIndex((line) => line.includes('*/'));
|
|
10995
|
+
if (endComment >= 0 && endComment < 40)
|
|
10996
|
+
sourceStart = endComment + 1;
|
|
10997
|
+
}
|
|
10998
|
+
while (sourceStart < fileLines.length - 1 && !fileLines[sourceStart]?.trim())
|
|
10999
|
+
sourceStart++;
|
|
11000
|
+
const wholeRange = { start: sourceStart + 1, end: Math.max(1, fileLines.length) };
|
|
10398
11001
|
const dd = (0, explore_dedup_1.dedupeRange)(wholeRange, served);
|
|
10399
11002
|
const uniqSymbols = [...new Set(group.nodes
|
|
10400
11003
|
.filter(n => n.kind !== 'import' && n.kind !== 'export')
|
|
@@ -10855,6 +11458,7 @@ class ToolHandler {
|
|
|
10855
11458
|
const output = flow.text + lines.join('\n');
|
|
10856
11459
|
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
|
|
10857
11460
|
let finalText;
|
|
11461
|
+
let sourceCutoff = output.length;
|
|
10858
11462
|
if (output.length > hardCeiling) {
|
|
10859
11463
|
// Prefer dropping trailing notes ("Not shown above", completeness, budget)
|
|
10860
11464
|
// over dropping a whole file's source — notes are recoverable, source isn't.
|
|
@@ -10869,8 +11473,10 @@ class ToolHandler {
|
|
|
10869
11473
|
if (trimmed.length <= hardCeiling)
|
|
10870
11474
|
break;
|
|
10871
11475
|
const at = trimmed.lastIndexOf(marker);
|
|
10872
|
-
if (at > hardCeiling * 0.4)
|
|
11476
|
+
if (at > hardCeiling * 0.4) {
|
|
10873
11477
|
trimmed = trimmed.slice(0, at).replace(/\n+$/, '');
|
|
11478
|
+
sourceCutoff = Math.min(sourceCutoff, trimmed.length);
|
|
11479
|
+
}
|
|
10874
11480
|
}
|
|
10875
11481
|
if (trimmed.length > hardCeiling) {
|
|
10876
11482
|
// Cut at a FILE-SECTION boundary so we drop whole trailing file-sections
|
|
@@ -10899,6 +11505,7 @@ class ToolHandler {
|
|
|
10899
11505
|
boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
|
|
10900
11506
|
}
|
|
10901
11507
|
const safe = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
11508
|
+
sourceCutoff = Math.min(sourceCutoff, safe.length);
|
|
10902
11509
|
finalText = safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For uncovered files/symbols, run another homegraph_explore with their exact names — not grep/read/node for symbols already shown.)';
|
|
10903
11510
|
}
|
|
10904
11511
|
else {
|
|
@@ -10949,14 +11556,85 @@ class ToolHandler {
|
|
|
10949
11556
|
});
|
|
10950
11557
|
sourceBytes += emitted.bytes;
|
|
10951
11558
|
}
|
|
11559
|
+
// Do not promote unseen subgraph hits or a large parent's unshown header.
|
|
11560
|
+
// The declaration's starting line must be in a surviving, fresh source span.
|
|
11561
|
+
const rootIds = new Set(subgraph.roots);
|
|
11562
|
+
for (const file of literalSource.files) {
|
|
11563
|
+
if (!finalText.includes(literalSource.text))
|
|
11564
|
+
break;
|
|
11565
|
+
emittedFiles.push(file);
|
|
11566
|
+
sourceBytes += file.bytes;
|
|
11567
|
+
}
|
|
11568
|
+
const locatedNodes = plan?.source === 'llm' ? [...(finalText.includes(literalSource.text) ? literalSource.nodes : []), ...emittedFiles.flatMap((file) => staleRendered.includes(file.path) || flow.text.length + (sourceEndByFile.get(file.path) ?? Infinity) > sourceCutoff
|
|
11569
|
+
? [] : (fileGroups.get(file.path)?.nodes ?? []).filter((node) => !['file', 'import', 'export', 'parameter'].includes(node.kind)
|
|
11570
|
+
&& (freshRangesByFile.get(file.path) ?? []).some((range) => node.startLine >= range.start && node.startLine <= range.end)))]
|
|
11571
|
+
.sort((a, b) => Number(rootIds.has(b.id)) - Number(rootIds.has(a.id)))
|
|
11572
|
+
.filter((node, i, nodes) => nodes.findIndex((other) => other.id === node.id) === i)
|
|
11573
|
+
.slice(0, 32).map((node) => ({ id: node.id, name: node.name, qualifiedName: node.qualifiedName,
|
|
11574
|
+
filePath: node.filePath, startLine: node.startLine })) : undefined;
|
|
10952
11575
|
return this.exploreResult(finalText, {
|
|
10953
11576
|
projectRoot,
|
|
10954
11577
|
query,
|
|
10955
11578
|
files: emittedFiles,
|
|
10956
11579
|
sourceBytes,
|
|
11580
|
+
evidenceStatus: sourceBytes > 0 ? (anyFileTrimmed || subgraph.confidence === 'low' || sourceCutoff < output.length
|
|
11581
|
+
|| (plan?.source === 'llm' && !(locatedNodes?.length)) ? 'partial' : 'complete')
|
|
11582
|
+
: filesIncluded > 0 && /HarmonyOS SDK API/.test(finalText) ? 'sdk-only' : 'empty',
|
|
10957
11583
|
responseBytes: finalText.length,
|
|
11584
|
+
...(locatedNodes ? { locatedNodes } : {}),
|
|
10958
11585
|
});
|
|
10959
11586
|
}
|
|
11587
|
+
/** Render literal witnesses before graph-heavy sections, including unindexed UI. */
|
|
11588
|
+
renderLiteralSource(cg, subgraph) {
|
|
11589
|
+
const sections = [];
|
|
11590
|
+
const files = [];
|
|
11591
|
+
const nodes = [];
|
|
11592
|
+
const seen = new Set();
|
|
11593
|
+
let chars = 0;
|
|
11594
|
+
for (const hit of subgraph.literalEvidence?.hits ?? []) {
|
|
11595
|
+
if (files.length >= 2 || seen.has(hit.filePath))
|
|
11596
|
+
continue;
|
|
11597
|
+
const absolute = (0, utils_1.validatePathWithinRoot)(cg.getProjectRoot(), hit.filePath);
|
|
11598
|
+
if (!absolute)
|
|
11599
|
+
continue;
|
|
11600
|
+
let source;
|
|
11601
|
+
try {
|
|
11602
|
+
source = (0, fs_1.readFileSync)(absolute, 'utf8');
|
|
11603
|
+
}
|
|
11604
|
+
catch {
|
|
11605
|
+
continue;
|
|
11606
|
+
}
|
|
11607
|
+
const lines = source.split('\n');
|
|
11608
|
+
// Never serve an earlier witness against a changed file.
|
|
11609
|
+
if (lines.slice(hit.startLine - 1, hit.endLine).join('\n') !== hit.text)
|
|
11610
|
+
continue;
|
|
11611
|
+
const containing = this.isFileStaleOnDisk(cg, hit.filePath, source) ? undefined
|
|
11612
|
+
: (0, evidence_rendering_1.canonicalSourceDeclarations)(cg.getNodesInFile(hit.filePath))
|
|
11613
|
+
.filter(node => !['file', 'import', 'export', 'parameter'].includes(node.kind)
|
|
11614
|
+
&& !node.name.startsWith('%') && node.name !== 'constructor'
|
|
11615
|
+
&& node.startLine <= hit.line && node.endLine >= hit.line)
|
|
11616
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
11617
|
+
const ranges = [{ start: hit.startLine, end: hit.endLine }];
|
|
11618
|
+
if (containing && containing.startLine < hit.startLine) {
|
|
11619
|
+
ranges.unshift({ start: containing.startLine, end: Math.min(containing.startLine + 2, hit.startLine - 1) });
|
|
11620
|
+
}
|
|
11621
|
+
const body = ranges.map(range => lines.slice(range.start - 1, range.end)
|
|
11622
|
+
.map((line, index) => `${range.start + index}\t${line}`).join('\n')).join('\n... (gap) ...\n');
|
|
11623
|
+
const resource = hit.resource
|
|
11624
|
+
? `\nResource: \`${hit.resource.filePath}:${hit.resource.line}\` — ${JSON.stringify(hit.resource.value)} → \`${hit.resource.key}\`.` : '';
|
|
11625
|
+
const section = `**Literal source witness: \`${hit.filePath}:${hit.line}\`**${resource}\n\n\`\`\`\n${body}\n\`\`\``;
|
|
11626
|
+
if (chars + section.length > 3000)
|
|
11627
|
+
continue;
|
|
11628
|
+
sections.push(section);
|
|
11629
|
+
chars += section.length;
|
|
11630
|
+
seen.add(hit.filePath);
|
|
11631
|
+
files.push({ path: hit.filePath, ranges, bytes: body.length, fingerprint: (0, explore_dedup_1.fileFingerprint)(source) });
|
|
11632
|
+
if (containing)
|
|
11633
|
+
nodes.push({ id: containing.id, name: containing.name,
|
|
11634
|
+
qualifiedName: containing.qualifiedName, filePath: containing.filePath, startLine: containing.startLine });
|
|
11635
|
+
}
|
|
11636
|
+
return { text: sections.join('\n\n'), files, nodes };
|
|
11637
|
+
}
|
|
10960
11638
|
/**
|
|
10961
11639
|
* An explore response plus the record of what it emitted (CG-17). The record
|
|
10962
11640
|
* rides the result only as far as {@link execute}, which files it into the
|
|
@@ -10967,7 +11645,8 @@ class ToolHandler {
|
|
|
10967
11645
|
const result = this.textResult(text);
|
|
10968
11646
|
result[explore_session_state_1.EXPLORE_EMISSION_KEY] = {
|
|
10969
11647
|
...emission,
|
|
10970
|
-
|
|
11648
|
+
evidenceStatus: emission.evidenceStatus ?? (emission.sourceBytes > 0 ? (meta.partial ? 'partial' : 'complete') : 'partial'),
|
|
11649
|
+
partial: emission.partial ?? (emission.evidenceStatus && emission.evidenceStatus !== 'complete' ? true : meta.partial),
|
|
10971
11650
|
nextAnchor: emission.nextAnchor ?? meta.nextAnchor,
|
|
10972
11651
|
};
|
|
10973
11652
|
return result;
|
|
@@ -10993,7 +11672,8 @@ class ToolHandler {
|
|
|
10993
11672
|
files: [],
|
|
10994
11673
|
sourceBytes: 0,
|
|
10995
11674
|
responseBytes: text.length,
|
|
10996
|
-
partial:
|
|
11675
|
+
partial: true,
|
|
11676
|
+
evidenceStatus: /HarmonyOS SDK API|ohos-sdk:/.test(text) ? 'sdk-only' : /No relevant code|No matching evidence|No survey ran/.test(text) ? 'empty' : 'partial',
|
|
10997
11677
|
nextAnchor: meta.nextAnchor,
|
|
10998
11678
|
};
|
|
10999
11679
|
return result;
|
|
@@ -11028,7 +11708,7 @@ class ToolHandler {
|
|
|
11028
11708
|
const symbol = this.validateString(args.symbol, 'symbol');
|
|
11029
11709
|
if (typeof symbol !== 'string')
|
|
11030
11710
|
return symbol;
|
|
11031
|
-
let matches = this.findSymbolMatches(cg, symbol);
|
|
11711
|
+
let matches = (0, evidence_rendering_1.canonicalSourceDeclarations)(this.findSymbolMatches(cg, symbol));
|
|
11032
11712
|
if (matches.length === 0) {
|
|
11033
11713
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
11034
11714
|
}
|
|
@@ -11397,21 +12077,18 @@ class ToolHandler {
|
|
|
11397
12077
|
lines.push(`**Build phase:** ${cg.getBuildPhase()}`, `**Files indexed:** ${stats.fileCount}`, `**Total nodes:** ${stats.nodeCount}`, `**Total edges:** ${stats.edgeCount}`, `**Database size:** ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`, ...(stats.walSizeBytes > 0
|
|
11398
12078
|
? [`**WAL size:** ${(stats.walSizeBytes / 1024 / 1024).toFixed(2)} MB`]
|
|
11399
12079
|
: []), `**Graph sources:** ${cg.getGraphSources()}`);
|
|
11400
|
-
// Surface the active SQLite backend: node:sqlite →
|
|
12080
|
+
// Surface the active SQLite backend: node:sqlite → wasm.
|
|
11401
12081
|
const backend = cg.getBackend();
|
|
11402
12082
|
if (backend === 'node-sqlite') {
|
|
11403
12083
|
lines.push(`**Backend:** node-sqlite (built-in)`);
|
|
11404
12084
|
}
|
|
11405
|
-
else if (backend === 'native') {
|
|
11406
|
-
lines.push(`**Backend:** native (better-sqlite3)`);
|
|
11407
|
-
}
|
|
11408
12085
|
else {
|
|
11409
12086
|
lines.push(`**Backend:** ⚠ wasm (no WAL backend available) — ` +
|
|
11410
12087
|
`5-10x slower than WAL. Fix: ${sqlite_adapter_1.WASM_FALLBACK_FIX_RECIPE}`);
|
|
11411
12088
|
}
|
|
11412
12089
|
// Effective journal mode. 'wal' ⇒ concurrent reads never block on a writer;
|
|
11413
|
-
// anything else ⇒ they can ("database is locked"). node:sqlite
|
|
11414
|
-
//
|
|
12090
|
+
// anything else ⇒ they can ("database is locked"). node:sqlite supports WAL;
|
|
12091
|
+
// wasm remaps to DELETE.
|
|
11415
12092
|
const journalMode = cg.getJournalMode();
|
|
11416
12093
|
if (journalMode === 'wal') {
|
|
11417
12094
|
lines.push(`**Journal mode:** wal (concurrent reads safe)`);
|
|
@@ -11464,10 +12141,27 @@ class ToolHandler {
|
|
|
11464
12141
|
*/
|
|
11465
12142
|
async handleProject(args) {
|
|
11466
12143
|
const cg = this.getHomeGraph(args.projectPath);
|
|
11467
|
-
const
|
|
11468
|
-
if (
|
|
11469
|
-
return this.textResult(
|
|
12144
|
+
const state = (0, index_availability_1.resolveProductIndexState)(cg);
|
|
12145
|
+
if (state === 'empty') {
|
|
12146
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('empty'));
|
|
12147
|
+
}
|
|
12148
|
+
if (state === 'syncing') {
|
|
12149
|
+
// Prefer returning the map when readable; only hard-stop if phase has no map.
|
|
12150
|
+
try {
|
|
12151
|
+
const peek = cg.getProjectMap({ includeFiles: false });
|
|
12152
|
+
if (peek.modules.length === 0) {
|
|
12153
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
12154
|
+
}
|
|
12155
|
+
}
|
|
12156
|
+
catch (err) {
|
|
12157
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12158
|
+
if ((0, index_availability_1.isSqliteBusyMessage)(msg)) {
|
|
12159
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
12160
|
+
}
|
|
12161
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
12162
|
+
}
|
|
11470
12163
|
}
|
|
12164
|
+
const phase = cg.getBuildPhase();
|
|
11471
12165
|
let map = cg.getProjectMap({
|
|
11472
12166
|
module: typeof args.module === 'string' ? args.module : undefined,
|
|
11473
12167
|
includeFiles: args.includeFiles !== false,
|
|
@@ -11486,6 +12180,9 @@ class ToolHandler {
|
|
|
11486
12180
|
}
|
|
11487
12181
|
catch (err) {
|
|
11488
12182
|
const message = err instanceof Error ? err.message : String(err);
|
|
12183
|
+
if ((0, index_availability_1.isSqliteBusyMessage)(message)) {
|
|
12184
|
+
return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
|
|
12185
|
+
}
|
|
11489
12186
|
return this.textResult(`Failed to build project map: ${message}`);
|
|
11490
12187
|
}
|
|
11491
12188
|
}
|
|
@@ -11493,14 +12190,18 @@ class ToolHandler {
|
|
|
11493
12190
|
return this.textResult('No modules found in the project map.');
|
|
11494
12191
|
}
|
|
11495
12192
|
const FILE_CAP = 80;
|
|
12193
|
+
const productState = (0, index_availability_1.resolveProductIndexState)(cg);
|
|
11496
12194
|
const lines = [
|
|
11497
|
-
`**Project map** (phase=${map.phase})`,
|
|
12195
|
+
`**Project map** (status=${productState}, phase=${map.phase})`,
|
|
11498
12196
|
`modules: ${map.modules.length} · files: ${map.fileCount}`,
|
|
11499
12197
|
'',
|
|
11500
12198
|
];
|
|
11501
|
-
if (map.phase === 'fast' || map.phase === 'indexing') {
|
|
12199
|
+
if (productState === 'fast' || map.phase === 'fast' || map.phase === 'indexing') {
|
|
11502
12200
|
lines.push('_Full symbol index still building — this map has modules/files only (no call graph)._', '');
|
|
11503
12201
|
}
|
|
12202
|
+
if (productState === 'syncing') {
|
|
12203
|
+
lines.push('_Index write in progress — map may be briefly stale._', '');
|
|
12204
|
+
}
|
|
11504
12205
|
for (const m of map.modules) {
|
|
11505
12206
|
const rootLabel = m.rootPath || '.';
|
|
11506
12207
|
lines.push(`### ${m.name} (\`${rootLabel}\`) · ${m.kind} · ${m.fileCount} files`);
|
|
@@ -12396,6 +13097,9 @@ class ToolHandler {
|
|
|
12396
13097
|
// Line-numbered (cat -n style, like homegraph_explore and Read) so the
|
|
12397
13098
|
// agent can cite/edit exact lines without re-Reading the file for them.
|
|
12398
13099
|
const numbered = node.startLine ? numberSourceLines(code, node.startLine) : code;
|
|
13100
|
+
if (node.startLine && process.env.HOMEGRAPH_SOURCE_RECEIPTS === '1') {
|
|
13101
|
+
lines.push('', (0, source_slice_identity_1.sourceSliceIdentity)(node.filePath, node.startLine, code));
|
|
13102
|
+
}
|
|
12399
13103
|
lines.push('', '```' + node.language, numbered, '```');
|
|
12400
13104
|
}
|
|
12401
13105
|
return lines.join('\n');
|
|
@@ -12404,7 +13108,7 @@ class ToolHandler {
|
|
|
12404
13108
|
return {
|
|
12405
13109
|
// Single choke point for every tool's text, so no section builder can ship
|
|
12406
13110
|
// a stop-searching directive on an output that declares itself partial.
|
|
12407
|
-
content: [{ type: 'text', text: reconcilePartialAnswerNow(text) }],
|
|
13111
|
+
content: [{ type: 'text', text: (0, evidence_rendering_1.neutralRetrievalGuidance)(reconcilePartialAnswerNow(text)) }],
|
|
12408
13112
|
};
|
|
12409
13113
|
}
|
|
12410
13114
|
/**
|