@shanepadgett/tau-agent 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { lstat } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
-
import { formatPathForDisplay, pathResolutionError, resolveExplorePath } from "
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { formatPathForDisplay, pathResolutionError, resolveExplorePath } from "../traverse.ts";
|
|
4
4
|
import type { ExploreEngine } from "../engine.ts";
|
|
5
5
|
import type { Candidate, Resolution } from "../identity.ts";
|
|
6
6
|
import { resolveTarget } from "../identity.ts";
|
|
7
7
|
import {
|
|
8
8
|
isCallableLike,
|
|
9
9
|
isTypeLike,
|
|
10
|
+
type CallKind,
|
|
10
11
|
type CallSite,
|
|
11
12
|
type Decl,
|
|
12
13
|
type DeclKind,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
} from "../ir.ts";
|
|
16
17
|
import { walkDecls } from "../query.ts";
|
|
17
18
|
import { scanSources } from "../scan.ts";
|
|
18
|
-
import { signatureText } from "../slice.ts";
|
|
19
|
+
import { compactSignature, signatureText } from "../slice.ts";
|
|
19
20
|
import type { ExploreFileGraph, FileGraphEdge } from "./file-graph.ts";
|
|
20
21
|
|
|
21
22
|
export type RelationshipOp = "callers" | "callees" | "references" | "implementations";
|
|
@@ -38,6 +39,7 @@ export type RelationshipSite = {
|
|
|
38
39
|
line: number;
|
|
39
40
|
kind: RelationshipSiteKind;
|
|
40
41
|
name: string;
|
|
42
|
+
target: { path: string; startLine: number } | undefined;
|
|
41
43
|
preview: string;
|
|
42
44
|
certainty: RelationshipCertainty;
|
|
43
45
|
competitors: Candidate[];
|
|
@@ -102,14 +104,18 @@ function sameDecl(a: DeclRef, b: DeclRef): boolean {
|
|
|
102
104
|
/** Same callable name in same file — overloads bind as one symbol for edge matching. */
|
|
103
105
|
function sameOverloadGroup(a: DeclRef, b: DeclRef): boolean {
|
|
104
106
|
return (
|
|
105
|
-
a.path === b.path &&
|
|
107
|
+
a.path === b.path &&
|
|
108
|
+
a.decl.name === b.decl.name &&
|
|
109
|
+
ownerName(a.decl.qualifiedName) === ownerName(b.decl.qualifiedName) &&
|
|
110
|
+
isCallableLike(a.decl.kind) &&
|
|
111
|
+
isCallableLike(b.decl.kind)
|
|
106
112
|
);
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
function
|
|
115
|
+
function allSameOverloadGroup(refs: readonly DeclRef[]): boolean {
|
|
110
116
|
const first = refs[0];
|
|
111
117
|
if (first === undefined) return false;
|
|
112
|
-
return refs.every((ref) => ref
|
|
118
|
+
return refs.every((ref) => sameOverloadGroup(ref, first));
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
function toCandidate(ref: DeclRef): Candidate {
|
|
@@ -171,6 +177,33 @@ function buildBundle(path: string, ir: FileIr, source: string): FileBundle {
|
|
|
171
177
|
return { path, ir, source, localByName, importByLocal };
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Owner for top-level call sites. Kept out of the IR decl tree so it never
|
|
182
|
+
* reaches outline, discover, or declaration counts.
|
|
183
|
+
*/
|
|
184
|
+
function fileScopeRef(bundle: FileBundle): DeclRef {
|
|
185
|
+
return {
|
|
186
|
+
path: bundle.path,
|
|
187
|
+
ir: bundle.ir,
|
|
188
|
+
source: bundle.source,
|
|
189
|
+
decl: {
|
|
190
|
+
kind: "module",
|
|
191
|
+
name: "",
|
|
192
|
+
qualifiedName: "",
|
|
193
|
+
startLine: 1,
|
|
194
|
+
endLine: bundle.ir.lineCount,
|
|
195
|
+
startOffset: 0,
|
|
196
|
+
endOffset: bundle.source.length,
|
|
197
|
+
signatureEndOffset: 0,
|
|
198
|
+
visibility: "private",
|
|
199
|
+
exported: false,
|
|
200
|
+
children: [],
|
|
201
|
+
calls: bundle.ir.fileCalls,
|
|
202
|
+
bases: [],
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
174
207
|
function internalTargets(edges: readonly FileGraphEdge[]): Set<string> {
|
|
175
208
|
const out = new Set<string>();
|
|
176
209
|
for (const edge of edges) {
|
|
@@ -179,6 +212,68 @@ function internalTargets(edges: readonly FileGraphEdge[]): Set<string> {
|
|
|
179
212
|
return out;
|
|
180
213
|
}
|
|
181
214
|
|
|
215
|
+
type MethodSetEntry = { ref: DeclRef | undefined; methods: Set<string> };
|
|
216
|
+
|
|
217
|
+
/** Directory-scoped type key: a Go method set spans every file in the package. */
|
|
218
|
+
function methodSetKey(path: string, typeName: string): string {
|
|
219
|
+
return `${dirname(path)}|${typeName}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function addMethodSetRef(out: Map<string, MethodSetEntry>, ref: DeclRef): void {
|
|
223
|
+
const key = methodSetKey(ref.path, ref.decl.name);
|
|
224
|
+
const existing = out.get(key);
|
|
225
|
+
const entry = existing ?? { ref: undefined, methods: new Set<string>() };
|
|
226
|
+
if (existing === undefined) out.set(key, entry);
|
|
227
|
+
entry.ref = ref;
|
|
228
|
+
for (const child of ref.decl.children) {
|
|
229
|
+
if (isCallableLike(child.kind)) entry.methods.add(child.name);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function addMethodSetOwner(out: Map<string, MethodSetEntry>, ref: DeclRef): void {
|
|
234
|
+
// Go methods are file-level decls owned through qualifiedName.
|
|
235
|
+
const owner = ownerName(ref.decl.qualifiedName);
|
|
236
|
+
if (owner.length === 0) return;
|
|
237
|
+
const key = methodSetKey(ref.path, owner);
|
|
238
|
+
const existing = out.get(key);
|
|
239
|
+
const entry = existing ?? { ref: undefined, methods: new Set<string>() };
|
|
240
|
+
if (existing === undefined) out.set(key, entry);
|
|
241
|
+
entry.methods.add(ref.decl.name);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function buildMethodSets(bundles: readonly FileBundle[]): Map<string, MethodSetEntry> {
|
|
245
|
+
const out = new Map<string, MethodSetEntry>();
|
|
246
|
+
for (const bundle of bundles) {
|
|
247
|
+
for (const ref of collectDeclRefs(bundle.path, bundle.ir, bundle.source)) {
|
|
248
|
+
if (isTypeLike(ref.decl.kind)) addMethodSetRef(out, ref);
|
|
249
|
+
else if (isCallableLike(ref.decl.kind)) addMethodSetOwner(out, ref);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Interface method names, with embedded interfaces expanded transitively. */
|
|
256
|
+
function requiredMethods(target: DeclRef, sets: Map<string, MethodSetEntry>): Set<string> {
|
|
257
|
+
const required = new Set<string>();
|
|
258
|
+
const seen = new Set<string>();
|
|
259
|
+
const visit = (path: string, decl: Decl): void => {
|
|
260
|
+
for (const child of decl.children) {
|
|
261
|
+
if (isCallableLike(child.kind)) required.add(child.name);
|
|
262
|
+
}
|
|
263
|
+
for (const base of decl.bases) {
|
|
264
|
+
const key = methodSetKey(path, base);
|
|
265
|
+
if (seen.has(key)) continue;
|
|
266
|
+
seen.add(key);
|
|
267
|
+
const entry = sets.get(key);
|
|
268
|
+
if (entry === undefined) continue;
|
|
269
|
+
for (const name of entry.methods) required.add(name);
|
|
270
|
+
if (entry.ref !== undefined) visit(entry.ref.path, entry.ref.decl);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
visit(target.path, target.decl);
|
|
274
|
+
return required;
|
|
275
|
+
}
|
|
276
|
+
|
|
182
277
|
function pickByDeps(
|
|
183
278
|
candidates: readonly DeclRef[],
|
|
184
279
|
depPaths: ReadonlySet<string>,
|
|
@@ -190,7 +285,7 @@ function pickByDeps(
|
|
|
190
285
|
}
|
|
191
286
|
if (inClosure.length > 1) {
|
|
192
287
|
// Overloads of one name bind together; mixed names stay ambiguous.
|
|
193
|
-
if (
|
|
288
|
+
if (allSameOverloadGroup(inClosure)) return { targets: [...inClosure], certainty: "exact", competitors: [] };
|
|
194
289
|
return {
|
|
195
290
|
targets: [],
|
|
196
291
|
certainty: "ambiguous",
|
|
@@ -202,7 +297,7 @@ function pickByDeps(
|
|
|
202
297
|
if (only !== undefined) return { targets: [only], certainty: "inferred", competitors: [] };
|
|
203
298
|
}
|
|
204
299
|
if (candidates.length > 1) {
|
|
205
|
-
if (
|
|
300
|
+
if (allSameOverloadGroup(candidates)) return { targets: [...candidates], certainty: "inferred", competitors: [] };
|
|
206
301
|
return {
|
|
207
302
|
targets: [],
|
|
208
303
|
certainty: "ambiguous",
|
|
@@ -212,42 +307,74 @@ function pickByDeps(
|
|
|
212
307
|
return { targets: [], certainty: "inferred", competitors: [] };
|
|
213
308
|
}
|
|
214
309
|
|
|
310
|
+
type ResolvedCall = { targets: DeclRef[]; certainty: RelationshipCertainty; competitors: Candidate[] };
|
|
311
|
+
|
|
312
|
+
function exactTargets(refs: readonly DeclRef[]): ResolvedCall {
|
|
313
|
+
return { targets: [...refs], certainty: "exact", competitors: [] };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function ambiguousTargets(refs: readonly DeclRef[]): ResolvedCall {
|
|
317
|
+
return {
|
|
318
|
+
targets: [],
|
|
319
|
+
certainty: "ambiguous",
|
|
320
|
+
competitors: refs.slice(0, MAX_COMPETITORS).map(toCandidate),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveLocalCall(local: readonly DeclRef[]): ResolvedCall | undefined {
|
|
325
|
+
if (local.length === 1) {
|
|
326
|
+
const only = local[0];
|
|
327
|
+
if (only !== undefined) return exactTargets([only]);
|
|
328
|
+
}
|
|
329
|
+
if (local.length > 1) {
|
|
330
|
+
if (allSameOverloadGroup(local)) return exactTargets(local);
|
|
331
|
+
return ambiguousTargets(local);
|
|
332
|
+
}
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
215
336
|
function resolveCallName(
|
|
216
337
|
name: string,
|
|
217
338
|
receiver: string,
|
|
339
|
+
kind: CallKind,
|
|
340
|
+
from: DeclRef,
|
|
218
341
|
caller: FileBundle,
|
|
219
342
|
globalByName: Map<string, DeclRef[]>,
|
|
220
343
|
callerDeps: ReadonlySet<string>,
|
|
221
|
-
):
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (local !== undefined && local.length > 1) {
|
|
228
|
-
if (allSameName(local)) return { targets: [...local], certainty: "exact", competitors: [] };
|
|
229
|
-
return {
|
|
230
|
-
targets: [],
|
|
231
|
-
certainty: "ambiguous",
|
|
232
|
-
competitors: local.slice(0, MAX_COMPETITORS).map(toCandidate),
|
|
233
|
-
};
|
|
344
|
+
): ResolvedCall {
|
|
345
|
+
const callableCandidates = (globalByName.get(name) ?? []).filter((ref) =>
|
|
346
|
+
kind === "construct" ? isTypeLike(ref.decl.kind) : isCallableLike(ref.decl.kind),
|
|
347
|
+
);
|
|
348
|
+
if (caller.importByLocal.has(name)) {
|
|
349
|
+
return pickByDeps(callableCandidates, callerDeps);
|
|
234
350
|
}
|
|
235
351
|
|
|
236
|
-
|
|
237
|
-
|
|
352
|
+
const local = caller.localByName
|
|
353
|
+
.get(name)
|
|
354
|
+
?.filter((ref) => (kind === "construct" ? isTypeLike(ref.decl.kind) : isCallableLike(ref.decl.kind)));
|
|
355
|
+
if (receiver.length === 0 && local !== undefined) {
|
|
356
|
+
const resolved = resolveLocalCall(local);
|
|
357
|
+
if (resolved !== undefined) return resolved;
|
|
238
358
|
}
|
|
239
359
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
360
|
+
const fromOwner = ownerName(from.decl.qualifiedName);
|
|
361
|
+
const global =
|
|
362
|
+
receiver.length === 0
|
|
363
|
+
? callableCandidates.filter(
|
|
364
|
+
(ref) => ref.decl.kind !== "method" || ownerName(ref.decl.qualifiedName) === fromOwner,
|
|
365
|
+
)
|
|
366
|
+
: callableCandidates;
|
|
367
|
+
if (receiver.length > 0) {
|
|
368
|
+
const receiverOwner = receiver === "self" || receiver === "this" ? fromOwner : receiver.split(/[.:]/u).at(-1);
|
|
369
|
+
const owned = global.filter((ref) => ownerName(ref.decl.qualifiedName) === receiverOwner);
|
|
370
|
+
if (owned.length > 0) return pickByDeps(owned, callerDeps);
|
|
371
|
+
return { targets: [], certainty: "inferred", competitors: [] };
|
|
372
|
+
}
|
|
243
373
|
if (global.length === 1) {
|
|
244
374
|
const only = global[0];
|
|
245
|
-
if (only !== undefined) return
|
|
246
|
-
}
|
|
247
|
-
if (global.length > 1 && allSameName(global)) {
|
|
248
|
-
// Same-name overloads across files still need dep evidence for Exact.
|
|
249
|
-
return pickByDeps(global, callerDeps);
|
|
375
|
+
if (only !== undefined) return exactTargets([only]);
|
|
250
376
|
}
|
|
377
|
+
// Same-name overloads across files still need dep evidence for Exact.
|
|
251
378
|
return pickByDeps(global, callerDeps);
|
|
252
379
|
}
|
|
253
380
|
|
|
@@ -260,21 +387,22 @@ function siteKindFromCall(site: CallSite): RelationshipSiteKind {
|
|
|
260
387
|
|
|
261
388
|
function makeSite(
|
|
262
389
|
path: string,
|
|
263
|
-
source: string,
|
|
264
390
|
line: number,
|
|
265
391
|
kind: RelationshipSiteKind,
|
|
266
392
|
name: string,
|
|
267
393
|
certainty: RelationshipCertainty,
|
|
268
394
|
competitors: Candidate[],
|
|
395
|
+
preview: string,
|
|
269
396
|
): RelationshipSite {
|
|
270
397
|
return {
|
|
271
398
|
path,
|
|
272
399
|
line,
|
|
273
400
|
kind,
|
|
274
401
|
name,
|
|
275
|
-
|
|
402
|
+
target: undefined,
|
|
276
403
|
certainty,
|
|
277
404
|
competitors,
|
|
405
|
+
preview,
|
|
278
406
|
};
|
|
279
407
|
}
|
|
280
408
|
|
|
@@ -319,17 +447,29 @@ function edgeHitsTarget(edge: EdgeHit, target: DeclRef): boolean {
|
|
|
319
447
|
}
|
|
320
448
|
|
|
321
449
|
function pushEdge(sites: RelationshipSite[], edge: EdgeHit): void {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
edge.competitors,
|
|
331
|
-
),
|
|
450
|
+
const site = makeSite(
|
|
451
|
+
edge.from.path,
|
|
452
|
+
edge.site.line,
|
|
453
|
+
siteKindFromCall(edge.site),
|
|
454
|
+
edge.site.name,
|
|
455
|
+
edge.certainty,
|
|
456
|
+
edge.competitors,
|
|
457
|
+
linePreview(edge.from.source, edge.site.line),
|
|
332
458
|
);
|
|
459
|
+
const soleTarget = edge.targets.length === 1 ? edge.targets[0] : undefined;
|
|
460
|
+
const receiverOwner = edge.site.receiver.split(/[.:]/u).at(-1);
|
|
461
|
+
// Dependency-free same-module methods can still be pinned when syntax names
|
|
462
|
+
// their owner. Other inferred single-name matches remain visible guesses.
|
|
463
|
+
const target =
|
|
464
|
+
soleTarget !== undefined &&
|
|
465
|
+
(edge.certainty === "exact" ||
|
|
466
|
+
edge.site.receiver === "self" ||
|
|
467
|
+
edge.site.receiver === "this" ||
|
|
468
|
+
ownerName(soleTarget.decl.qualifiedName) === receiverOwner)
|
|
469
|
+
? soleTarget
|
|
470
|
+
: undefined;
|
|
471
|
+
if (target !== undefined) site.target = { path: target.path, startLine: target.decl.startLine };
|
|
472
|
+
sites.push(site);
|
|
333
473
|
}
|
|
334
474
|
|
|
335
475
|
export type RelationshipQueryArgs = {
|
|
@@ -422,9 +562,10 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
422
562
|
for (const bundle of bundles) {
|
|
423
563
|
signal.throwIfAborted();
|
|
424
564
|
const deps = await depsOf(bundle.path);
|
|
425
|
-
|
|
565
|
+
const owners = [...collectDeclRefs(bundle.path, bundle.ir, bundle.source), fileScopeRef(bundle)];
|
|
566
|
+
for (const from of owners) {
|
|
426
567
|
for (const site of from.decl.calls) {
|
|
427
|
-
const resolved = resolveCallName(site.name, site.receiver, bundle, globalByName, deps);
|
|
568
|
+
const resolved = resolveCallName(site.name, site.receiver, site.kind, from, bundle, globalByName, deps);
|
|
428
569
|
edges.push({
|
|
429
570
|
from,
|
|
430
571
|
site,
|
|
@@ -438,32 +579,51 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
438
579
|
|
|
439
580
|
const sites: RelationshipSite[] = [];
|
|
440
581
|
|
|
582
|
+
const heritageCertainty = (
|
|
583
|
+
ref: DeclRef,
|
|
584
|
+
deps: ReadonlySet<string>,
|
|
585
|
+
candidates: readonly DeclRef[],
|
|
586
|
+
): RelationshipCertainty => {
|
|
587
|
+
// Same file and same directory are one package: no import edge exists to
|
|
588
|
+
// prove, so label them alike rather than implying a confidence gap.
|
|
589
|
+
if (ref.path === target.path || dirname(ref.path) === dirname(target.path) || deps.has(target.path)) {
|
|
590
|
+
return "exact";
|
|
591
|
+
}
|
|
592
|
+
return candidates.length === 1 ? "inferred" : "ambiguous";
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const isHeritageCandidate = (ref: DeclRef, baseName: string): boolean => {
|
|
596
|
+
if (!ref.decl.bases.includes(baseName) || !isTypeLike(ref.decl.kind)) return false;
|
|
597
|
+
// Never list the base type as its own implementor.
|
|
598
|
+
return !(sameDecl(ref, target) || (ref.decl.name === baseName && ref.path === target.path));
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const pushHeritageSite = async (
|
|
602
|
+
ref: DeclRef,
|
|
603
|
+
baseName: string,
|
|
604
|
+
siteKind: RelationshipSiteKind,
|
|
605
|
+
candidates: readonly DeclRef[],
|
|
606
|
+
): Promise<void> => {
|
|
607
|
+
const certainty = heritageCertainty(ref, await depsOf(ref.path), candidates);
|
|
608
|
+
sites.push(
|
|
609
|
+
makeSite(
|
|
610
|
+
ref.path,
|
|
611
|
+
ref.decl.startLine,
|
|
612
|
+
siteKind,
|
|
613
|
+
baseName,
|
|
614
|
+
certainty,
|
|
615
|
+
certainty === "ambiguous" ? candidates.slice(0, MAX_COMPETITORS).map(toCandidate) : [],
|
|
616
|
+
// Declaration-shaped site: its own signature, not the annotation line above it.
|
|
617
|
+
compactSignature(signatureText(ref.decl, ref.source)),
|
|
618
|
+
),
|
|
619
|
+
);
|
|
620
|
+
};
|
|
621
|
+
|
|
441
622
|
const heritageSites = async (baseName: string, siteKind: RelationshipSiteKind): Promise<void> => {
|
|
623
|
+
const candidates = globalByName.get(baseName) ?? [];
|
|
442
624
|
for (const bundle of bundles) {
|
|
443
625
|
for (const ref of collectDeclRefs(bundle.path, bundle.ir, bundle.source)) {
|
|
444
|
-
if (
|
|
445
|
-
if (!isTypeLike(ref.decl.kind)) continue;
|
|
446
|
-
// Never list the base type as its own implementor.
|
|
447
|
-
if (sameDecl(ref, target) || (ref.decl.name === baseName && ref.path === target.path)) continue;
|
|
448
|
-
const deps = await depsOf(ref.path);
|
|
449
|
-
const candidates = globalByName.get(baseName) ?? [];
|
|
450
|
-
const certainty =
|
|
451
|
-
ref.path === target.path || deps.has(target.path)
|
|
452
|
-
? "exact"
|
|
453
|
-
: candidates.length === 1
|
|
454
|
-
? "inferred"
|
|
455
|
-
: "ambiguous";
|
|
456
|
-
sites.push(
|
|
457
|
-
makeSite(
|
|
458
|
-
ref.path,
|
|
459
|
-
ref.source,
|
|
460
|
-
ref.decl.startLine,
|
|
461
|
-
siteKind,
|
|
462
|
-
baseName,
|
|
463
|
-
certainty,
|
|
464
|
-
certainty === "ambiguous" ? candidates.slice(0, MAX_COMPETITORS).map(toCandidate) : [],
|
|
465
|
-
),
|
|
466
|
-
);
|
|
626
|
+
if (isHeritageCandidate(ref, baseName)) await pushHeritageSite(ref, baseName, siteKind, candidates);
|
|
467
627
|
}
|
|
468
628
|
}
|
|
469
629
|
};
|
|
@@ -488,7 +648,6 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
488
648
|
sites.push(
|
|
489
649
|
makeSite(
|
|
490
650
|
edge.from.path,
|
|
491
|
-
edge.from.source,
|
|
492
651
|
edge.site.line,
|
|
493
652
|
"construct",
|
|
494
653
|
edge.site.name,
|
|
@@ -496,6 +655,7 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
496
655
|
n > 1
|
|
497
656
|
? (globalByName.get(target.decl.name) ?? []).slice(0, MAX_COMPETITORS).map(toCandidate)
|
|
498
657
|
: [],
|
|
658
|
+
linePreview(edge.from.source, edge.site.line),
|
|
499
659
|
),
|
|
500
660
|
);
|
|
501
661
|
} else {
|
|
@@ -518,7 +678,6 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
518
678
|
sites.push(
|
|
519
679
|
makeSite(
|
|
520
680
|
target.path,
|
|
521
|
-
target.source,
|
|
522
681
|
target.decl.startLine,
|
|
523
682
|
"implementation",
|
|
524
683
|
base,
|
|
@@ -528,6 +687,7 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
528
687
|
? "exact"
|
|
529
688
|
: picked.certainty,
|
|
530
689
|
picked.competitors,
|
|
690
|
+
compactSignature(signatureText(target.decl, target.source)),
|
|
531
691
|
),
|
|
532
692
|
);
|
|
533
693
|
}
|
|
@@ -543,7 +703,17 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
543
703
|
if (binding.local !== target.decl.name && remote !== target.decl.name) continue;
|
|
544
704
|
const deps = await depsOf(bundle.path);
|
|
545
705
|
const certainty = deps.has(target.path) || bundle.path === target.path ? "exact" : "inferred";
|
|
546
|
-
sites.push(
|
|
706
|
+
sites.push(
|
|
707
|
+
makeSite(
|
|
708
|
+
bundle.path,
|
|
709
|
+
imp.startLine,
|
|
710
|
+
"import",
|
|
711
|
+
binding.local,
|
|
712
|
+
certainty,
|
|
713
|
+
[],
|
|
714
|
+
linePreview(bundle.source, imp.startLine),
|
|
715
|
+
),
|
|
716
|
+
);
|
|
547
717
|
}
|
|
548
718
|
}
|
|
549
719
|
}
|
|
@@ -552,6 +722,41 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
552
722
|
// implementations
|
|
553
723
|
if (isTypeLike(target.decl.kind)) {
|
|
554
724
|
await heritageSites(target.decl.name, "implementation");
|
|
725
|
+
// Structurally typed interfaces (Go): a type implements by method set, with
|
|
726
|
+
// nothing naming the interface. Syntax cannot check signatures — inferred.
|
|
727
|
+
if (target.decl.kind === "interface" && targetAdapter.capabilities.structuralImplements) {
|
|
728
|
+
const sets = buildMethodSets(bundles);
|
|
729
|
+
const required = requiredMethods(target, sets);
|
|
730
|
+
const reported = new Set(sites.map((site) => `${site.path}|${site.line}`));
|
|
731
|
+
if (required.size > 0) {
|
|
732
|
+
for (const entry of sets.values()) {
|
|
733
|
+
const ref = entry.ref;
|
|
734
|
+
if (ref === undefined || sameDecl(ref, target)) continue;
|
|
735
|
+
if (!isTypeLike(ref.decl.kind)) continue;
|
|
736
|
+
if (reported.has(`${ref.path}|${ref.decl.startLine}`)) continue;
|
|
737
|
+
if (entry.methods.size < required.size) continue;
|
|
738
|
+
let covers = true;
|
|
739
|
+
for (const name of required) {
|
|
740
|
+
if (!entry.methods.has(name)) {
|
|
741
|
+
covers = false;
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (!covers) continue;
|
|
746
|
+
sites.push(
|
|
747
|
+
makeSite(
|
|
748
|
+
ref.path,
|
|
749
|
+
ref.decl.startLine,
|
|
750
|
+
"implementation",
|
|
751
|
+
target.decl.name,
|
|
752
|
+
"inferred",
|
|
753
|
+
[],
|
|
754
|
+
compactSignature(signatureText(ref.decl, ref.source)),
|
|
755
|
+
),
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
555
760
|
}
|
|
556
761
|
if (isCallableLike(target.decl.kind)) {
|
|
557
762
|
const owner = ownerName(target.decl.qualifiedName);
|
|
@@ -568,7 +773,15 @@ export async function queryRelationships(args: RelationshipQueryArgs): Promise<R
|
|
|
568
773
|
continue;
|
|
569
774
|
}
|
|
570
775
|
sites.push(
|
|
571
|
-
makeSite(
|
|
776
|
+
makeSite(
|
|
777
|
+
typeRef.path,
|
|
778
|
+
child.startLine,
|
|
779
|
+
"override",
|
|
780
|
+
child.name,
|
|
781
|
+
"inferred",
|
|
782
|
+
[],
|
|
783
|
+
compactSignature(signatureText(child, typeRef.source)),
|
|
784
|
+
),
|
|
572
785
|
);
|
|
573
786
|
}
|
|
574
787
|
}
|
|
@@ -131,6 +131,12 @@ export type FileIr = {
|
|
|
131
131
|
lineCount: number;
|
|
132
132
|
decls: Decl[];
|
|
133
133
|
imports: ImportRef[];
|
|
134
|
+
/**
|
|
135
|
+
* Call sites in top-level statements, owned by no declaration.
|
|
136
|
+
* Relationship queries read these; they are not declarations and never
|
|
137
|
+
* appear in outline, discover, or declaration counts.
|
|
138
|
+
*/
|
|
139
|
+
fileCalls: CallSite[];
|
|
134
140
|
/** True when the parse tree contained ERROR/MISSING nodes. */
|
|
135
141
|
parseDegraded: boolean;
|
|
136
142
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import type { FileDepResolver } from "../adapter.ts";
|
|
3
|
+
import { buildDottedIndex, resolveDottedFile, resolveDottedPackage, type HeadScan } from "./dotted-index.ts";
|
|
4
|
+
import { externalId, internalPaths, isWithin, packageDirectory } from "./file-dep-util.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* C# file deps. A `using` names a namespace, so it resolves to that namespace's
|
|
8
|
+
* directory as a package edge; alias usings (`using A = X.Y.Type;`) name one
|
|
9
|
+
* type and resolve to its file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** BCL / engine — never treat polyfills as project file edges. */
|
|
13
|
+
function isFrameworkNamespace(ns: string): boolean {
|
|
14
|
+
if (ns === "System" || ns.startsWith("System.")) return true;
|
|
15
|
+
if (ns === "Microsoft" || ns.startsWith("Microsoft.")) return true;
|
|
16
|
+
if (ns === "Windows" || ns.startsWith("Windows.")) return true;
|
|
17
|
+
if (ns === "UnityEngine" || ns.startsWith("UnityEngine.")) return true;
|
|
18
|
+
if (ns === "UnityEditor" || ns.startsWith("UnityEditor.")) return true;
|
|
19
|
+
if (ns === "Godot" || ns.startsWith("Godot.")) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeUsing(specifier: string): string {
|
|
24
|
+
let s = specifier.trim().replace(/;$/u, "").trim();
|
|
25
|
+
s = s.replace(/^global\s+/iu, "").trim();
|
|
26
|
+
s = s.replace(/^static\s+/iu, "").trim();
|
|
27
|
+
// Alias: using A = X.Y.Z → resolve the target.
|
|
28
|
+
const eq = s.indexOf("=");
|
|
29
|
+
if (eq !== -1) s = s.slice(eq + 1).trim();
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const TYPE_KEYWORDS = ["class ", "struct ", "interface ", "record ", "enum ", "delegate "] as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Namespace + type pairs, scanned sequentially — types bind to the nearest
|
|
37
|
+
* preceding namespace declaration.
|
|
38
|
+
*/
|
|
39
|
+
function scanCsharpHead(head: string): HeadScan {
|
|
40
|
+
const names: HeadScan["names"] = [];
|
|
41
|
+
const packages: string[] = [];
|
|
42
|
+
let currentNs: string | undefined;
|
|
43
|
+
for (const raw of head.split("\n")) {
|
|
44
|
+
const line = raw.trim();
|
|
45
|
+
if (line.length === 0 || line.startsWith("//")) continue;
|
|
46
|
+
|
|
47
|
+
// file-scoped `namespace X;`, block `namespace X {`, or `namespace X` + brace next line
|
|
48
|
+
const nsMatch =
|
|
49
|
+
/^(?:internal\s+)?namespace\s+([A-Za-z_][\w.]*)\s*[;{]?\s*$/u.exec(line) ??
|
|
50
|
+
/^(?:internal\s+)?namespace\s+([A-Za-z_][\w.]*)\s*[{/]/u.exec(line);
|
|
51
|
+
if (nsMatch?.[1] !== undefined) {
|
|
52
|
+
currentNs = nsMatch[1];
|
|
53
|
+
packages.push(currentNs);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (currentNs === undefined) continue;
|
|
57
|
+
|
|
58
|
+
for (const kw of TYPE_KEYWORDS) {
|
|
59
|
+
const idx = line.indexOf(kw);
|
|
60
|
+
if (idx === -1) continue;
|
|
61
|
+
if (idx > 0 && /[A-Za-z0-9_]/u.test(line.charAt(idx - 1))) continue;
|
|
62
|
+
const rest = line.slice(idx + kw.length).trimStart();
|
|
63
|
+
const name = /^([A-Za-z_][\w]*)/u.exec(rest)?.[1];
|
|
64
|
+
if (name !== undefined) names.push({ pkg: currentNs, name });
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { packages, names };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const resolveCsharpFileDep: FileDepResolver = async (fromPath, specifier, host, signal) => {
|
|
72
|
+
signal.throwIfAborted();
|
|
73
|
+
const dotted = normalizeUsing(specifier);
|
|
74
|
+
if (dotted.length === 0) return { kind: "unresolved" };
|
|
75
|
+
if (isFrameworkNamespace(dotted)) return externalId(dotted);
|
|
76
|
+
|
|
77
|
+
const index = await buildDottedIndex(host, "csharp-dotted", [".cs"], scanCsharpHead, signal);
|
|
78
|
+
|
|
79
|
+
// Namespace first: a plain `using` imports the whole namespace, not one file.
|
|
80
|
+
const pkg = resolveDottedPackage(index, fromPath, dotted);
|
|
81
|
+
if (pkg !== undefined && isWithin(host.scopeRoot, pkg.dir)) {
|
|
82
|
+
return packageDirectory(dotted, pkg.dir, pkg.fileCount);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const file = resolveDottedFile(index, fromPath, dotted);
|
|
86
|
+
if (file === undefined) return externalId(dotted);
|
|
87
|
+
if (!isWithin(host.scopeRoot, file) || !host.ownsPath(file)) return externalId(dotted);
|
|
88
|
+
if (resolve(file) === resolve(fromPath)) return { kind: "unresolved" };
|
|
89
|
+
return internalPaths([file]);
|
|
90
|
+
};
|