trail-code 0.0.0-stage → 0.28.0-semantic.3
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/LICENSE +21 -0
- package/README.md +567 -2
- package/dist/ai/crux.d.ts +88 -0
- package/dist/ai/crux.d.ts.map +1 -0
- package/dist/ai/crux.js +219 -0
- package/dist/ai/crux.js.map +1 -0
- package/dist/ai/failure.d.ts +60 -0
- package/dist/ai/failure.d.ts.map +1 -0
- package/dist/ai/failure.js +86 -0
- package/dist/ai/failure.js.map +1 -0
- package/dist/ai/llm/anthropic.d.ts +37 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -0
- package/dist/ai/llm/anthropic.js +144 -0
- package/dist/ai/llm/anthropic.js.map +1 -0
- package/dist/ai/llm/factory.d.ts +28 -0
- package/dist/ai/llm/factory.d.ts.map +1 -0
- package/dist/ai/llm/factory.js +36 -0
- package/dist/ai/llm/factory.js.map +1 -0
- package/dist/ai/llm/litellm.d.ts +34 -0
- package/dist/ai/llm/litellm.d.ts.map +1 -0
- package/dist/ai/llm/litellm.js +45 -0
- package/dist/ai/llm/litellm.js.map +1 -0
- package/dist/ai/llm/openai.d.ts +145 -0
- package/dist/ai/llm/openai.d.ts.map +1 -0
- package/dist/ai/llm/openai.js +629 -0
- package/dist/ai/llm/openai.js.map +1 -0
- package/dist/ai/llm/orcarouter.d.ts +34 -0
- package/dist/ai/llm/orcarouter.d.ts.map +1 -0
- package/dist/ai/llm/orcarouter.js +45 -0
- package/dist/ai/llm/orcarouter.js.map +1 -0
- package/dist/ai/llm/recover-tool.d.ts +21 -0
- package/dist/ai/llm/recover-tool.d.ts.map +1 -0
- package/dist/ai/llm/recover-tool.js +109 -0
- package/dist/ai/llm/recover-tool.js.map +1 -0
- package/dist/ai/llm/types.d.ts +122 -0
- package/dist/ai/llm/types.d.ts.map +1 -0
- package/dist/ai/llm/types.js +33 -0
- package/dist/ai/llm/types.js.map +1 -0
- package/dist/ai/providers.d.ts +59 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +54 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/summarize.d.ts +25 -0
- package/dist/ai/summarize.d.ts.map +1 -0
- package/dist/ai/summarize.js +34 -0
- package/dist/ai/summarize.js.map +1 -0
- package/dist/ai/synthesize.d.ts +40 -0
- package/dist/ai/synthesize.d.ts.map +1 -0
- package/dist/ai/synthesize.js +230 -0
- package/dist/ai/synthesize.js.map +1 -0
- package/dist/ask/ask.d.ts +199 -0
- package/dist/ask/ask.d.ts.map +1 -0
- package/dist/ask/ask.js +1291 -0
- package/dist/ask/ask.js.map +1 -0
- package/dist/ask/file-rank.d.ts +72 -0
- package/dist/ask/file-rank.d.ts.map +1 -0
- package/dist/ask/file-rank.js +160 -0
- package/dist/ask/file-rank.js.map +1 -0
- package/dist/ask/file-selection.d.ts +22 -0
- package/dist/ask/file-selection.d.ts.map +1 -0
- package/dist/ask/file-selection.js +66 -0
- package/dist/ask/file-selection.js.map +1 -0
- package/dist/ask/fuse.d.ts +195 -0
- package/dist/ask/fuse.d.ts.map +1 -0
- package/dist/ask/fuse.js +344 -0
- package/dist/ask/fuse.js.map +1 -0
- package/dist/ask/graphrank.d.ts +61 -0
- package/dist/ask/graphrank.d.ts.map +1 -0
- package/dist/ask/graphrank.js +130 -0
- package/dist/ask/graphrank.js.map +1 -0
- package/dist/ask/index-file.d.ts +44 -0
- package/dist/ask/index-file.d.ts.map +1 -0
- package/dist/ask/index-file.js +128 -0
- package/dist/ask/index-file.js.map +1 -0
- package/dist/ask/semantic.d.ts +42 -0
- package/dist/ask/semantic.d.ts.map +1 -0
- package/dist/ask/semantic.js +293 -0
- package/dist/ask/semantic.js.map +1 -0
- package/dist/bin/graft.d.ts +3 -0
- package/dist/bin/graft.d.ts.map +1 -0
- package/dist/bin/graft.js +14 -0
- package/dist/bin/graft.js.map +1 -0
- package/dist/bin/trail.d.ts +3 -0
- package/dist/bin/trail.d.ts.map +1 -0
- package/dist/bin/trail.js +13 -0
- package/dist/bin/trail.js.map +1 -0
- package/dist/blast/blast-cli.d.ts +35 -0
- package/dist/blast/blast-cli.d.ts.map +1 -0
- package/dist/blast/blast-cli.js +155 -0
- package/dist/blast/blast-cli.js.map +1 -0
- package/dist/blast/blast.d.ts +128 -0
- package/dist/blast/blast.d.ts.map +1 -0
- package/dist/blast/blast.js +383 -0
- package/dist/blast/blast.js.map +1 -0
- package/dist/blast/diff.d.ts +53 -0
- package/dist/blast/diff.d.ts.map +1 -0
- package/dist/blast/diff.js +193 -0
- package/dist/blast/diff.js.map +1 -0
- package/dist/blast/evidence.d.ts +51 -0
- package/dist/blast/evidence.d.ts.map +1 -0
- package/dist/blast/evidence.js +162 -0
- package/dist/blast/evidence.js.map +1 -0
- package/dist/blast/modules.d.ts +33 -0
- package/dist/blast/modules.d.ts.map +1 -0
- package/dist/blast/modules.js +83 -0
- package/dist/blast/modules.js.map +1 -0
- package/dist/blast/name.d.ts +74 -0
- package/dist/blast/name.d.ts.map +1 -0
- package/dist/blast/name.js +269 -0
- package/dist/blast/name.js.map +1 -0
- package/dist/blast/owners.d.ts +85 -0
- package/dist/blast/owners.d.ts.map +1 -0
- package/dist/blast/owners.js +272 -0
- package/dist/blast/owners.js.map +1 -0
- package/dist/blast/render.d.ts +46 -0
- package/dist/blast/render.d.ts.map +1 -0
- package/dist/blast/render.js +422 -0
- package/dist/blast/render.js.map +1 -0
- package/dist/blast/viz.d.ts +37 -0
- package/dist/blast/viz.d.ts.map +1 -0
- package/dist/blast/viz.js +180 -0
- package/dist/blast/viz.js.map +1 -0
- package/dist/brand.d.ts +111 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +282 -0
- package/dist/brand.js.map +1 -0
- package/dist/claude/build-progress.d.ts +30 -0
- package/dist/claude/build-progress.d.ts.map +1 -0
- package/dist/claude/build-progress.js +81 -0
- package/dist/claude/build-progress.js.map +1 -0
- package/dist/claude/hermes-skill-template.d.ts +16 -0
- package/dist/claude/hermes-skill-template.d.ts.map +1 -0
- package/dist/claude/hermes-skill-template.js +154 -0
- package/dist/claude/hermes-skill-template.js.map +1 -0
- package/dist/claude/init.d.ts +65 -0
- package/dist/claude/init.d.ts.map +1 -0
- package/dist/claude/init.js +258 -0
- package/dist/claude/init.js.map +1 -0
- package/dist/claude/paths.d.ts +9 -0
- package/dist/claude/paths.d.ts.map +1 -0
- package/dist/claude/paths.js +22 -0
- package/dist/claude/paths.js.map +1 -0
- package/dist/claude/session-metrics.d.ts +73 -0
- package/dist/claude/session-metrics.d.ts.map +1 -0
- package/dist/claude/session-metrics.js +209 -0
- package/dist/claude/session-metrics.js.map +1 -0
- package/dist/claude/settings-merge.d.ts +76 -0
- package/dist/claude/settings-merge.d.ts.map +1 -0
- package/dist/claude/settings-merge.js +283 -0
- package/dist/claude/settings-merge.js.map +1 -0
- package/dist/claude/skill-template.d.ts +13 -0
- package/dist/claude/skill-template.d.ts.map +1 -0
- package/dist/claude/skill-template.js +290 -0
- package/dist/claude/skill-template.js.map +1 -0
- package/dist/claude/state.d.ts +85 -0
- package/dist/claude/state.d.ts.map +1 -0
- package/dist/claude/state.js +88 -0
- package/dist/claude/state.js.map +1 -0
- package/dist/claude/stats.d.ts +5 -0
- package/dist/claude/stats.d.ts.map +1 -0
- package/dist/claude/stats.js +24 -0
- package/dist/claude/stats.js.map +1 -0
- package/dist/cli-epilogue.d.ts +12 -0
- package/dist/cli-epilogue.d.ts.map +1 -0
- package/dist/cli-epilogue.js +72 -0
- package/dist/cli-epilogue.js.map +1 -0
- package/dist/cli-meta.d.ts +82 -0
- package/dist/cli-meta.d.ts.map +1 -0
- package/dist/cli-meta.js +238 -0
- package/dist/cli-meta.js.map +1 -0
- package/dist/cli-picker.d.ts +139 -0
- package/dist/cli-picker.d.ts.map +1 -0
- package/dist/cli-picker.js +439 -0
- package/dist/cli-picker.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1638 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/batches.d.ts +29 -0
- package/dist/context/batches.d.ts.map +1 -0
- package/dist/context/batches.js +130 -0
- package/dist/context/batches.js.map +1 -0
- package/dist/context/build.d.ts +57 -0
- package/dist/context/build.d.ts.map +1 -0
- package/dist/context/build.js +396 -0
- package/dist/context/build.js.map +1 -0
- package/dist/context/check.d.ts +42 -0
- package/dist/context/check.d.ts.map +1 -0
- package/dist/context/check.js +162 -0
- package/dist/context/check.js.map +1 -0
- package/dist/context/map-dir.d.ts +13 -0
- package/dist/context/map-dir.d.ts.map +1 -0
- package/dist/context/map-dir.js +81 -0
- package/dist/context/map-dir.js.map +1 -0
- package/dist/context/node-file.d.ts +109 -0
- package/dist/context/node-file.d.ts.map +1 -0
- package/dist/context/node-file.js +390 -0
- package/dist/context/node-file.js.map +1 -0
- package/dist/context/price.d.ts +49 -0
- package/dist/context/price.d.ts.map +1 -0
- package/dist/context/price.js +83 -0
- package/dist/context/price.js.map +1 -0
- package/dist/context/savings.d.ts +67 -0
- package/dist/context/savings.d.ts.map +1 -0
- package/dist/context/savings.js +125 -0
- package/dist/context/savings.js.map +1 -0
- package/dist/engine.d.ts +85 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +122 -0
- package/dist/engine.js.map +1 -0
- package/dist/graph/bindings.d.ts +64 -0
- package/dist/graph/bindings.d.ts.map +1 -0
- package/dist/graph/bindings.js +713 -0
- package/dist/graph/bindings.js.map +1 -0
- package/dist/graph/build-run.d.ts +25 -0
- package/dist/graph/build-run.d.ts.map +1 -0
- package/dist/graph/build-run.js +121 -0
- package/dist/graph/build-run.js.map +1 -0
- package/dist/graph/build.d.ts +88 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +386 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/cards.d.ts +46 -0
- package/dist/graph/cards.d.ts.map +1 -0
- package/dist/graph/cards.js +280 -0
- package/dist/graph/cards.js.map +1 -0
- package/dist/graph/check.d.ts +25 -0
- package/dist/graph/check.d.ts.map +1 -0
- package/dist/graph/check.js +192 -0
- package/dist/graph/check.js.map +1 -0
- package/dist/graph/container.d.ts +63 -0
- package/dist/graph/container.d.ts.map +1 -0
- package/dist/graph/container.js +191 -0
- package/dist/graph/container.js.map +1 -0
- package/dist/graph/depth-parser.d.ts +61 -0
- package/dist/graph/depth-parser.d.ts.map +1 -0
- package/dist/graph/depth-parser.js +153 -0
- package/dist/graph/depth-parser.js.map +1 -0
- package/dist/graph/enrich.d.ts +62 -0
- package/dist/graph/enrich.d.ts.map +1 -0
- package/dist/graph/enrich.js +258 -0
- package/dist/graph/enrich.js.map +1 -0
- package/dist/graph/extract-cache.d.ts +101 -0
- package/dist/graph/extract-cache.d.ts.map +1 -0
- package/dist/graph/extract-cache.js +199 -0
- package/dist/graph/extract-cache.js.map +1 -0
- package/dist/graph/extract.d.ts +85 -0
- package/dist/graph/extract.d.ts.map +1 -0
- package/dist/graph/extract.js +2377 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/fingerprint.d.ts +76 -0
- package/dist/graph/fingerprint.d.ts.map +1 -0
- package/dist/graph/fingerprint.js +160 -0
- package/dist/graph/fingerprint.js.map +1 -0
- package/dist/graph/generic.d.ts +74 -0
- package/dist/graph/generic.d.ts.map +1 -0
- package/dist/graph/generic.js +773 -0
- package/dist/graph/generic.js.map +1 -0
- package/dist/graph/invariants.d.ts +33 -0
- package/dist/graph/invariants.d.ts.map +1 -0
- package/dist/graph/invariants.js +52 -0
- package/dist/graph/invariants.js.map +1 -0
- package/dist/graph/load.d.ts +29 -0
- package/dist/graph/load.d.ts.map +1 -0
- package/dist/graph/load.js +97 -0
- package/dist/graph/load.js.map +1 -0
- package/dist/graph/lsp/client.d.ts +61 -0
- package/dist/graph/lsp/client.d.ts.map +1 -0
- package/dist/graph/lsp/client.js +217 -0
- package/dist/graph/lsp/client.js.map +1 -0
- package/dist/graph/lsp/enrich.d.ts +11 -0
- package/dist/graph/lsp/enrich.d.ts.map +1 -0
- package/dist/graph/lsp/enrich.js +163 -0
- package/dist/graph/lsp/enrich.js.map +1 -0
- package/dist/graph/lsp/registry.d.ts +22 -0
- package/dist/graph/lsp/registry.d.ts.map +1 -0
- package/dist/graph/lsp/registry.js +71 -0
- package/dist/graph/lsp/registry.js.map +1 -0
- package/dist/graph/map.d.ts +107 -0
- package/dist/graph/map.d.ts.map +1 -0
- package/dist/graph/map.js +239 -0
- package/dist/graph/map.js.map +1 -0
- package/dist/graph/markdown.d.ts +22 -0
- package/dist/graph/markdown.d.ts.map +1 -0
- package/dist/graph/markdown.js +143 -0
- package/dist/graph/markdown.js.map +1 -0
- package/dist/graph/native.d.ts +46 -0
- package/dist/graph/native.d.ts.map +1 -0
- package/dist/graph/native.js +169 -0
- package/dist/graph/native.js.map +1 -0
- package/dist/graph/queries/c.scm +13 -0
- package/dist/graph/queries/c_sharp.scm +19 -0
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/queries/cpp.scm +23 -0
- package/dist/graph/queries/dart.scm +42 -0
- package/dist/graph/queries/elixir.scm +54 -0
- package/dist/graph/queries/gleam.scm +51 -0
- package/dist/graph/queries/java.scm +20 -0
- package/dist/graph/queries/kotlin.scm +43 -0
- package/dist/graph/queries/lua.scm +36 -0
- package/dist/graph/queries/nix.scm +32 -0
- package/dist/graph/queries/php.scm +40 -0
- package/dist/graph/queries/ruby.scm +65 -0
- package/dist/graph/queries/rust.scm +41 -0
- package/dist/graph/queries/scala.scm +66 -0
- package/dist/graph/queries/solidity.scm +43 -0
- package/dist/graph/queries/swift.scm +51 -0
- package/dist/graph/refresh.d.ts +106 -0
- package/dist/graph/refresh.d.ts.map +1 -0
- package/dist/graph/refresh.js +430 -0
- package/dist/graph/refresh.js.map +1 -0
- package/dist/graph/relations.d.ts +14 -0
- package/dist/graph/relations.d.ts.map +1 -0
- package/dist/graph/relations.js +8 -0
- package/dist/graph/relations.js.map +1 -0
- package/dist/graph/resolve.d.ts +17 -0
- package/dist/graph/resolve.d.ts.map +1 -0
- package/dist/graph/resolve.js +716 -0
- package/dist/graph/resolve.js.map +1 -0
- package/dist/graph/root.d.ts +11 -0
- package/dist/graph/root.d.ts.map +1 -0
- package/dist/graph/root.js +40 -0
- package/dist/graph/root.js.map +1 -0
- package/dist/graph/scopes.d.ts +40 -0
- package/dist/graph/scopes.d.ts.map +1 -0
- package/dist/graph/scopes.js +383 -0
- package/dist/graph/scopes.js.map +1 -0
- package/dist/graph/seed.d.ts +38 -0
- package/dist/graph/seed.d.ts.map +1 -0
- package/dist/graph/seed.js +229 -0
- package/dist/graph/seed.js.map +1 -0
- package/dist/graph/source-files.d.ts +46 -0
- package/dist/graph/source-files.d.ts.map +1 -0
- package/dist/graph/source-files.js +118 -0
- package/dist/graph/source-files.js.map +1 -0
- package/dist/graph/traverse-cli.d.ts +55 -0
- package/dist/graph/traverse-cli.d.ts.map +1 -0
- package/dist/graph/traverse-cli.js +189 -0
- package/dist/graph/traverse-cli.js.map +1 -0
- package/dist/graph/traverse.d.ts +112 -0
- package/dist/graph/traverse.d.ts.map +1 -0
- package/dist/graph/traverse.js +192 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/graph/types.d.ts +78 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +14 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/wasm-runtime.d.ts +19 -0
- package/dist/graph/wasm-runtime.d.ts.map +1 -0
- package/dist/graph/wasm-runtime.js +38 -0
- package/dist/graph/wasm-runtime.js.map +1 -0
- package/dist/graph/workspace-cli.d.ts +56 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -0
- package/dist/graph/workspace-cli.js +111 -0
- package/dist/graph/workspace-cli.js.map +1 -0
- package/dist/graph/workspace.d.ts +172 -0
- package/dist/graph/workspace.d.ts.map +1 -0
- package/dist/graph/workspace.js +714 -0
- package/dist/graph/workspace.js.map +1 -0
- package/dist/graph/write.d.ts +13 -0
- package/dist/graph/write.d.ts.map +1 -0
- package/dist/graph/write.js +86 -0
- package/dist/graph/write.js.map +1 -0
- package/dist/hosts/antigravity.d.ts +14 -0
- package/dist/hosts/antigravity.d.ts.map +1 -0
- package/dist/hosts/antigravity.js +59 -0
- package/dist/hosts/antigravity.js.map +1 -0
- package/dist/hosts/auto.d.ts +37 -0
- package/dist/hosts/auto.d.ts.map +1 -0
- package/dist/hosts/auto.js +141 -0
- package/dist/hosts/auto.js.map +1 -0
- package/dist/hosts/claude-global.d.ts +72 -0
- package/dist/hosts/claude-global.d.ts.map +1 -0
- package/dist/hosts/claude-global.js +230 -0
- package/dist/hosts/claude-global.js.map +1 -0
- package/dist/hosts/config-write.d.ts +36 -0
- package/dist/hosts/config-write.d.ts.map +1 -0
- package/dist/hosts/config-write.js +64 -0
- package/dist/hosts/config-write.js.map +1 -0
- package/dist/hosts/init.d.ts +28 -0
- package/dist/hosts/init.d.ts.map +1 -0
- package/dist/hosts/init.js +89 -0
- package/dist/hosts/init.js.map +1 -0
- package/dist/hosts/instructions.d.ts +14 -0
- package/dist/hosts/instructions.d.ts.map +1 -0
- package/dist/hosts/instructions.js +95 -0
- package/dist/hosts/instructions.js.map +1 -0
- package/dist/hosts/mcp-config.d.ts +78 -0
- package/dist/hosts/mcp-config.d.ts.map +1 -0
- package/dist/hosts/mcp-config.js +251 -0
- package/dist/hosts/mcp-config.js.map +1 -0
- package/dist/hosts/plan.d.ts +32 -0
- package/dist/hosts/plan.d.ts.map +1 -0
- package/dist/hosts/plan.js +73 -0
- package/dist/hosts/plan.js.map +1 -0
- package/dist/hosts/registry.d.ts +41 -0
- package/dist/hosts/registry.d.ts.map +1 -0
- package/dist/hosts/registry.js +164 -0
- package/dist/hosts/registry.js.map +1 -0
- package/dist/hosts/retract.d.ts +47 -0
- package/dist/hosts/retract.d.ts.map +1 -0
- package/dist/hosts/retract.js +520 -0
- package/dist/hosts/retract.js.map +1 -0
- package/dist/hosts/sections.d.ts +47 -0
- package/dist/hosts/sections.d.ts.map +1 -0
- package/dist/hosts/sections.js +127 -0
- package/dist/hosts/sections.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/fs.d.ts +79 -0
- package/dist/ingest/fs.d.ts.map +1 -0
- package/dist/ingest/fs.js +306 -0
- package/dist/ingest/fs.js.map +1 -0
- package/dist/init-guard.d.ts +16 -0
- package/dist/init-guard.d.ts.map +1 -0
- package/dist/init-guard.js +27 -0
- package/dist/init-guard.js.map +1 -0
- package/dist/mcp/instructions.d.ts +5 -0
- package/dist/mcp/instructions.d.ts.map +1 -0
- package/dist/mcp/instructions.js +64 -0
- package/dist/mcp/instructions.js.map +1 -0
- package/dist/mcp/kept-listing.d.ts +27 -0
- package/dist/mcp/kept-listing.d.ts.map +1 -0
- package/dist/mcp/kept-listing.js +63 -0
- package/dist/mcp/kept-listing.js.map +1 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +147 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool-names.d.ts +27 -0
- package/dist/mcp/tool-names.d.ts.map +1 -0
- package/dist/mcp/tool-names.js +47 -0
- package/dist/mcp/tool-names.js.map +1 -0
- package/dist/mcp/tools.d.ts +22 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +440 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/node-version.d.ts +15 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +23 -0
- package/dist/node-version.js.map +1 -0
- package/dist/search/grep-cli.d.ts +35 -0
- package/dist/search/grep-cli.d.ts.map +1 -0
- package/dist/search/grep-cli.js +115 -0
- package/dist/search/grep-cli.js.map +1 -0
- package/dist/search/grep.d.ts +60 -0
- package/dist/search/grep.d.ts.map +1 -0
- package/dist/search/grep.js +143 -0
- package/dist/search/grep.js.map +1 -0
- package/dist/search/ignored-code.d.ts +9 -0
- package/dist/search/ignored-code.d.ts.map +1 -0
- package/dist/search/ignored-code.js +73 -0
- package/dist/search/ignored-code.js.map +1 -0
- package/dist/search/user-regexp.d.ts +26 -0
- package/dist/search/user-regexp.d.ts.map +1 -0
- package/dist/search/user-regexp.js +150 -0
- package/dist/search/user-regexp.js.map +1 -0
- package/dist/telemetry/contract.d.ts +117 -0
- package/dist/telemetry/contract.d.ts.map +1 -0
- package/dist/telemetry/contract.js +341 -0
- package/dist/telemetry/contract.js.map +1 -0
- package/dist/telemetry/flush.d.ts +21 -0
- package/dist/telemetry/flush.d.ts.map +1 -0
- package/dist/telemetry/flush.js +85 -0
- package/dist/telemetry/flush.js.map +1 -0
- package/dist/telemetry/gate.d.ts +30 -0
- package/dist/telemetry/gate.d.ts.map +1 -0
- package/dist/telemetry/gate.js +81 -0
- package/dist/telemetry/gate.js.map +1 -0
- package/dist/telemetry/identity.d.ts +40 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +80 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +16 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/key.d.ts +21 -0
- package/dist/telemetry/key.d.ts.map +1 -0
- package/dist/telemetry/key.js +49 -0
- package/dist/telemetry/key.js.map +1 -0
- package/dist/telemetry/notice.d.ts +19 -0
- package/dist/telemetry/notice.d.ts.map +1 -0
- package/dist/telemetry/notice.js +93 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +162 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/send.d.ts +19 -0
- package/dist/telemetry/send.d.ts.map +1 -0
- package/dist/telemetry/send.js +84 -0
- package/dist/telemetry/send.js.map +1 -0
- package/dist/telemetry/sessions.d.ts +25 -0
- package/dist/telemetry/sessions.d.ts.map +1 -0
- package/dist/telemetry/sessions.js +104 -0
- package/dist/telemetry/sessions.js.map +1 -0
- package/dist/telemetry/track.d.ts +62 -0
- package/dist/telemetry/track.d.ts.map +1 -0
- package/dist/telemetry/track.js +143 -0
- package/dist/telemetry/track.js.map +1 -0
- package/dist/upkeep-run.d.ts +22 -0
- package/dist/upkeep-run.d.ts.map +1 -0
- package/dist/upkeep-run.js +85 -0
- package/dist/upkeep-run.js.map +1 -0
- package/dist/upkeep.d.ts +127 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +252 -0
- package/dist/upkeep.js.map +1 -0
- package/dist/util/actions-folder.d.ts +2 -0
- package/dist/util/actions-folder.d.ts.map +1 -0
- package/dist/util/actions-folder.js +9 -0
- package/dist/util/actions-folder.js.map +1 -0
- package/dist/util/browser.d.ts +3 -0
- package/dist/util/browser.d.ts.map +1 -0
- package/dist/util/browser.js +14 -0
- package/dist/util/browser.js.map +1 -0
- package/dist/util/child-process.d.ts +15 -0
- package/dist/util/child-process.d.ts.map +1 -0
- package/dist/util/child-process.js +15 -0
- package/dist/util/child-process.js.map +1 -0
- package/dist/util/front-matter.d.ts +46 -0
- package/dist/util/front-matter.d.ts.map +1 -0
- package/dist/util/front-matter.js +113 -0
- package/dist/util/front-matter.js.map +1 -0
- package/dist/util/id.d.ts +7 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +14 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/paths.d.ts +29 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +71 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/private-file.d.ts +14 -0
- package/dist/util/private-file.d.ts.map +1 -0
- package/dist/util/private-file.js +82 -0
- package/dist/util/private-file.js.map +1 -0
- package/dist/util/source.d.ts +4 -0
- package/dist/util/source.d.ts.map +1 -0
- package/dist/util/source.js +12 -0
- package/dist/util/source.js.map +1 -0
- package/dist/util/spinner.d.ts +15 -0
- package/dist/util/spinner.d.ts.map +1 -0
- package/dist/util/spinner.js +52 -0
- package/dist/util/spinner.js.map +1 -0
- package/dist/util/state.d.ts +120 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +264 -0
- package/dist/util/state.js.map +1 -0
- package/dist/vendor/grammars/tree-sitter-go.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-go.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-java.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-java.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-kotlin.LICENSE +9 -0
- package/dist/vendor/grammars/tree-sitter-kotlin.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-php.LICENSE +22 -0
- package/dist/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-python.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-python.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-r.LICENSE +9 -0
- package/dist/vendor/grammars/tree-sitter-r.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-swift.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-swift.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/vendor/grammars/tree-sitter-typescript.LICENSE +21 -0
- package/dist/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/viewer/app.js +2 -0
- package/dist/viewer/index.html +49 -0
- package/dist/viewer/style.css +154 -0
- package/dist/viz/assemble.d.ts +83 -0
- package/dist/viz/assemble.d.ts.map +1 -0
- package/dist/viz/assemble.js +101 -0
- package/dist/viz/assemble.js.map +1 -0
- package/dist/viz/export.d.ts +39 -0
- package/dist/viz/export.d.ts.map +1 -0
- package/dist/viz/export.js +98 -0
- package/dist/viz/export.js.map +1 -0
- package/dist/viz/serve.d.ts +13 -0
- package/dist/viz/serve.d.ts.map +1 -0
- package/dist/viz/serve.js +130 -0
- package/dist/viz/serve.js.map +1 -0
- package/package.json +115 -4
- package/scripts/build-grammar-wasm.mjs +112 -0
- package/scripts/build-viewer.mjs +44 -0
- package/scripts/graph-quality.mjs +117 -0
- package/scripts/pack-trail.mjs +53 -0
- package/scripts/postinstall.mjs +65 -0
- package/scripts/run-tests.mjs +72 -0
- package/scripts/stamp-telemetry-key.mjs +56 -0
- package/scripts/tally-audit.mjs +140 -0
- package/scripts/vendor-grammars.mjs +53 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace federation — a "workspace" is a parent directory that holds two or
|
|
3
|
+
* more immediate git-repo children and has no source graph of its own. Instead
|
|
4
|
+
* of one mega-graph pooling every repo, each child keeps its OWN committable
|
|
5
|
+
* `graft/` (byte-identical to building that child standalone), and the parent
|
|
6
|
+
* holds a single `graft/workspace.json` index:
|
|
7
|
+
*
|
|
8
|
+
* { "version": 1, "children": ["repoA", "repoB"] }
|
|
9
|
+
*
|
|
10
|
+
* Queries run at the parent federate across the children: `ask` fuses every
|
|
11
|
+
* child's ranked hits with `fuseScopes`, reciprocal-rank fusion, so the big
|
|
12
|
+
* repo can't drown the small one. Scopes INSIDE one repo no longer take that
|
|
13
|
+
* path — they share corpus statistics and a normalization denominator, so they
|
|
14
|
+
* are combined by score (see `ask/fuse.ts`). Separate child repositories have
|
|
15
|
+
* no such shared scale, which is exactly the case rank fusion is for.
|
|
16
|
+
* `grep`/`map`/`check`/`callers` run per child and merge, always labeled
|
|
17
|
+
* `<child>/`.
|
|
18
|
+
*
|
|
19
|
+
* This module owns the pure/core pieces (the format, its readers/writers, graph
|
|
20
|
+
* loading, and the federated command bodies that return renderable data). The
|
|
21
|
+
* CLI print/exit wrappers and the per-child build orchestration live in
|
|
22
|
+
* `workspace-cli.ts`; `mcp/tools.ts` calls the federate* functions directly.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join, resolve, dirname } from "node:path";
|
|
26
|
+
import { contextDirFor } from "../context/node-file.js";
|
|
27
|
+
import { checkGraph } from "./check.js";
|
|
28
|
+
import { loadGraphCached } from "./load.js";
|
|
29
|
+
import { buildRepoMap, formatRepoMap } from "./map.js";
|
|
30
|
+
import { discoverWorkspaceChildren } from "./scopes.js";
|
|
31
|
+
import { callersSavings, headerOf, hitLine, looseNoteFor, } from "./traverse-cli.js";
|
|
32
|
+
import { edgeWalk, resolveSymbol } from "./traverse.js";
|
|
33
|
+
import { wiringPath } from "./write.js";
|
|
34
|
+
import { ask, } from "../ask/ask.js";
|
|
35
|
+
import { fileFirstRoundRobin, roundRobinQueues, } from "../ask/file-selection.js";
|
|
36
|
+
import { fuseScopes, STRONG_FLOOR, HIGH_FLOOR } from "../ask/fuse.js";
|
|
37
|
+
import { grepGraph } from "../search/grep.js";
|
|
38
|
+
import { formatGrepResult, zeroHitNote } from "../search/grep-cli.js";
|
|
39
|
+
import { withSavings } from "../context/savings.js";
|
|
40
|
+
import { cmd } from "../brand.js";
|
|
41
|
+
import { searchSemantic } from "../ask/semantic.js";
|
|
42
|
+
const WORKSPACE_FILE = "workspace.json";
|
|
43
|
+
/** Committed file that stores shared workspace settings (knowledgeRepos).
|
|
44
|
+
* Lives in .trail/ (committed) separate from .trail/cache/workspace.json (git-ignored). */
|
|
45
|
+
const KNOWLEDGE_REPOS_FILE = "knowledge-repos.json";
|
|
46
|
+
/** Absolute path to the knowledge-repos config for a workspace root. Always committed. */
|
|
47
|
+
export function knowledgeReposPath(root) {
|
|
48
|
+
return join(root, ".trail", KNOWLEDGE_REPOS_FILE);
|
|
49
|
+
}
|
|
50
|
+
/** Read the list of knowledge repo URLs for a workspace root. */
|
|
51
|
+
export function readKnowledgeRepos(root) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(knowledgeReposPath(root), "utf8"));
|
|
54
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Write the list of knowledge repo URLs. Ensures .trail/ exists. */
|
|
61
|
+
export function writeKnowledgeRepos(root, urls) {
|
|
62
|
+
const dir = join(root, ".trail");
|
|
63
|
+
mkdirSync(dir, { recursive: true });
|
|
64
|
+
writeFileSync(knowledgeReposPath(root), JSON.stringify(urls, null, 2) + "\n");
|
|
65
|
+
}
|
|
66
|
+
/** Absolute path to the workspace index for a parent root (`<dir>/graft/workspace.json`). */
|
|
67
|
+
export function workspacePath(root, override) {
|
|
68
|
+
return join(contextDirFor(root, override), WORKSPACE_FILE);
|
|
69
|
+
}
|
|
70
|
+
/** Read the workspace index, or null when the parent has none (not a workspace,
|
|
71
|
+
* or unparseable/foreign json — treated the same as absent). */
|
|
72
|
+
export function readWorkspace(root, override) {
|
|
73
|
+
const path = workspacePath(root, override);
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
76
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.children))
|
|
77
|
+
return null;
|
|
78
|
+
// knowledgeRepos: read from committed .trail/knowledge-repos.json first,
|
|
79
|
+
// fall back to workspace.json for backward compatibility.
|
|
80
|
+
const knowledgeRepos = readKnowledgeRepos(root).length > 0
|
|
81
|
+
? readKnowledgeRepos(root)
|
|
82
|
+
: Array.isArray(parsed.knowledgeRepos) ? parsed.knowledgeRepos.map(String) : undefined;
|
|
83
|
+
return {
|
|
84
|
+
version: 1,
|
|
85
|
+
children: parsed.children.map(String),
|
|
86
|
+
excludeFromDiscovery: Array.isArray(parsed.excludeFromDiscovery)
|
|
87
|
+
? parsed.excludeFromDiscovery.map(String)
|
|
88
|
+
: undefined,
|
|
89
|
+
knowledgeRepos,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Write the workspace index, sorting children for a stable, minimal git diff.
|
|
97
|
+
* Preserves `excludeFromDiscovery` across rebuilds. */
|
|
98
|
+
export function writeWorkspace(root, ws, override) {
|
|
99
|
+
const dir = contextDirFor(root, override);
|
|
100
|
+
mkdirSync(dir, { recursive: true });
|
|
101
|
+
const path = join(dir, WORKSPACE_FILE);
|
|
102
|
+
const sorted = { version: 1, children: [...ws.children].sort() };
|
|
103
|
+
if (ws.excludeFromDiscovery && ws.excludeFromDiscovery.length > 0)
|
|
104
|
+
sorted.excludeFromDiscovery = [...ws.excludeFromDiscovery].sort();
|
|
105
|
+
if (ws.knowledgeRepos && ws.knowledgeRepos.length > 0)
|
|
106
|
+
sorted.knowledgeRepos = [...ws.knowledgeRepos];
|
|
107
|
+
writeFileSync(path, JSON.stringify(sorted, null, 2) + "\n");
|
|
108
|
+
return path;
|
|
109
|
+
}
|
|
110
|
+
/** A parent is a workspace BUILD target when it has no own `.git` and ≥2 git
|
|
111
|
+
* children — or it was already split (a `workspace.json` is present). The
|
|
112
|
+
* no-own-`.git` guard keeps a normal repo with git submodules from being
|
|
113
|
+
* mistaken for a workspace. */
|
|
114
|
+
/**
|
|
115
|
+
* Walk up from `dir` to find the nearest directory with knowledgeRepos configured
|
|
116
|
+
* (.trail/knowledge-repos.json). Works for repos, workspaces, and plain folders.
|
|
117
|
+
* Falls back to the first workspace found if none has knowledgeRepos.
|
|
118
|
+
*/
|
|
119
|
+
export function findWorkspaceRoot(dir) {
|
|
120
|
+
let current = resolve(dir);
|
|
121
|
+
let firstWorkspace = null;
|
|
122
|
+
for (let i = 0; i < 8; i++) {
|
|
123
|
+
// Check for committed knowledge-repos.json first (works in any git repo).
|
|
124
|
+
if (readKnowledgeRepos(current).length > 0)
|
|
125
|
+
return current;
|
|
126
|
+
const ws = readWorkspace(current);
|
|
127
|
+
if (ws) {
|
|
128
|
+
if (!firstWorkspace)
|
|
129
|
+
firstWorkspace = current;
|
|
130
|
+
if (ws.knowledgeRepos && ws.knowledgeRepos.length > 0)
|
|
131
|
+
return current;
|
|
132
|
+
}
|
|
133
|
+
const parent = dirname(current);
|
|
134
|
+
if (parent === current)
|
|
135
|
+
break;
|
|
136
|
+
current = parent;
|
|
137
|
+
}
|
|
138
|
+
return firstWorkspace;
|
|
139
|
+
}
|
|
140
|
+
export function isWorkspaceBuildRoot(root, override) {
|
|
141
|
+
if (readWorkspace(root, override))
|
|
142
|
+
return true;
|
|
143
|
+
if (existsSync(join(root, ".git")))
|
|
144
|
+
return false;
|
|
145
|
+
return discoverWorkspaceChildren(root).length >= 2;
|
|
146
|
+
}
|
|
147
|
+
/** True when the parent has a mega-graph from an older single-graph build
|
|
148
|
+
* (`graft/.graph/wiring.json`) — the thing a workspace build migrates away. */
|
|
149
|
+
export function hasMegaGraph(root, override) {
|
|
150
|
+
return existsSync(wiringPath(contextDirFor(root, override)));
|
|
151
|
+
}
|
|
152
|
+
/** The EXACT split warning printed once, when a mega-graph parent is first
|
|
153
|
+
* built as a workspace. Templated on the child list so it names the real repos. */
|
|
154
|
+
export function migrationNote(children) {
|
|
155
|
+
const dirs = children.map((c) => `${c}/graft/`).join(", ");
|
|
156
|
+
return (`⚠ this folder contains ${children.length} separate git repos — splitting: ` +
|
|
157
|
+
`each repo now gets its own committable graft/ (${dirs}); the combined graph ` +
|
|
158
|
+
`here is replaced by a workspace index. Queries from here now search all repos, fairly.`);
|
|
159
|
+
}
|
|
160
|
+
/** Remove the parent's entire `graft/` tree — the mega-graph, its `.cache`, and
|
|
161
|
+
* any stale cards — so after `writeWorkspace` the parent holds ONLY
|
|
162
|
+
* workspace.json. Child graphs live in sibling `<child>/graft/`, never under
|
|
163
|
+
* this dir, so they are untouched. */
|
|
164
|
+
export function clearParentGraft(root, override) {
|
|
165
|
+
rmSync(contextDirFor(root, override), { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
/** Load each child's graph via `loadGraphCached`. Children come from
|
|
168
|
+
* workspace.json when present, otherwise from live git-child discovery (a
|
|
169
|
+
* not-yet-built workspace). A child without a built graph is counted into
|
|
170
|
+
* `missing`, not dropped. */
|
|
171
|
+
export function loadWorkspaceGraphs(root, override) {
|
|
172
|
+
const ws = readWorkspace(root, override);
|
|
173
|
+
const children = (ws ? ws.children : discoverWorkspaceChildren(root)).slice().sort();
|
|
174
|
+
const loaded = [];
|
|
175
|
+
const missing = [];
|
|
176
|
+
for (const child of children) {
|
|
177
|
+
const graph = loadGraphCached(contextDirFor(join(root, child)));
|
|
178
|
+
if (graph)
|
|
179
|
+
loaded.push({ child, graph });
|
|
180
|
+
else
|
|
181
|
+
missing.push(child);
|
|
182
|
+
}
|
|
183
|
+
return { loaded, missing };
|
|
184
|
+
}
|
|
185
|
+
/** "2 of 3 workspace repos have graphs; run graft build to cover repoC" — the
|
|
186
|
+
* coverage line federated commands append when some listed child is unbuilt.
|
|
187
|
+
* Empty string when every child has a graph. */
|
|
188
|
+
export function coverageNote(g) {
|
|
189
|
+
if (g.missing.length === 0)
|
|
190
|
+
return "";
|
|
191
|
+
const total = g.loaded.length + g.missing.length;
|
|
192
|
+
return `${g.loaded.length} of ${total} workspace repos have graphs; run ${cmd("graft build")} to cover ${g.missing.join(", ")}`;
|
|
193
|
+
}
|
|
194
|
+
/** Prefix a hit pointer with its child dir so a `path:span` (or concept path
|
|
195
|
+
* list) opens correctly from the parent. Leaves free-text fragments alone. */
|
|
196
|
+
function prefixPointer(child, pointer) {
|
|
197
|
+
return pointer
|
|
198
|
+
.split(",")
|
|
199
|
+
.map((part) => {
|
|
200
|
+
const t = part.trim();
|
|
201
|
+
if (!t || t.includes(" "))
|
|
202
|
+
return part;
|
|
203
|
+
return `${child}/${t}`;
|
|
204
|
+
})
|
|
205
|
+
.join(", ");
|
|
206
|
+
}
|
|
207
|
+
/** The exact child-relative file owning a lexical symbol hit. File nodes use a
|
|
208
|
+
* bare path; definition hits append `:Lx-Ly`. */
|
|
209
|
+
function hitFile(pointer) {
|
|
210
|
+
return pointer.match(/^(.*):L\d+-L\d+$/)?.[1] ?? pointer;
|
|
211
|
+
}
|
|
212
|
+
// STRONG_FLOOR / HIGH_FLOOR (a child federates if its top hit matched a
|
|
213
|
+
// query term in a NAME/PATH field ≥ STRONG_FLOOR, OR its overall name+path+
|
|
214
|
+
// body coverage is broad enough to be real even body-only, ≥ HIGH_FLOOR) are
|
|
215
|
+
// defined ONCE in `../ask/fuse.js` and imported here. This is now their only
|
|
216
|
+
// caller: a share-of-the-query threshold is the best available signal when the
|
|
217
|
+
// candidates carry no common score scale, which is the situation across child
|
|
218
|
+
// repositories. `rankScopesAndFuse` used to gate on them for the same reason
|
|
219
|
+
// and no longer needs to — within one repo the scores are comparable, so a
|
|
220
|
+
// weak scope is held back by scoring low rather than by a strength floor.
|
|
221
|
+
/**
|
|
222
|
+
* Federated `ask` across a workspace: run each child's own ask pipeline, then
|
|
223
|
+
* fuse ALL the children's scope lists with `fuseScopes` (reciprocal rank). Each
|
|
224
|
+
* child contributes one fusion scope per intra-child scope, labeled
|
|
225
|
+
* `<child>/<scope>` (or just `<child>` for a child's root scope), so a big repo
|
|
226
|
+
* can't drown a small one — rank positions are comparable across repos where
|
|
227
|
+
* raw scores are not.
|
|
228
|
+
*
|
|
229
|
+
* Rank fusion is still right HERE, and only here: each child was scored against
|
|
230
|
+
* its own corpus, so cross-child scores share no scale. Within a single repo
|
|
231
|
+
* that is no longer true — `rankScopesAndFuse` gives every scope the same
|
|
232
|
+
* statistics and the same denominator and combines them by score.
|
|
233
|
+
*
|
|
234
|
+
* Returns a normal `AskResult`; `formatAsk` renders it unchanged, labeling each
|
|
235
|
+
* hit `[<child>/…]` via the standard multi-scope path.
|
|
236
|
+
*/
|
|
237
|
+
export function federateAsk(root, override, query, opts = {}) {
|
|
238
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
239
|
+
const limit = opts.limit ?? 8;
|
|
240
|
+
const fileTopLock = opts.fileTopLock ?? true;
|
|
241
|
+
// `--in` scopes to a single child (and, past the child path, a sub-scope
|
|
242
|
+
// within it). A prefix naming no known child at all is a caller mistake.
|
|
243
|
+
// Children may be compound paths (e.g. "default-core/frontend"), so we
|
|
244
|
+
// find the longest child path that is a prefix of (or equal to) the --in
|
|
245
|
+
// value, rather than splitting on the first "/" only.
|
|
246
|
+
let onlyChild;
|
|
247
|
+
let childIn;
|
|
248
|
+
if (opts.in) {
|
|
249
|
+
const prefix = opts.in.replace(/\/+$/, "");
|
|
250
|
+
const allChildren = [...wg.loaded.map((l) => l.child), ...wg.missing].sort();
|
|
251
|
+
// Longest matching child path wins (e.g. "default-core/frontend" over "default-core").
|
|
252
|
+
const match = allChildren
|
|
253
|
+
.filter((c) => prefix === c || prefix.startsWith(c + "/"))
|
|
254
|
+
.sort((a, b) => b.length - a.length)[0];
|
|
255
|
+
if (!match) {
|
|
256
|
+
throw new Error(`no workspace repo matching "${prefix}" — repos: ${allChildren.join(", ")}`);
|
|
257
|
+
}
|
|
258
|
+
onlyChild = match;
|
|
259
|
+
const remaining = prefix.slice(match.length).replace(/^\//, "");
|
|
260
|
+
childIn = remaining || undefined;
|
|
261
|
+
}
|
|
262
|
+
// Pass 1: run each child's ask; keep its hits + RAW top-hit coverage.
|
|
263
|
+
const runs = [];
|
|
264
|
+
for (const { child } of wg.loaded) {
|
|
265
|
+
if (onlyChild && child !== onlyChild)
|
|
266
|
+
continue;
|
|
267
|
+
let r;
|
|
268
|
+
try {
|
|
269
|
+
// Over-fetch per child so cross-child fusion has enough candidates to
|
|
270
|
+
// rank before the final `limit` slice.
|
|
271
|
+
r = ask(join(root, child), query, {
|
|
272
|
+
limit: Math.max(limit * 4, 20),
|
|
273
|
+
source: opts.source,
|
|
274
|
+
full: opts.full,
|
|
275
|
+
graphRank: opts.graphRank,
|
|
276
|
+
in: childIn,
|
|
277
|
+
// The parent owns the final cross-repo ranking. Selecting inside each
|
|
278
|
+
// child would be undone by fusion and could truncate a hybrid list.
|
|
279
|
+
fileFirst: false,
|
|
280
|
+
fileComplement: fileTopLock,
|
|
281
|
+
includeRankingMetadata: fileTopLock,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
continue; // corrupt child, or a sub-scope --in matching nothing here
|
|
286
|
+
}
|
|
287
|
+
if (r.hits.length === 0)
|
|
288
|
+
continue;
|
|
289
|
+
const topGroup = r.ranking?.groups[0];
|
|
290
|
+
runs.push({
|
|
291
|
+
child,
|
|
292
|
+
hits: r.hits,
|
|
293
|
+
coverage: topGroup?.coverage ?? r.coverage ?? 0,
|
|
294
|
+
coverageStrong: topGroup?.coverageStrong ?? r.coverageStrong ?? 0,
|
|
295
|
+
baselineCoverage: r.ranking?.baselineCoverage ?? r.coverage ?? 0,
|
|
296
|
+
baselineCoverageStrong: r.ranking?.baselineCoverageStrong ?? r.coverageStrong ?? 0,
|
|
297
|
+
ranking: r.ranking,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// Cross-child participation gate on match STRENGTH, not a lenient ratio.
|
|
301
|
+
// Per-child hit scores are each per-child-normalized (top ~1.0 everywhere),
|
|
302
|
+
// so feeding them to fuseScopes makes its gate vacuous across repos — a junk
|
|
303
|
+
// body-token collision would federate beside another repo's genuine hits.
|
|
304
|
+
// Gate: a child federates iff its top hit matched a query term in a NAME/PATH
|
|
305
|
+
// field (coverageStrong ≥ STRONG_FLOOR — not just an incidental body token)
|
|
306
|
+
// OR its overall coverage is broad enough to be real (≥ HIGH_FLOOR). A
|
|
307
|
+
// body-only collision has coverageStrong 0 and coverage ~0.3 → gated to
|
|
308
|
+
// alsoMatched. Both floors are absolute + scale-invariant (see their docs).
|
|
309
|
+
// The gate is a cross-child FAIRNESS mechanism: it stops a weak body-token
|
|
310
|
+
// collision in one repo from federating beside another repo's genuine hits.
|
|
311
|
+
// An explicit `--in <child>` removes all cross-child competition — the caller
|
|
312
|
+
// has already chosen the scope — so the gate must NOT apply there, or the
|
|
313
|
+
// `also matched: <child> — narrow with --in <child>` hint would lead to an
|
|
314
|
+
// empty result for exactly the weak child it points at. With `onlyChild` set,
|
|
315
|
+
// every run (there is at most one) survives, matching a standalone child ask.
|
|
316
|
+
const gatedOut = [];
|
|
317
|
+
const survivors = [];
|
|
318
|
+
const baselineSurvivors = [];
|
|
319
|
+
for (const run of runs) {
|
|
320
|
+
const baselineEligible = onlyChild ||
|
|
321
|
+
run.baselineCoverageStrong >= STRONG_FLOOR ||
|
|
322
|
+
run.baselineCoverage >= HIGH_FLOOR;
|
|
323
|
+
const fileEligible = onlyChild ||
|
|
324
|
+
run.coverageStrong >= STRONG_FLOOR ||
|
|
325
|
+
run.coverage >= HIGH_FLOOR;
|
|
326
|
+
if (baselineEligible)
|
|
327
|
+
baselineSurvivors.push(run);
|
|
328
|
+
// Distributed file evidence may admit a child, but the exact baseline
|
|
329
|
+
// top lock must never resurrect a child that the final stream calls gated
|
|
330
|
+
// out. The production survivor set is therefore the union of both gates;
|
|
331
|
+
// baselineSurvivors remains the strict baseline subset used only to choose the
|
|
332
|
+
// locked pointer.
|
|
333
|
+
if (baselineEligible || fileEligible)
|
|
334
|
+
survivors.push(run);
|
|
335
|
+
else {
|
|
336
|
+
const best = run.ranking?.groups[0]?.hits[0] ?? run.hits[0];
|
|
337
|
+
gatedOut.push({ scope: run.child, bestId: prefixPointer(run.child, best.pointer) });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (fileTopLock) {
|
|
341
|
+
const qualifyHit = (child, hit, score = hit.score, scope = hit.scope ? `${child}/${hit.scope}` : child) => ({
|
|
342
|
+
...hit,
|
|
343
|
+
score,
|
|
344
|
+
scope,
|
|
345
|
+
pointer: prefixPointer(child, hit.pointer),
|
|
346
|
+
});
|
|
347
|
+
const sameHit = (a, b) => a.kind === b.kind && a.pointer === b.pointer && a.title === b.title;
|
|
348
|
+
// Baseline workspace stream: reproduce the all-span RRF exactly so the
|
|
349
|
+
// final lock owns the same concrete pointer and fused score as the baseline.
|
|
350
|
+
const baselineDocs = [];
|
|
351
|
+
const baselineBack = new Map();
|
|
352
|
+
for (const run of baselineSurvivors) {
|
|
353
|
+
const entries = run.ranking?.baseline ?? run.hits.map((hit, index) => ({
|
|
354
|
+
group: `singleton:${index}`,
|
|
355
|
+
hit,
|
|
356
|
+
}));
|
|
357
|
+
entries.forEach((entry, index) => {
|
|
358
|
+
// Keep the legacy id shape so equal-score baseline ties remain exact.
|
|
359
|
+
const id = `${run.child} ${index}`;
|
|
360
|
+
const scope = entry.hit.scope
|
|
361
|
+
? `${run.child}/${entry.hit.scope}`
|
|
362
|
+
: run.child;
|
|
363
|
+
baselineDocs.push({ id, scope, score: entry.hit.score });
|
|
364
|
+
baselineBack.set(id, {
|
|
365
|
+
child: run.child,
|
|
366
|
+
group: `${run.child}\0${entry.group}`,
|
|
367
|
+
hit: entry.hit,
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const baselineFused = fuseScopes(baselineDocs);
|
|
372
|
+
const baselineTopRanked = baselineFused.ranked[0];
|
|
373
|
+
const baselineTopBack = baselineTopRanked
|
|
374
|
+
? baselineBack.get(baselineTopRanked.id)
|
|
375
|
+
: undefined;
|
|
376
|
+
const baselineTop = baselineTopRanked && baselineTopBack
|
|
377
|
+
? qualifyHit(baselineTopBack.child, baselineTopBack.hit, baselineTopRanked.score, baselineTopRanked.scope)
|
|
378
|
+
: undefined;
|
|
379
|
+
// File-aware workspace stream: exactly one leader per child/file (concepts stay
|
|
380
|
+
// singleton groups) participates in RRF. Span queues remain in a side map
|
|
381
|
+
// and cannot consume reciprocal-rank positions.
|
|
382
|
+
const fileDocs = [];
|
|
383
|
+
const fileBack = new Map();
|
|
384
|
+
for (const run of survivors) {
|
|
385
|
+
const groups = run.ranking?.groups ?? run.hits.map((hit, index) => ({
|
|
386
|
+
key: `singleton:${index}`,
|
|
387
|
+
hits: [hit],
|
|
388
|
+
baselineHits: [hit],
|
|
389
|
+
coverage: 0,
|
|
390
|
+
coverageStrong: 0,
|
|
391
|
+
}));
|
|
392
|
+
groups.forEach((group, index) => {
|
|
393
|
+
const leader = group.hits[0];
|
|
394
|
+
if (!leader)
|
|
395
|
+
return;
|
|
396
|
+
const id = `${run.child} file ${String(index).padStart(8, "0")}`;
|
|
397
|
+
const scope = leader.scope ? `${run.child}/${leader.scope}` : run.child;
|
|
398
|
+
const key = `${run.child}\0${group.key}`;
|
|
399
|
+
fileDocs.push({ id, scope, score: leader.score });
|
|
400
|
+
fileBack.set(id, {
|
|
401
|
+
key,
|
|
402
|
+
child: run.child,
|
|
403
|
+
hits: group.hits,
|
|
404
|
+
baselineHits: group.baselineHits ?? [leader],
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const fileFused = fuseScopes(fileDocs);
|
|
409
|
+
const rankedGroups = fileFused.ranked.flatMap((ranked) => {
|
|
410
|
+
const group = fileBack.get(ranked.id);
|
|
411
|
+
if (!group)
|
|
412
|
+
return [];
|
|
413
|
+
return [{
|
|
414
|
+
...group,
|
|
415
|
+
// These spans are projections of one cross-child file document. Keep
|
|
416
|
+
// their public score on that same RRF scale; child-local tail scores are
|
|
417
|
+
// not comparable after workspace fusion and downstream re-sorting would
|
|
418
|
+
// otherwise silently undo the file ordering.
|
|
419
|
+
hits: group.hits.map((hit) => qualifyHit(group.child, hit, ranked.score, hit.scope ? `${group.child}/${hit.scope}` : group.child)),
|
|
420
|
+
baselineHits: group.baselineHits.map((hit) => qualifyHit(group.child, hit)),
|
|
421
|
+
}];
|
|
422
|
+
});
|
|
423
|
+
let projectedGroups = rankedGroups;
|
|
424
|
+
if (baselineTop && baselineTopBack) {
|
|
425
|
+
const key = baselineTopBack.group;
|
|
426
|
+
const existing = rankedGroups.find((group) => group.key === key);
|
|
427
|
+
const baselineQueue = existing?.baselineHits ?? [baselineTop];
|
|
428
|
+
const projectedScore = existing?.hits[0]?.score ?? baselineTop.score;
|
|
429
|
+
const locked = existing
|
|
430
|
+
? {
|
|
431
|
+
...existing,
|
|
432
|
+
hits: [
|
|
433
|
+
baselineTop,
|
|
434
|
+
...baselineQueue
|
|
435
|
+
.filter((hit) => !sameHit(hit, baselineTop))
|
|
436
|
+
.map((hit) => ({ ...hit, score: projectedScore })),
|
|
437
|
+
],
|
|
438
|
+
}
|
|
439
|
+
: {
|
|
440
|
+
key,
|
|
441
|
+
child: baselineTopBack.child,
|
|
442
|
+
hits: [baselineTop],
|
|
443
|
+
baselineHits: [baselineTop],
|
|
444
|
+
};
|
|
445
|
+
projectedGroups = [
|
|
446
|
+
locked,
|
|
447
|
+
...rankedGroups.filter((group) => group.key !== key),
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
const hits = roundRobinQueues(projectedGroups.map((group) => group.hits), limit);
|
|
451
|
+
const unmatched = [...fileFused.alsoMatched, ...gatedOut];
|
|
452
|
+
const note = coverageNote(wg);
|
|
453
|
+
const result = {
|
|
454
|
+
query,
|
|
455
|
+
mode: hits.length ? "lexical" : "empty",
|
|
456
|
+
hits,
|
|
457
|
+
};
|
|
458
|
+
if (hits.length) {
|
|
459
|
+
const lockedScope = baselineTop?.scope;
|
|
460
|
+
const federated = [
|
|
461
|
+
...new Set([
|
|
462
|
+
...(lockedScope ? [lockedScope] : []),
|
|
463
|
+
...fileFused.federated,
|
|
464
|
+
]),
|
|
465
|
+
];
|
|
466
|
+
const federatedSet = new Set(federated);
|
|
467
|
+
result.scopes = {
|
|
468
|
+
federated,
|
|
469
|
+
alsoMatched: unmatched.filter((match) => !federatedSet.has(match.scope)),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
result.note = `no matching nodes across ${wg.loaded.length} workspace repo(s) — try different words, or \`${cmd("graft build")}\` at a child`;
|
|
474
|
+
}
|
|
475
|
+
if (note)
|
|
476
|
+
result.note = result.note ? `${result.note}\n${note}` : note;
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
// Fuse the survivors' scope lists (within-child fusion is left untouched —
|
|
480
|
+
// each child already ran its own per-scope RRF).
|
|
481
|
+
const docs = [];
|
|
482
|
+
const back = new Map();
|
|
483
|
+
for (const { child, hits } of survivors) {
|
|
484
|
+
hits.forEach((hit, i) => {
|
|
485
|
+
const scope = hit.scope ? `${child}/${hit.scope}` : child;
|
|
486
|
+
const id = `${child} ${i}`;
|
|
487
|
+
docs.push({ id, scope, score: hit.score });
|
|
488
|
+
back.set(id, { child, hit });
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const fused = fuseScopes(docs);
|
|
492
|
+
const rankedHits = fused.ranked.map((rd) => {
|
|
493
|
+
const { child, hit } = back.get(rd.id);
|
|
494
|
+
const value = {
|
|
495
|
+
...hit,
|
|
496
|
+
score: rd.score,
|
|
497
|
+
scope: rd.scope,
|
|
498
|
+
pointer: prefixPointer(child, hit.pointer),
|
|
499
|
+
};
|
|
500
|
+
return {
|
|
501
|
+
// Lexical symbols share a file queue. Concepts and structural neighbours
|
|
502
|
+
// remain singleton partitions, so this lexical selection layer cannot
|
|
503
|
+
// reorder `who calls` / `dependencies of` answers.
|
|
504
|
+
group: hit.kind === "symbol"
|
|
505
|
+
? `file:${child}/${hitFile(hit.pointer)}`
|
|
506
|
+
: `singleton:${child}:${rd.id}`,
|
|
507
|
+
value,
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
const hits = fileFirstRoundRobin(rankedHits, limit);
|
|
511
|
+
const alsoMatched = [...fused.alsoMatched, ...gatedOut];
|
|
512
|
+
const note = coverageNote(wg);
|
|
513
|
+
const result = { query, mode: hits.length ? "lexical" : "empty", hits };
|
|
514
|
+
if (hits.length) {
|
|
515
|
+
const federated = fused.federated.length ? fused.federated : [...new Set(hits.map((h) => h.scope))];
|
|
516
|
+
result.scopes = { federated, alsoMatched };
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
result.note = `no matching nodes across ${wg.loaded.length} workspace repo(s) — try different words, or \`${cmd("graft build")}\` at a child`;
|
|
520
|
+
}
|
|
521
|
+
if (note)
|
|
522
|
+
result.note = result.note ? `${result.note}\n${note}` : note;
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
/** Merge every child's `GrepResult` into one, prefixing group paths with the
|
|
526
|
+
* child dir and re-sorting by coupling (inDegree desc, path asc) across repos.
|
|
527
|
+
* In-degree stays each child's own — it comes from that child's graph. */
|
|
528
|
+
export function federateGrep(root, override, pattern, opts = {}) {
|
|
529
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
530
|
+
const groups = [];
|
|
531
|
+
let filesSearched = 0;
|
|
532
|
+
let totalHits = 0;
|
|
533
|
+
const truncated = { files: 0, hits: 0 };
|
|
534
|
+
let savedFiles = 0;
|
|
535
|
+
let savedChars = 0;
|
|
536
|
+
for (const { child, graph } of wg.loaded) {
|
|
537
|
+
const r = grepGraph(graph, join(root, child), pattern, {
|
|
538
|
+
ignoreCase: opts.ignoreCase,
|
|
539
|
+
fixed: opts.fixed,
|
|
540
|
+
});
|
|
541
|
+
filesSearched += r.filesSearched;
|
|
542
|
+
totalHits += r.totalHits;
|
|
543
|
+
truncated.files += r.truncated.files;
|
|
544
|
+
truncated.hits += r.truncated.hits;
|
|
545
|
+
if (r.saved) {
|
|
546
|
+
savedFiles += r.saved.files;
|
|
547
|
+
savedChars += r.saved.baselineChars;
|
|
548
|
+
}
|
|
549
|
+
for (const g of r.groups) {
|
|
550
|
+
groups.push({
|
|
551
|
+
...g,
|
|
552
|
+
path: `${child}/${g.path}`,
|
|
553
|
+
symbol: g.symbol ? { ...g.symbol, path: `${child}/${g.symbol.path}` } : null,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
groups.sort((a, b) => b.inDegree - a.inDegree || a.path.localeCompare(b.path));
|
|
558
|
+
const saved = savedChars > 0 ? { files: savedFiles, baselineChars: savedChars } : undefined;
|
|
559
|
+
const result = { pattern, filesSearched, totalHits, groups, truncated, saved };
|
|
560
|
+
return { result, coverage: coverageNote(wg) };
|
|
561
|
+
}
|
|
562
|
+
/** One `graft map` section per child (each child's own map, budget split evenly
|
|
563
|
+
* across the loaded children), joined under `<child>/` headers. */
|
|
564
|
+
export function federateMap(root, override, opts = {}) {
|
|
565
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
566
|
+
const perChild = opts.maxDirs !== undefined && wg.loaded.length > 0
|
|
567
|
+
? Math.max(1, Math.floor(opts.maxDirs / wg.loaded.length))
|
|
568
|
+
: undefined;
|
|
569
|
+
const sections = wg.loaded.map(({ child, graph }) => {
|
|
570
|
+
const map = buildRepoMap(graph, perChild !== undefined ? { maxDirs: perChild } : {});
|
|
571
|
+
return `## ${child}/\n${formatRepoMap(map).trimEnd()}`;
|
|
572
|
+
});
|
|
573
|
+
const head = `workspace map — ${wg.loaded.length} repo(s)`;
|
|
574
|
+
const parts = [head, "", sections.join("\n\n")];
|
|
575
|
+
const cov = coverageNote(wg);
|
|
576
|
+
if (cov)
|
|
577
|
+
parts.push("", cov);
|
|
578
|
+
return parts.join("\n") + "\n";
|
|
579
|
+
}
|
|
580
|
+
/** Per-child drift status. `ok` is false when any BUILT child is stale — an
|
|
581
|
+
* unbuilt child is surfaced (coverage), never a failure. */
|
|
582
|
+
export async function federateCheck(root, override) {
|
|
583
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
584
|
+
const lines = [`workspace check — ${wg.loaded.length + wg.missing.length} repo(s)`, ""];
|
|
585
|
+
let ok = true;
|
|
586
|
+
for (const { child } of wg.loaded) {
|
|
587
|
+
const g = await checkGraph(join(root, child));
|
|
588
|
+
if (g.ok) {
|
|
589
|
+
lines.push(`${child}/: OK`);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
ok = false;
|
|
593
|
+
const bits = [];
|
|
594
|
+
if (g.added.length)
|
|
595
|
+
bits.push(`${g.added.length} added`);
|
|
596
|
+
if (g.removed.length)
|
|
597
|
+
bits.push(`${g.removed.length} removed`);
|
|
598
|
+
if (g.changed.length)
|
|
599
|
+
bits.push(`${g.changed.length} changed`);
|
|
600
|
+
if (g.stale.length)
|
|
601
|
+
bits.push(`${g.stale.length} stale`);
|
|
602
|
+
lines.push(`${child}/: STALE (${bits.join(", ")})`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
for (const child of wg.missing)
|
|
606
|
+
lines.push(`${child}/: not built (run ${cmd("graft build")})`);
|
|
607
|
+
const cov = coverageNote(wg);
|
|
608
|
+
if (cov)
|
|
609
|
+
lines.push("", cov);
|
|
610
|
+
return { text: lines.join("\n") + "\n", ok };
|
|
611
|
+
}
|
|
612
|
+
/** Resolve a symbol across every child, grouped per child. Reuses the shared
|
|
613
|
+
* traverse-cli formatters so each block reads exactly like `graft callers`. */
|
|
614
|
+
export function federateCallers(root, override, symbol, opts = {}) {
|
|
615
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
616
|
+
const direction = opts.direction ?? "in";
|
|
617
|
+
const depth = opts.depth && opts.depth >= 1 ? Math.floor(opts.depth) : 1;
|
|
618
|
+
const showDepth = depth > 1;
|
|
619
|
+
const blocks = [];
|
|
620
|
+
let found = false;
|
|
621
|
+
for (const { child, graph } of wg.loaded) {
|
|
622
|
+
const matches = resolveSymbol(graph, symbol, opts.in ? { in: opts.in } : {});
|
|
623
|
+
if (matches.length === 0)
|
|
624
|
+
continue;
|
|
625
|
+
found = true;
|
|
626
|
+
const results = matches.map((m) => ({ symbol: m, hits: edgeWalk(graph, m, direction, depth) }));
|
|
627
|
+
const lines = [`## ${child}/`];
|
|
628
|
+
for (const { symbol: sym, hits } of results) {
|
|
629
|
+
lines.push(headerOf(sym));
|
|
630
|
+
if (hits.length === 0)
|
|
631
|
+
lines.push(looseNoteFor(direction, sym.name, matches.length));
|
|
632
|
+
else
|
|
633
|
+
for (const h of hits)
|
|
634
|
+
lines.push(hitLine(direction, h, showDepth));
|
|
635
|
+
}
|
|
636
|
+
const body = lines.join("\n");
|
|
637
|
+
blocks.push(withSavings(body, callersSavings(graph, results)));
|
|
638
|
+
}
|
|
639
|
+
const cov = coverageNote(wg);
|
|
640
|
+
if (!found) {
|
|
641
|
+
const base = `no symbol "${symbol}" in any of the ${wg.loaded.length} workspace repo(s) — check spelling or run ${cmd("graft build")}`;
|
|
642
|
+
return { text: cov ? `${base}\n${cov}` : base, found: false };
|
|
643
|
+
}
|
|
644
|
+
let text = blocks.join("\n\n");
|
|
645
|
+
if (cov)
|
|
646
|
+
text += `\n\n${cov}`;
|
|
647
|
+
return { text, found: true };
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Split a parent into a workspace: build each git child (via the supplied
|
|
651
|
+
* `buildChild` callback, so this stays free of any engine/LLM dependency),
|
|
652
|
+
* then REPLACE the parent's `graft/` with just `workspace.json`. `onStart`
|
|
653
|
+
* fires once — before any child is built — carrying whether this build is a
|
|
654
|
+
* mega-graph migration, so the caller can print the one-time split warning
|
|
655
|
+
* first, exactly as the spec requires.
|
|
656
|
+
*
|
|
657
|
+
* The child build writes into `<child>/graft/` and is byte-identical to
|
|
658
|
+
* building that child standalone (`buildChild` is just `buildGraph(childDir)`),
|
|
659
|
+
* because nothing about the parent path enters the child's build.
|
|
660
|
+
*/
|
|
661
|
+
export async function splitWorkspace(root, override, buildChild, onStart) {
|
|
662
|
+
// Preserve user-configured exclusions across rebuilds.
|
|
663
|
+
const existing = readWorkspace(root, override);
|
|
664
|
+
const excluded = existing?.excludeFromDiscovery ?? [];
|
|
665
|
+
// Match exact name OR prefix (e.g. "Biometria" excludes "Biometria/frontend")
|
|
666
|
+
const isExcluded = (child) => excluded.some((e) => child === e || child.startsWith(`${e}/`));
|
|
667
|
+
const discovered = discoverWorkspaceChildren(root)
|
|
668
|
+
.filter((c) => !isExcluded(c))
|
|
669
|
+
.sort();
|
|
670
|
+
const migrated = hasMegaGraph(root, override);
|
|
671
|
+
onStart?.({ children: discovered, migrated });
|
|
672
|
+
for (const child of discovered)
|
|
673
|
+
await buildChild(join(root, child), child);
|
|
674
|
+
clearParentGraft(root, override);
|
|
675
|
+
writeWorkspace(root, { version: 1, children: discovered, excludeFromDiscovery: existing?.excludeFromDiscovery }, override);
|
|
676
|
+
return { children: discovered, migrated };
|
|
677
|
+
}
|
|
678
|
+
export { formatGrepResult, zeroHitNote };
|
|
679
|
+
/**
|
|
680
|
+
* Federated semantic search across all workspace children.
|
|
681
|
+
* For each child, queries that child's embeddings DB and prefixes hits with
|
|
682
|
+
* `[child/]`. All hits are merged, sorted by score desc, top `limit` returned.
|
|
683
|
+
*/
|
|
684
|
+
export async function federateSemantic(root, override, query, limit) {
|
|
685
|
+
const wg = loadWorkspaceGraphs(root, override);
|
|
686
|
+
const allHits = [];
|
|
687
|
+
for (const { child } of wg.loaded) {
|
|
688
|
+
const dbPath = join(root, child, 'graft', '.cache', 'embeddings.db');
|
|
689
|
+
try {
|
|
690
|
+
const hits = await searchSemantic(query, dbPath, limit * 2);
|
|
691
|
+
for (const h of hits) {
|
|
692
|
+
allHits.push({
|
|
693
|
+
child,
|
|
694
|
+
node_id: h.node_id,
|
|
695
|
+
file_path: `[${child}/]${h.file_path}`,
|
|
696
|
+
symbol: h.symbol,
|
|
697
|
+
score: h.score,
|
|
698
|
+
snippet: h.snippet,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
// Child has no embeddings yet — skip silently.
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (allHits.length === 0) {
|
|
707
|
+
return { text: 'no semantic matches found across workspace children', isError: false };
|
|
708
|
+
}
|
|
709
|
+
allHits.sort((a, b) => b.score - a.score);
|
|
710
|
+
const top = allHits.slice(0, limit);
|
|
711
|
+
const lines = top.map((h, i) => `${i + 1}. ${h.file_path} · ${h.symbol} (score: ${h.score})\n ${h.snippet}`);
|
|
712
|
+
return { text: lines.join('\n\n'), isError: false };
|
|
713
|
+
}
|
|
714
|
+
//# sourceMappingURL=workspace.js.map
|