@shanepadgett/tau-agent 0.27.0 → 0.28.1

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.
Files changed (113) hide show
  1. package/extensions/context/sync.ts +1 -9
  2. package/extensions/context-pruning/prune.ts +2 -2
  3. package/extensions/explore/README.md +13 -21
  4. package/extensions/explore/ast/adapter.ts +97 -0
  5. package/extensions/explore/ast/engine.ts +470 -0
  6. package/extensions/explore/ast/format/ast-search.ts +97 -0
  7. package/extensions/explore/ast/format/composite.ts +26 -0
  8. package/extensions/explore/ast/format/context.ts +66 -0
  9. package/extensions/explore/ast/format/deps.ts +86 -0
  10. package/extensions/explore/ast/format/discover.ts +85 -0
  11. package/extensions/explore/ast/format/impact.ts +105 -0
  12. package/extensions/explore/ast/format/outline.ts +66 -0
  13. package/extensions/explore/ast/format/relationships.ts +81 -0
  14. package/extensions/explore/ast/format/show.ts +38 -0
  15. package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
  16. package/extensions/explore/ast/grammars/manifest.json +41 -0
  17. package/extensions/explore/ast/grammars/manifest.ts +99 -0
  18. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  19. package/extensions/explore/ast/grammars/swift.wasm +0 -0
  20. package/extensions/explore/ast/graph/file-graph.ts +360 -0
  21. package/extensions/explore/ast/graph/relationships.ts +597 -0
  22. package/extensions/explore/ast/identity.ts +119 -0
  23. package/extensions/explore/ast/ir.ts +136 -0
  24. package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
  25. package/extensions/explore/ast/languages/csharp.ts +493 -0
  26. package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
  27. package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
  28. package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
  29. package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
  30. package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
  31. package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
  32. package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
  33. package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
  34. package/extensions/explore/ast/languages/go.ts +545 -0
  35. package/extensions/explore/ast/languages/java.ts +412 -0
  36. package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
  37. package/extensions/explore/ast/languages/kotlin.ts +611 -0
  38. package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
  39. package/extensions/explore/ast/languages/odin.ts +407 -0
  40. package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
  41. package/extensions/explore/ast/languages/rust.ts +506 -0
  42. package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
  43. package/extensions/explore/ast/languages/swift.ts +466 -0
  44. package/extensions/explore/ast/languages/tree.ts +220 -0
  45. package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
  46. package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
  47. package/extensions/explore/ast/languages/typescript.ts +751 -0
  48. package/extensions/explore/ast/markdown.ts +145 -0
  49. package/extensions/explore/ast/package-surface.ts +42 -0
  50. package/extensions/explore/ast/queries/ast-search.ts +306 -0
  51. package/extensions/explore/ast/queries/composite-target.ts +77 -0
  52. package/extensions/explore/ast/queries/context.ts +406 -0
  53. package/extensions/explore/ast/queries/discover.ts +427 -0
  54. package/extensions/explore/ast/queries/impact.ts +250 -0
  55. package/extensions/explore/ast/queries/outline.ts +203 -0
  56. package/extensions/explore/ast/queries/show.ts +262 -0
  57. package/extensions/explore/ast/query.ts +48 -0
  58. package/extensions/explore/ast/read/hook.ts +125 -0
  59. package/extensions/explore/ast/read/policy.ts +55 -0
  60. package/extensions/explore/ast/registry.ts +120 -0
  61. package/extensions/explore/ast/scan.ts +76 -0
  62. package/extensions/explore/ast/slice.ts +44 -0
  63. package/extensions/explore/ast/tools/ast-search.ts +138 -0
  64. package/extensions/explore/ast/tools/context.ts +126 -0
  65. package/extensions/explore/ast/tools/deps.ts +72 -0
  66. package/extensions/explore/ast/tools/discover.ts +208 -0
  67. package/extensions/explore/ast/tools/impact.ts +135 -0
  68. package/extensions/explore/ast/tools/outline.ts +200 -0
  69. package/extensions/explore/ast/tools/relationships.ts +160 -0
  70. package/extensions/explore/ast/tools/render.ts +121 -0
  71. package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
  72. package/extensions/explore/ast/tools/show.ts +116 -0
  73. package/extensions/explore/guidance.ts +52 -0
  74. package/extensions/explore/index.ts +82 -92
  75. package/extensions/explore/read/autoread.ts +118 -0
  76. package/extensions/explore/settings.ts +49 -31
  77. package/extensions/explore/traverse.ts +254 -91
  78. package/extensions/handoff/index.ts +1 -1
  79. package/extensions/publish/index.ts +18 -85
  80. package/extensions/publish/publish-ui.ts +0 -4
  81. package/extensions/soul/README.md +7 -4
  82. package/extensions/soul/index.ts +11 -5
  83. package/extensions/soul/prompt.ts +31 -13
  84. package/extensions/soul/settings.ts +7 -2
  85. package/extensions/subagent/agents/context-sync.md +6 -10
  86. package/extensions/subagent/agents/review.md +24 -55
  87. package/extensions/subagent/agents/scout.md +38 -115
  88. package/extensions/subagent/run.ts +1 -1
  89. package/extensions/tau-help/help.md +2 -4
  90. package/package.json +6 -4
  91. package/schemas/tau.schema.json +35 -30
  92. package/{extensions/explore → shared}/autoread.ts +21 -7
  93. package/shared/events.ts +1 -1
  94. package/shared/full-file-knowledge.ts +49 -0
  95. package/extensions/explore/ast-guidance.ts +0 -67
  96. package/extensions/explore/ast-languages.ts +0 -61
  97. package/extensions/explore/ast-tools.ts +0 -1994
  98. package/extensions/explore/ast-worker.ts +0 -1025
  99. package/extensions/explore/find.ts +0 -147
  100. package/extensions/explore/full-file-knowledge.ts +0 -234
  101. package/extensions/explore/grep.ts +0 -749
  102. package/extensions/explore/limits.ts +0 -22
  103. package/extensions/explore/ls.ts +0 -113
  104. package/extensions/explore/orientation-state.ts +0 -280
  105. package/extensions/explore/path-display.ts +0 -41
  106. package/extensions/explore/path-tree.ts +0 -150
  107. package/extensions/explore/read-cache.ts +0 -273
  108. package/extensions/explore/read-snapshots.ts +0 -57
  109. package/extensions/explore/read-stats-panel.ts +0 -176
  110. package/extensions/explore/read-stats.ts +0 -138
  111. package/extensions/explore/read.ts +0 -372
  112. package/extensions/explore/result.ts +0 -29
  113. package/native-bin/darwin-arm64/tau-ast +0 -0
@@ -0,0 +1,41 @@
1
+ {
2
+ "webTreeSitter": "0.26.11",
3
+ "vscodeTreeSitterWasm": "0.3.1",
4
+ "treeSitterCli": "0.26.11",
5
+ "wasiSdk": "wasi-sdk-29",
6
+ "grammars": [
7
+ { "id": "typescript", "source": "vscode", "file": "tree-sitter-typescript.wasm" },
8
+ { "id": "tsx", "source": "vscode", "file": "tree-sitter-tsx.wasm" },
9
+ { "id": "go", "source": "vscode", "file": "tree-sitter-go.wasm" },
10
+ { "id": "rust", "source": "vscode", "file": "tree-sitter-rust.wasm" },
11
+ { "id": "c_sharp", "source": "vscode", "file": "tree-sitter-c-sharp.wasm" },
12
+ { "id": "java", "source": "vscode", "file": "tree-sitter-java.wasm" },
13
+ {
14
+ "id": "kotlin",
15
+ "source": "built",
16
+ "file": "kotlin.wasm",
17
+ "repo": "https://github.com/fwcd/tree-sitter-kotlin",
18
+ "tag": "0.3.8",
19
+ "rev": "9e7e62415a0546efb4549e730af1d9bf5460a37b",
20
+ "subdir": "."
21
+ },
22
+ {
23
+ "id": "swift",
24
+ "source": "release",
25
+ "file": "swift.wasm",
26
+ "repo": "https://github.com/alex-pinkus/tree-sitter-swift",
27
+ "tag": "0.7.3",
28
+ "rev": "b8b22bffbb3441780e6471665bacfb263741c86a",
29
+ "url": "https://github.com/alex-pinkus/tree-sitter-swift/releases/download/0.7.3/tree-sitter-swift.wasm"
30
+ },
31
+ {
32
+ "id": "odin",
33
+ "source": "built",
34
+ "file": "odin.wasm",
35
+ "repo": "https://github.com/tree-sitter-grammars/tree-sitter-odin",
36
+ "tag": "v1.3.0",
37
+ "rev": "e8adc739b78409a99f8c31313f0bb54cc538cf73",
38
+ "subdir": "."
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,99 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ export type VscodeGrammarPin = {
7
+ id: string;
8
+ source: "vscode";
9
+ file: string;
10
+ };
11
+
12
+ export type BuiltGrammarPin = {
13
+ id: string;
14
+ source: "built";
15
+ file: string;
16
+ repo: string;
17
+ tag: string;
18
+ rev: string;
19
+ subdir: string;
20
+ };
21
+
22
+ export type ReleaseGrammarPin = {
23
+ id: string;
24
+ source: "release";
25
+ file: string;
26
+ repo: string;
27
+ tag: string;
28
+ rev: string;
29
+ url: string;
30
+ };
31
+
32
+ export type GrammarPin = VscodeGrammarPin | BuiltGrammarPin | ReleaseGrammarPin;
33
+
34
+ export type GrammarManifest = {
35
+ webTreeSitter: string;
36
+ vscodeTreeSitterWasm: string;
37
+ treeSitterCli: string;
38
+ wasiSdk: string;
39
+ grammars: GrammarPin[];
40
+ };
41
+
42
+ export const grammarsDir = dirname(fileURLToPath(import.meta.url));
43
+ const resolveModule = createRequire(import.meta.url).resolve;
44
+
45
+ export function runtimeWasmPath(): string {
46
+ return join(dirname(resolveModule("web-tree-sitter")), "web-tree-sitter.wasm");
47
+ }
48
+
49
+ export function grammarWasmPath(pin: GrammarPin): string {
50
+ if (pin.source === "vscode") {
51
+ return join(dirname(resolveModule("@vscode/tree-sitter-wasm")), pin.file);
52
+ }
53
+ return join(grammarsDir, pin.file);
54
+ }
55
+
56
+ function requireString(record: Record<string, unknown>, key: string, context: string): string {
57
+ const value = record[key];
58
+ if (typeof value !== "string" || value.length === 0) {
59
+ throw new Error(`grammar manifest: ${context}.${key} must be a non-empty string`);
60
+ }
61
+ return value;
62
+ }
63
+
64
+ function parseGrammarPin(entry: unknown, index: number): GrammarPin {
65
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
66
+ throw new Error(`grammar manifest: grammars[${index}] must be an object`);
67
+ }
68
+ const record = entry as Record<string, unknown>;
69
+ const context = `grammars[${index}]`;
70
+ const id = requireString(record, "id", context);
71
+ const file = requireString(record, "file", context);
72
+ const source = requireString(record, "source", context);
73
+ if (source === "vscode") return { id, source, file };
74
+ const repo = requireString(record, "repo", context);
75
+ const tag = requireString(record, "tag", context);
76
+ const rev = requireString(record, "rev", context);
77
+ if (source === "built")
78
+ return { id, source, file, repo, tag, rev, subdir: requireString(record, "subdir", context) };
79
+ if (source === "release") return { id, source, file, repo, tag, rev, url: requireString(record, "url", context) };
80
+ throw new Error(`grammar manifest: ${context}.source must be vscode, built, or release`);
81
+ }
82
+
83
+ export function loadGrammarManifest(): GrammarManifest {
84
+ const parsed: unknown = JSON.parse(readFileSync(join(grammarsDir, "manifest.json"), "utf8"));
85
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
86
+ throw new Error("grammar manifest: root must be an object");
87
+ }
88
+ const root = parsed as Record<string, unknown>;
89
+ if (!Array.isArray(root.grammars) || root.grammars.length === 0) {
90
+ throw new Error("grammar manifest: grammars must be a non-empty array");
91
+ }
92
+ return {
93
+ webTreeSitter: requireString(root, "webTreeSitter", "root"),
94
+ vscodeTreeSitterWasm: requireString(root, "vscodeTreeSitterWasm", "root"),
95
+ treeSitterCli: requireString(root, "treeSitterCli", "root"),
96
+ wasiSdk: requireString(root, "wasiSdk", "root"),
97
+ grammars: root.grammars.map(parseGrammarPin),
98
+ };
99
+ }
@@ -0,0 +1,360 @@
1
+ import { lstat, opendir } from "node:fs/promises";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import type { FileDepHost, LanguageAdapter } from "../adapter.ts";
4
+ import type { ExploreEngine } from "../engine.ts";
5
+ import { scanSources } from "../scan.ts";
6
+ import { formatPathForDisplay, pathResolutionError, resolveExplorePath } from "../../traverse.ts";
7
+
8
+ export type FileGraphEdge =
9
+ | { kind: "internal"; from: string; to: string }
10
+ | { kind: "external"; from: string; id: string };
11
+
12
+ export type FileDepHit = {
13
+ depth: number;
14
+ /** Absolute path for internal hits. */
15
+ path: string | undefined;
16
+ /** External package/module id. */
17
+ externalId: string | undefined;
18
+ };
19
+
20
+ export type FileDepQueryResult = {
21
+ seedPath: string;
22
+ depth: number;
23
+ hits: FileDepHit[];
24
+ resultLimit: number;
25
+ resultLimitReached: boolean;
26
+ };
27
+
28
+ export type ExploreFileGraph = {
29
+ deps(path: string, depth: number, resultLimit: number, signal: AbortSignal): Promise<FileDepQueryResult>;
30
+ reverseDeps(path: string, depth: number, resultLimit: number, signal: AbortSignal): Promise<FileDepQueryResult>;
31
+ /** Forward edges for one file (impact/relationships). */
32
+ forwardEdges(path: string, signal: AbortSignal): Promise<readonly FileGraphEdge[]>;
33
+ invalidate(paths: readonly string[]): void;
34
+ clear(): void;
35
+ };
36
+
37
+ type ForwardEntry = {
38
+ contentHash: string;
39
+ edges: FileGraphEdge[];
40
+ };
41
+
42
+ type ReverseIndex = {
43
+ /** target absolute path → importers */
44
+ byTarget: Map<string, string[]>;
45
+ };
46
+
47
+ function isWithin(parent: string, child: string): boolean {
48
+ const rel = relative(resolve(parent), resolve(child));
49
+ return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
50
+ }
51
+
52
+ async function pathExists(path: string): Promise<boolean> {
53
+ try {
54
+ await lstat(path);
55
+ return true;
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ async function isFile(path: string): Promise<boolean> {
62
+ try {
63
+ return (await lstat(path)).isFile();
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ async function readDir(path: string): Promise<string[]> {
70
+ try {
71
+ const dir = await opendir(path);
72
+ const names: string[] = [];
73
+ for await (const entry of dir) names.push(entry.name);
74
+ return names;
75
+ } catch {
76
+ return [];
77
+ }
78
+ }
79
+
80
+ async function nearestGitRoot(startDir: string): Promise<string | undefined> {
81
+ let dir = resolve(startDir);
82
+ for (;;) {
83
+ if (await pathExists(resolve(dir, ".git"))) return dir;
84
+ const parent = dirname(dir);
85
+ if (parent === dir) return undefined;
86
+ dir = parent;
87
+ }
88
+ }
89
+
90
+ async function scopeRootFor(absolutePath: string, cwd: string): Promise<string> {
91
+ if (isWithin(cwd, absolutePath)) return cwd;
92
+ const git = await nearestGitRoot(dirname(absolutePath));
93
+ if (git !== undefined) return git;
94
+ return dirname(absolutePath);
95
+ }
96
+
97
+ function requireFileDepsAdapter(
98
+ adapter: LanguageAdapter | undefined,
99
+ absolutePath: string,
100
+ cwd: string,
101
+ ): LanguageAdapter {
102
+ if (adapter === undefined) {
103
+ throw new Error(`Unsupported language for path: ${formatPathForDisplay(absolutePath, cwd)}`);
104
+ }
105
+ if (!adapter.capabilities.fileDeps || adapter.resolveFileDep === undefined) {
106
+ throw new Error(`File dependency graph unavailable for ${adapter.id} (fileDeps capability not supported)`);
107
+ }
108
+ return adapter;
109
+ }
110
+
111
+ function sortUnique(paths: readonly string[]): string[] {
112
+ return [...new Set(paths.map((path) => resolve(path)))].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
113
+ }
114
+
115
+ /**
116
+ * Session file-import graph. Shared code never interprets import syntax —
117
+ * adapters own resolveFileDep.
118
+ */
119
+ export function createFileGraph(engine: ExploreEngine): ExploreFileGraph {
120
+ const forwardCache = new Map<string, ForwardEntry>();
121
+ const reverseCache = new Map<string, ReverseIndex>();
122
+ const memo = new Map<string, unknown>();
123
+
124
+ const makeHost = (scopeRoot: string): FileDepHost => ({
125
+ cwd: engine.cwd,
126
+ scopeRoot,
127
+ pathExists,
128
+ isFile,
129
+ readDir,
130
+ ownsPath: (path) => {
131
+ const adapter = engine.registry.adapterForPath(path);
132
+ return adapter !== undefined && adapter.capabilities.fileDeps;
133
+ },
134
+ memo,
135
+ });
136
+
137
+ const loadForward = async (
138
+ absolutePath: string,
139
+ scopeRoot: string,
140
+ signal: AbortSignal,
141
+ ): Promise<FileGraphEdge[]> => {
142
+ signal.throwIfAborted();
143
+ const adapter = requireFileDepsAdapter(engine.registry.adapterForPath(absolutePath), absolutePath, engine.cwd);
144
+ const resolveFileDep = adapter.resolveFileDep;
145
+ if (resolveFileDep === undefined) {
146
+ throw new Error(`File dependency graph unavailable for ${adapter.id} (fileDeps capability not supported)`);
147
+ }
148
+
149
+ const ir = await engine.irForFile(absolutePath);
150
+ const cached = forwardCache.get(absolutePath);
151
+ if (cached !== undefined && cached.contentHash === ir.contentHash) return cached.edges;
152
+
153
+ const host = makeHost(scopeRoot);
154
+ const edges: FileGraphEdge[] = [];
155
+ const seenInternal = new Set<string>();
156
+ const seenExternal = new Set<string>();
157
+
158
+ for (const imp of ir.imports) {
159
+ signal.throwIfAborted();
160
+ const resolution = await resolveFileDep(absolutePath, imp.specifier, host, signal);
161
+ if (resolution.kind === "unresolved") continue;
162
+ if (resolution.kind === "external") {
163
+ if (seenExternal.has(resolution.id)) continue;
164
+ seenExternal.add(resolution.id);
165
+ edges.push({ kind: "external", from: absolutePath, id: resolution.id });
166
+ continue;
167
+ }
168
+ for (const target of sortUnique(resolution.paths)) {
169
+ if (!isWithin(scopeRoot, target)) continue;
170
+ if (!host.ownsPath(target)) continue;
171
+ if (target === absolutePath) continue;
172
+ if (seenInternal.has(target)) continue;
173
+ seenInternal.add(target);
174
+ edges.push({ kind: "internal", from: absolutePath, to: target });
175
+ }
176
+ }
177
+
178
+ forwardCache.set(absolutePath, { contentHash: ir.contentHash, edges });
179
+ return edges;
180
+ };
181
+
182
+ const ensureReverse = async (scopeRoot: string, signal: AbortSignal): Promise<ReverseIndex> => {
183
+ const cached = reverseCache.get(scopeRoot);
184
+ if (cached !== undefined) return cached;
185
+
186
+ const byTarget = new Map<string, string[]>();
187
+ const scan = scanSources({
188
+ engine,
189
+ cwd: engine.cwd,
190
+ root: scopeRoot,
191
+ signal,
192
+ });
193
+ let step = await scan.next();
194
+ while (!step.done) {
195
+ signal.throwIfAborted();
196
+ const filePath = step.value.ir.path;
197
+ const adapter = engine.registry.adapterForPath(filePath);
198
+ if (adapter?.capabilities.fileDeps === true && adapter.resolveFileDep !== undefined) {
199
+ try {
200
+ const edges = await loadForward(filePath, scopeRoot, signal);
201
+ for (const edge of edges) {
202
+ if (edge.kind !== "internal") continue;
203
+ const list = byTarget.get(edge.to);
204
+ if (list === undefined) byTarget.set(edge.to, [edge.from]);
205
+ else if (!list.includes(edge.from)) list.push(edge.from);
206
+ }
207
+ } catch {
208
+ // Skip files that fail resolve/parse during reverse build; keep scanning.
209
+ }
210
+ }
211
+ step = await scan.next();
212
+ }
213
+
214
+ for (const [, importers] of byTarget) {
215
+ importers.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
216
+ }
217
+
218
+ const index: ReverseIndex = { byTarget };
219
+ reverseCache.set(scopeRoot, index);
220
+ return index;
221
+ };
222
+
223
+ const collectForwardHits = async (
224
+ seedPath: string,
225
+ depth: number,
226
+ resultLimit: number,
227
+ signal: AbortSignal,
228
+ ): Promise<FileDepQueryResult> => {
229
+ const scopeRoot = await scopeRootFor(seedPath, engine.cwd);
230
+ const hits: FileDepHit[] = [];
231
+ let resultLimitReached = false;
232
+ const listedInternal = new Set<string>();
233
+ const listedExternal = new Set<string>();
234
+
235
+ const queue: { path: string; depth: number }[] = [{ path: seedPath, depth: 0 }];
236
+ const visited = new Set<string>([seedPath]);
237
+
238
+ while (queue.length > 0) {
239
+ signal.throwIfAborted();
240
+ const current = queue.shift();
241
+ if (current === undefined) break;
242
+ if (current.depth >= depth) continue;
243
+
244
+ const edges = await loadForward(current.path, scopeRoot, signal);
245
+ const nextDepth = current.depth + 1;
246
+
247
+ for (const edge of edges) {
248
+ if (hits.length >= resultLimit) {
249
+ resultLimitReached = true;
250
+ break;
251
+ }
252
+ if (edge.kind === "external") {
253
+ if (listedExternal.has(edge.id)) continue;
254
+ listedExternal.add(edge.id);
255
+ hits.push({ depth: nextDepth, path: undefined, externalId: edge.id });
256
+ continue;
257
+ }
258
+ if (listedInternal.has(edge.to)) continue;
259
+ listedInternal.add(edge.to);
260
+ hits.push({ depth: nextDepth, path: edge.to, externalId: undefined });
261
+ if (nextDepth < depth && !visited.has(edge.to)) {
262
+ visited.add(edge.to);
263
+ queue.push({ path: edge.to, depth: nextDepth });
264
+ }
265
+ }
266
+ if (resultLimitReached) break;
267
+ }
268
+
269
+ return { seedPath, depth, hits, resultLimit, resultLimitReached };
270
+ };
271
+
272
+ const collectReverseHits = async (
273
+ seedPath: string,
274
+ depth: number,
275
+ resultLimit: number,
276
+ signal: AbortSignal,
277
+ ): Promise<FileDepQueryResult> => {
278
+ const scopeRoot = await scopeRootFor(seedPath, engine.cwd);
279
+ const index = await ensureReverse(scopeRoot, signal);
280
+ const hits: FileDepHit[] = [];
281
+ let resultLimitReached = false;
282
+ const listed = new Set<string>();
283
+
284
+ const queue: { path: string; depth: number }[] = [{ path: seedPath, depth: 0 }];
285
+ const visited = new Set<string>([seedPath]);
286
+
287
+ while (queue.length > 0) {
288
+ signal.throwIfAborted();
289
+ const current = queue.shift();
290
+ if (current === undefined) break;
291
+ if (current.depth >= depth) continue;
292
+
293
+ const importers = index.byTarget.get(current.path) ?? [];
294
+ const nextDepth = current.depth + 1;
295
+ for (const importer of importers) {
296
+ if (hits.length >= resultLimit) {
297
+ resultLimitReached = true;
298
+ break;
299
+ }
300
+ if (listed.has(importer)) continue;
301
+ listed.add(importer);
302
+ hits.push({ depth: nextDepth, path: importer, externalId: undefined });
303
+ if (nextDepth < depth && !visited.has(importer)) {
304
+ visited.add(importer);
305
+ queue.push({ path: importer, depth: nextDepth });
306
+ }
307
+ }
308
+ if (resultLimitReached) break;
309
+ }
310
+
311
+ return { seedPath, depth, hits, resultLimit, resultLimitReached };
312
+ };
313
+
314
+ const resolveSeedFile = async (pathInput: string): Promise<string> => {
315
+ const absolutePath = resolveExplorePath(engine.cwd, pathInput);
316
+ let stats;
317
+ try {
318
+ stats = await lstat(absolutePath);
319
+ } catch (error) {
320
+ throw pathResolutionError(error, pathInput);
321
+ }
322
+ if (!stats.isFile()) {
323
+ throw new Error(`Not a file: ${formatPathForDisplay(absolutePath, engine.cwd)}`);
324
+ }
325
+ requireFileDepsAdapter(engine.registry.adapterForPath(absolutePath), absolutePath, engine.cwd);
326
+ return absolutePath;
327
+ };
328
+
329
+ return {
330
+ async deps(path, depth, resultLimit, signal) {
331
+ const seed = await resolveSeedFile(path);
332
+ return collectForwardHits(seed, depth, resultLimit, signal);
333
+ },
334
+
335
+ async reverseDeps(path, depth, resultLimit, signal) {
336
+ const seed = await resolveSeedFile(path);
337
+ return collectReverseHits(seed, depth, resultLimit, signal);
338
+ },
339
+
340
+ async forwardEdges(path, signal) {
341
+ const seed = await resolveSeedFile(path);
342
+ const scopeRoot = await scopeRootFor(seed, engine.cwd);
343
+ return loadForward(seed, scopeRoot, signal);
344
+ },
345
+
346
+ invalidate(paths) {
347
+ for (const path of paths) {
348
+ forwardCache.delete(resolveExplorePath(engine.cwd, path));
349
+ }
350
+ reverseCache.clear();
351
+ memo.clear();
352
+ },
353
+
354
+ clear() {
355
+ forwardCache.clear();
356
+ reverseCache.clear();
357
+ memo.clear();
358
+ },
359
+ };
360
+ }