@shanepadgett/tau-agent 0.33.1 → 0.35.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/bash-approval/README.md +20 -0
- package/extensions/bash-approval/index.ts +235 -0
- package/extensions/bash-approval/settings.ts +24 -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 +66 -0
- package/extensions/checkpoint/checkpoint.ts +279 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +170 -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/commit/commit-plan.ts +2 -2
- 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/index.ts +2 -0
- package/extensions/reference/panel.ts +94 -31
- package/extensions/review/README.md +14 -6
- package/extensions/review/index.ts +58 -99
- package/extensions/review/model.ts +13 -33
- package/extensions/review/session.ts +9 -23
- 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/soul/README.md +2 -2
- package/extensions/soul/index.ts +4 -4
- package/extensions/soul/prompt.ts +6 -10
- package/extensions/soul/settings.ts +6 -3
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents/web-research.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 +308 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +20 -8
- 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 +38 -35
- 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 +72 -30
- package/shared/model-effort.ts +23 -22
- 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/review/panel.ts +0 -128
- 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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
2
|
+
import type { Candidate } from "../identity.ts";
|
|
3
|
+
import type { ExploreFileGraph, FileDepHit, FileDepTarget, FileGraphEdge } from "../graph/file-graph.ts";
|
|
4
|
+
import {
|
|
5
|
+
queryRelationships,
|
|
6
|
+
type RelationshipCertainty,
|
|
7
|
+
type RelationshipSite,
|
|
8
|
+
type RelationshipSiteKind,
|
|
9
|
+
} from "../graph/relationships.ts";
|
|
10
|
+
import { resolveCompositeTarget, type CompositeTarget } from "./composite-target.ts";
|
|
11
|
+
|
|
12
|
+
const IMPACT_SECTION_LIMIT = 50;
|
|
13
|
+
/** File edges are lower value per row than symbol sites; a module import can carry hundreds. */
|
|
14
|
+
const IMPACT_FILE_LIMIT = 20;
|
|
15
|
+
export const IMPACT_DEPTH_DEFAULT = 2;
|
|
16
|
+
export const IMPACT_DEPTH_MAX = 5;
|
|
17
|
+
|
|
18
|
+
export type ImpactMode = "all" | "deps" | "dependents";
|
|
19
|
+
|
|
20
|
+
export type ImpactSectionId = "callees" | "fileImports" | "callers" | "fileImporters" | "transitiveDependents";
|
|
21
|
+
|
|
22
|
+
export type ImpactSiteRow = {
|
|
23
|
+
kind: "site";
|
|
24
|
+
path: string;
|
|
25
|
+
line: number;
|
|
26
|
+
siteKind: RelationshipSiteKind;
|
|
27
|
+
name: string;
|
|
28
|
+
preview: string;
|
|
29
|
+
certainty: RelationshipCertainty;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ImpactFileRow = {
|
|
33
|
+
kind: "file";
|
|
34
|
+
depth: number;
|
|
35
|
+
target: FileDepTarget;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ImpactRow = ImpactSiteRow | ImpactFileRow;
|
|
39
|
+
|
|
40
|
+
export type ImpactSection = {
|
|
41
|
+
id: ImpactSectionId;
|
|
42
|
+
label: string;
|
|
43
|
+
rows: ImpactRow[];
|
|
44
|
+
limit: number;
|
|
45
|
+
omitted: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ImpactTarget = CompositeTarget;
|
|
49
|
+
|
|
50
|
+
export type ImpactResult =
|
|
51
|
+
| {
|
|
52
|
+
kind: "resolved";
|
|
53
|
+
target: ImpactTarget;
|
|
54
|
+
sections: ImpactSection[];
|
|
55
|
+
parseDegraded: boolean;
|
|
56
|
+
notes: string[];
|
|
57
|
+
}
|
|
58
|
+
| { kind: "candidates"; candidates: Candidate[] }
|
|
59
|
+
| { kind: "notFound" }
|
|
60
|
+
| { kind: "error"; message: string };
|
|
61
|
+
|
|
62
|
+
export type ImpactQueryArgs = {
|
|
63
|
+
engine: ExploreEngine;
|
|
64
|
+
graph: ExploreFileGraph;
|
|
65
|
+
scopePath: string;
|
|
66
|
+
targetPath: string | undefined;
|
|
67
|
+
name: string;
|
|
68
|
+
line: number | undefined;
|
|
69
|
+
depth: number;
|
|
70
|
+
mode: ImpactMode;
|
|
71
|
+
signal: AbortSignal;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function siteRows(sites: readonly RelationshipSite[]): ImpactSiteRow[] {
|
|
75
|
+
return sites.slice(0, IMPACT_SECTION_LIMIT).map((site) => ({
|
|
76
|
+
kind: "site" as const,
|
|
77
|
+
path: site.path,
|
|
78
|
+
line: site.line,
|
|
79
|
+
siteKind: site.kind,
|
|
80
|
+
name: site.name,
|
|
81
|
+
preview: site.preview,
|
|
82
|
+
certainty: site.certainty,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fileRowsFromEdges(edges: readonly FileGraphEdge[]): ImpactFileRow[] {
|
|
87
|
+
const rows: ImpactFileRow[] = [];
|
|
88
|
+
for (const edge of edges) {
|
|
89
|
+
if (rows.length >= IMPACT_FILE_LIMIT) break;
|
|
90
|
+
if (edge.kind === "internal") {
|
|
91
|
+
rows.push({ kind: "file", depth: 1, target: { kind: "internal", path: edge.to } });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (edge.kind === "package") {
|
|
95
|
+
rows.push({
|
|
96
|
+
kind: "file",
|
|
97
|
+
depth: 1,
|
|
98
|
+
target: { kind: "package", id: edge.id, dir: edge.dir, fileCount: edge.fileCount },
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
rows.push({ kind: "file", depth: 1, target: { kind: "external", id: edge.id } });
|
|
103
|
+
}
|
|
104
|
+
return rows;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fileRowsFromHits(hits: readonly FileDepHit[]): ImpactFileRow[] {
|
|
108
|
+
return hits.slice(0, IMPACT_FILE_LIMIT).map((hit): ImpactFileRow => {
|
|
109
|
+
const { depth, ...target } = hit;
|
|
110
|
+
return { kind: "file", depth, target };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function noteFromError(error: unknown): string {
|
|
115
|
+
return error instanceof Error ? error.message : String(error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function maybeFileSection(
|
|
119
|
+
id: ImpactSectionId,
|
|
120
|
+
label: string,
|
|
121
|
+
rows: ImpactFileRow[],
|
|
122
|
+
totalCount: number,
|
|
123
|
+
limitReached = false,
|
|
124
|
+
): ImpactSection | undefined {
|
|
125
|
+
if (rows.length === 0) return undefined;
|
|
126
|
+
return {
|
|
127
|
+
id,
|
|
128
|
+
label,
|
|
129
|
+
rows,
|
|
130
|
+
limit: IMPACT_FILE_LIMIT,
|
|
131
|
+
omitted: totalCount > IMPACT_FILE_LIMIT || limitReached,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type SectionBatch = {
|
|
136
|
+
sections: ImpactSection[];
|
|
137
|
+
parseDegraded: boolean;
|
|
138
|
+
notes: string[];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function emptyBatch(): SectionBatch {
|
|
142
|
+
return { sections: [], parseDegraded: false, notes: [] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function absorbRelationship(
|
|
146
|
+
batch: SectionBatch,
|
|
147
|
+
part: { section: ImpactSection | undefined; parseDegraded: boolean; note: string | undefined },
|
|
148
|
+
): void {
|
|
149
|
+
if (part.section !== undefined) batch.sections.push(part.section);
|
|
150
|
+
if (part.parseDegraded) batch.parseDegraded = true;
|
|
151
|
+
if (part.note !== undefined) batch.notes.push(part.note);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function relationshipSection(
|
|
155
|
+
args: ImpactQueryArgs,
|
|
156
|
+
op: "callees" | "callers",
|
|
157
|
+
id: ImpactSectionId,
|
|
158
|
+
label: string,
|
|
159
|
+
target: ImpactTarget,
|
|
160
|
+
): Promise<{ section: ImpactSection | undefined; parseDegraded: boolean; note: string | undefined }> {
|
|
161
|
+
const result = await queryRelationships({
|
|
162
|
+
engine: args.engine,
|
|
163
|
+
graph: args.graph,
|
|
164
|
+
scopePath: args.scopePath,
|
|
165
|
+
op,
|
|
166
|
+
targetPath: target.path,
|
|
167
|
+
name: target.name,
|
|
168
|
+
line: target.startLine,
|
|
169
|
+
resultLimit: IMPACT_SECTION_LIMIT,
|
|
170
|
+
signal: args.signal,
|
|
171
|
+
});
|
|
172
|
+
if (result.kind === "resolved") {
|
|
173
|
+
const rows = siteRows(result.sites);
|
|
174
|
+
if (rows.length === 0) return { section: undefined, parseDegraded: result.parseDegraded, note: undefined };
|
|
175
|
+
return {
|
|
176
|
+
section: {
|
|
177
|
+
id,
|
|
178
|
+
label,
|
|
179
|
+
rows,
|
|
180
|
+
limit: IMPACT_SECTION_LIMIT,
|
|
181
|
+
omitted: result.resultLimitReached || result.sites.length > IMPACT_SECTION_LIMIT,
|
|
182
|
+
},
|
|
183
|
+
parseDegraded: result.parseDegraded,
|
|
184
|
+
note: undefined,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (result.kind === "error") {
|
|
188
|
+
return { section: undefined, parseDegraded: false, note: result.message };
|
|
189
|
+
}
|
|
190
|
+
return { section: undefined, parseDegraded: false, note: undefined };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function collectDepsSections(args: ImpactQueryArgs, target: ImpactTarget): Promise<SectionBatch> {
|
|
194
|
+
const batch = emptyBatch();
|
|
195
|
+
absorbRelationship(batch, await relationshipSection(args, "callees", "callees", "callees", target));
|
|
196
|
+
try {
|
|
197
|
+
const edges = await args.graph.forwardEdges(target.path, args.signal);
|
|
198
|
+
const section = maybeFileSection("fileImports", "file imports", fileRowsFromEdges(edges), edges.length);
|
|
199
|
+
if (section !== undefined) batch.sections.push(section);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
batch.notes.push(noteFromError(error));
|
|
202
|
+
}
|
|
203
|
+
return batch;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function collectDependentsSections(
|
|
207
|
+
args: ImpactQueryArgs,
|
|
208
|
+
target: ImpactTarget,
|
|
209
|
+
depth: number,
|
|
210
|
+
): Promise<SectionBatch> {
|
|
211
|
+
const batch = emptyBatch();
|
|
212
|
+
absorbRelationship(batch, await relationshipSection(args, "callers", "callers", "callers", target));
|
|
213
|
+
try {
|
|
214
|
+
const reverseLimit = IMPACT_SECTION_LIMIT * Math.max(depth, 1);
|
|
215
|
+
const reverse = await args.graph.reverseDeps(target.path, depth, reverseLimit, args.signal);
|
|
216
|
+
const importerHits = reverse.hits.filter((hit) => hit.depth === 1);
|
|
217
|
+
const importers = maybeFileSection(
|
|
218
|
+
"fileImporters",
|
|
219
|
+
"file importers",
|
|
220
|
+
fileRowsFromHits(importerHits),
|
|
221
|
+
importerHits.length,
|
|
222
|
+
);
|
|
223
|
+
if (importers !== undefined) batch.sections.push(importers);
|
|
224
|
+
|
|
225
|
+
if (depth >= 2) {
|
|
226
|
+
const transitiveHits = reverse.hits.filter((hit) => hit.depth >= 2 && hit.depth <= depth);
|
|
227
|
+
const transitive = maybeFileSection(
|
|
228
|
+
"transitiveDependents",
|
|
229
|
+
"transitive dependents",
|
|
230
|
+
fileRowsFromHits(transitiveHits),
|
|
231
|
+
transitiveHits.length,
|
|
232
|
+
reverse.resultLimitReached,
|
|
233
|
+
);
|
|
234
|
+
if (transitive !== undefined) batch.sections.push(transitive);
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
batch.notes.push(noteFromError(error));
|
|
238
|
+
}
|
|
239
|
+
return batch;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function mergeBatch(into: SectionBatch, part: SectionBatch): void {
|
|
243
|
+
into.sections.push(...part.sections);
|
|
244
|
+
if (part.parseDegraded) into.parseDegraded = true;
|
|
245
|
+
into.notes.push(...part.notes);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Blast radius composite: symbol callees/callers + file imports/importers + transitive file dependents.
|
|
250
|
+
* Symbol hops are depth 1 only; `depth` applies to reverse file BFS only.
|
|
251
|
+
*/
|
|
252
|
+
export async function queryImpact(args: ImpactQueryArgs): Promise<ImpactResult> {
|
|
253
|
+
if (args.depth < 1) {
|
|
254
|
+
return { kind: "error", message: "depth must be >= 1" };
|
|
255
|
+
}
|
|
256
|
+
const depth = Math.min(args.depth, IMPACT_DEPTH_MAX);
|
|
257
|
+
|
|
258
|
+
const resolved = await resolveCompositeTarget(
|
|
259
|
+
args.engine,
|
|
260
|
+
args.scopePath,
|
|
261
|
+
args.targetPath,
|
|
262
|
+
args.name,
|
|
263
|
+
args.line,
|
|
264
|
+
"impact",
|
|
265
|
+
args.signal,
|
|
266
|
+
);
|
|
267
|
+
if (resolved.kind !== "resolved") return resolved;
|
|
268
|
+
|
|
269
|
+
const target = resolved.value.target;
|
|
270
|
+
const mode = args.mode;
|
|
271
|
+
const batch: SectionBatch = {
|
|
272
|
+
sections: [],
|
|
273
|
+
parseDegraded: resolved.value.parseDegraded,
|
|
274
|
+
notes: [],
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (mode === "all" || mode === "deps") {
|
|
278
|
+
mergeBatch(batch, await collectDepsSections(args, target));
|
|
279
|
+
}
|
|
280
|
+
if (mode === "all" || mode === "dependents") {
|
|
281
|
+
mergeBatch(batch, await collectDependentsSections(args, target, depth));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
kind: "resolved",
|
|
286
|
+
target,
|
|
287
|
+
sections: batch.sections,
|
|
288
|
+
parseDegraded: batch.parseDegraded,
|
|
289
|
+
notes: [...new Set(batch.notes)],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { Decl, DeclKind, FileIr } from "../ir.ts";
|
|
|
4
4
|
import { filterDeclForest, walkDecls } from "../query.ts";
|
|
5
5
|
import { scanSources, type ScanOutcome } from "../scan.ts";
|
|
6
6
|
import { docsText, signatureText } from "../slice.ts";
|
|
7
|
-
import { formatPathForDisplay, pathResolutionError, resolveExplorePath, walkPaths } from "
|
|
7
|
+
import { formatPathForDisplay, pathResolutionError, resolveExplorePath, walkPaths } from "../traverse.ts";
|
|
8
8
|
|
|
9
9
|
export type OutlineOptions = {
|
|
10
10
|
includePrivate: boolean;
|
|
@@ -38,7 +38,9 @@ export type OutlineResult = { mode: "file"; file: OutlineFileView } | { mode: "d
|
|
|
38
38
|
|
|
39
39
|
function keepVisibility(includePrivate: boolean): (decl: Decl) => boolean {
|
|
40
40
|
if (includePrivate) return () => true;
|
|
41
|
-
|
|
41
|
+
// Only file/type-private declarations are hidden. `internal` (pub(crate), Go
|
|
42
|
+
// package scope, C#/Kotlin internal) is module-visible surface a caller needs.
|
|
43
|
+
return (decl) => decl.visibility !== "private";
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/** Keep decls that match a name, or ancestors of a match (structure preserved). */
|
|
@@ -3,7 +3,7 @@ import type { Candidate, Target } from "../identity.ts";
|
|
|
3
3
|
import { resolveTarget } from "../identity.ts";
|
|
4
4
|
import type { Decl, FileIr, ImportRef } from "../ir.ts";
|
|
5
5
|
import { declarationText, signatureText, signatureWithDocsText } from "../slice.ts";
|
|
6
|
-
import { formatPathForDisplay, stripLeadingAt } from "
|
|
6
|
+
import { formatPathForDisplay, stripLeadingAt } from "../traverse.ts";
|
|
7
7
|
|
|
8
8
|
export type ShowView = "signature" | "signatureWithDocs" | "declaration" | "declarationWithImports";
|
|
9
9
|
|
|
@@ -199,6 +199,48 @@ function formatCandidateError(label: string, candidates: readonly Candidate[], c
|
|
|
199
199
|
return lines.join("\n");
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function targetLabel(target: ShowTargetInput): string {
|
|
203
|
+
return target.line === undefined
|
|
204
|
+
? `${target.name} @ ${target.path}`
|
|
205
|
+
: `${target.name} @ ${target.path}:${target.line}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function resolveShowBlock(
|
|
209
|
+
engine: ExploreEngine,
|
|
210
|
+
target: ShowTargetInput,
|
|
211
|
+
view: ShowView,
|
|
212
|
+
context: number,
|
|
213
|
+
signal: AbortSignal,
|
|
214
|
+
): Promise<ShowBlock> {
|
|
215
|
+
const resolutionTarget: Target = {
|
|
216
|
+
path: target.path,
|
|
217
|
+
name: target.name,
|
|
218
|
+
...(target.line === undefined ? {} : { line: target.line }),
|
|
219
|
+
};
|
|
220
|
+
const resolution = await resolveTarget(engine, engine.cwd, resolutionTarget, signal);
|
|
221
|
+
const label = targetLabel(target);
|
|
222
|
+
if (resolution.kind === "notFound") {
|
|
223
|
+
throw new Error(`No declaration matched ${label}`);
|
|
224
|
+
}
|
|
225
|
+
if (resolution.kind === "candidates") {
|
|
226
|
+
throw new Error(formatCandidateError(label, resolution.candidates, engine.cwd));
|
|
227
|
+
}
|
|
228
|
+
const adapter = engine.registry.adapterForPath(resolution.path);
|
|
229
|
+
const importNoise = adapter?.importNoiseIdentifiers ?? NO_IMPORT_NOISE;
|
|
230
|
+
return buildBlock(resolution.decl, resolution.path, resolution.ir, resolution.source, view, context, importNoise);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function dedupeDocWarnings(blocks: ShowBlock[]): void {
|
|
234
|
+
let docsWarningEmitted = false;
|
|
235
|
+
for (const block of blocks) {
|
|
236
|
+
if (docsWarningEmitted) {
|
|
237
|
+
block.warnings = block.warnings.filter((warning) => warning !== "no attached documentation");
|
|
238
|
+
} else if (block.warnings.includes("no attached documentation")) {
|
|
239
|
+
docsWarningEmitted = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
202
244
|
/**
|
|
203
245
|
* Resolve every target before emitting. Any missing/ambiguous target fails the whole batch.
|
|
204
246
|
* `contextLines` must be omitted (undefined) unless view is declaration.
|
|
@@ -214,49 +256,11 @@ export async function showTargets(
|
|
|
214
256
|
throw new Error("contextLines is supported only with view=declaration");
|
|
215
257
|
}
|
|
216
258
|
const context = contextLines ?? 0;
|
|
217
|
-
|
|
218
|
-
const unique = dedupeShowTargets(targets);
|
|
219
259
|
const blocks: ShowBlock[] = [];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
for (const target of unique) {
|
|
260
|
+
for (const target of dedupeShowTargets(targets)) {
|
|
223
261
|
signal.throwIfAborted();
|
|
224
|
-
|
|
225
|
-
path: target.path,
|
|
226
|
-
name: target.name,
|
|
227
|
-
...(target.line === undefined ? {} : { line: target.line }),
|
|
228
|
-
};
|
|
229
|
-
const resolution = await resolveTarget(engine, engine.cwd, resolutionTarget, signal);
|
|
230
|
-
const label =
|
|
231
|
-
target.line === undefined
|
|
232
|
-
? `${target.name} @ ${target.path}`
|
|
233
|
-
: `${target.name} @ ${target.path}:${target.line}`;
|
|
234
|
-
|
|
235
|
-
if (resolution.kind === "notFound") {
|
|
236
|
-
throw new Error(`No declaration matched ${label}`);
|
|
237
|
-
}
|
|
238
|
-
if (resolution.kind === "candidates") {
|
|
239
|
-
throw new Error(formatCandidateError(label, resolution.candidates, engine.cwd));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const adapter = engine.registry.adapterForPath(resolution.path);
|
|
243
|
-
const importNoise = adapter?.importNoiseIdentifiers ?? NO_IMPORT_NOISE;
|
|
244
|
-
const block = buildBlock(
|
|
245
|
-
resolution.decl,
|
|
246
|
-
resolution.path,
|
|
247
|
-
resolution.ir,
|
|
248
|
-
resolution.source,
|
|
249
|
-
view,
|
|
250
|
-
context,
|
|
251
|
-
importNoise,
|
|
252
|
-
);
|
|
253
|
-
if (docsWarningEmitted) {
|
|
254
|
-
block.warnings = block.warnings.filter((warning) => warning !== "no attached documentation");
|
|
255
|
-
} else if (block.warnings.includes("no attached documentation")) {
|
|
256
|
-
docsWarningEmitted = true;
|
|
257
|
-
}
|
|
258
|
-
blocks.push(block);
|
|
262
|
+
blocks.push(await resolveShowBlock(engine, target, view, context, signal));
|
|
259
263
|
}
|
|
260
|
-
|
|
264
|
+
dedupeDocWarnings(blocks);
|
|
261
265
|
return { blocks };
|
|
262
266
|
}
|
|
@@ -12,7 +12,7 @@ export function walkDecls(decls: readonly Decl[], visit: (decl: Decl, depth: num
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function nameMatches(decl: Decl, name: string): boolean {
|
|
15
|
-
return decl.name === name || decl.qualifiedName === name;
|
|
15
|
+
return decl.name === name || decl.qualifiedName === name || decl.qualifiedName.endsWith(`.${name}`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function coversLine(decl: Decl, line: number): boolean {
|
|
@@ -4,7 +4,6 @@ import { csharpAdapter } from "./languages/csharp.ts";
|
|
|
4
4
|
import { goAdapter } from "./languages/go.ts";
|
|
5
5
|
import { javaAdapter } from "./languages/java.ts";
|
|
6
6
|
import { kotlinAdapter } from "./languages/kotlin.ts";
|
|
7
|
-
import { odinAdapter } from "./languages/odin.ts";
|
|
8
7
|
import { rustAdapter } from "./languages/rust.ts";
|
|
9
8
|
import { swiftAdapter } from "./languages/swift.ts";
|
|
10
9
|
import { tsxAdapter, typescriptAdapter } from "./languages/typescript.ts";
|
|
@@ -34,6 +33,7 @@ const MARKDOWN_CAPABILITIES: LanguageCapabilities = {
|
|
|
34
33
|
fileDeps: false,
|
|
35
34
|
callEdges: false,
|
|
36
35
|
packageSurface: false,
|
|
36
|
+
structuralImplements: false,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
function markdownAdapter(): LanguageAdapter {
|
|
@@ -115,6 +115,5 @@ export function createDefaultRegistry(): AdapterRegistry {
|
|
|
115
115
|
javaAdapter(),
|
|
116
116
|
kotlinAdapter(),
|
|
117
117
|
swiftAdapter(),
|
|
118
|
-
odinAdapter(),
|
|
119
118
|
]);
|
|
120
119
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExploreEngine, FileSource } from "./engine.ts";
|
|
2
|
-
import { DEFAULT_TRAVERSAL_BUDGETS, type TraversalBudgets, type TraversalLimit, walkPaths } from "
|
|
2
|
+
import { DEFAULT_TRAVERSAL_BUDGETS, type TraversalBudgets, type TraversalLimit, walkPaths } from "./traverse.ts";
|
|
3
3
|
|
|
4
4
|
export type ScanOptions = {
|
|
5
5
|
engine: ExploreEngine;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { createExploreEngine, type ExploreEngine } from "./engine.ts";
|
|
3
|
+
import { createFileGraph, type ExploreFileGraph } from "./graph/file-graph.ts";
|
|
4
|
+
|
|
5
|
+
// One engine and file graph per process. Explore tools and file injection share
|
|
6
|
+
// the same parse caches instead of standing up competing engines per caller.
|
|
7
|
+
let current: { engine: ExploreEngine; graph: ExploreFileGraph } | undefined;
|
|
8
|
+
|
|
9
|
+
function sessionFor(cwd: string): { engine: ExploreEngine; graph: ExploreFileGraph } {
|
|
10
|
+
const absoluteCwd = resolve(cwd);
|
|
11
|
+
if (current !== undefined && current.engine.cwd === absoluteCwd) return current;
|
|
12
|
+
current?.engine.shutdown();
|
|
13
|
+
current?.graph.clear();
|
|
14
|
+
const engine = createExploreEngine({ cwd: absoluteCwd });
|
|
15
|
+
current = { engine, graph: createFileGraph(engine) };
|
|
16
|
+
return current;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function astEngineFor(cwd: string): ExploreEngine {
|
|
20
|
+
return sessionFor(cwd).engine;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function astGraphFor(cwd: string): ExploreFileGraph {
|
|
24
|
+
return sessionFor(cwd).graph;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function restartAstSession(cwd: string): void {
|
|
28
|
+
current?.engine.shutdown();
|
|
29
|
+
current?.graph.clear();
|
|
30
|
+
current = undefined;
|
|
31
|
+
sessionFor(cwd);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function invalidateAstPaths(paths: readonly string[], topologyChanged: boolean): void {
|
|
35
|
+
if (current === undefined) return;
|
|
36
|
+
current.engine.invalidate([...paths]);
|
|
37
|
+
if (topologyChanged) current.graph.clear();
|
|
38
|
+
else current.graph.invalidate([...paths]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearAstSession(): void {
|
|
42
|
+
current?.engine.clear();
|
|
43
|
+
current?.graph.clear();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function shutdownAstSession(): void {
|
|
47
|
+
current?.engine.shutdown();
|
|
48
|
+
current?.graph.clear();
|
|
49
|
+
current = undefined;
|
|
50
|
+
}
|
|
@@ -27,6 +27,14 @@ export function docsText(decl: Decl, source: string): string | undefined {
|
|
|
27
27
|
return text.length === 0 ? undefined : text;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const COMPACT_SIGNATURE_MAX = 80;
|
|
31
|
+
|
|
32
|
+
/** One-line signature for row output: whitespace collapsed, capped. */
|
|
33
|
+
export function compactSignature(text: string): string {
|
|
34
|
+
const collapsed = text.replace(/\s+/gu, " ").trim();
|
|
35
|
+
return collapsed.length > COMPACT_SIGNATURE_MAX ? `${collapsed.slice(0, COMPACT_SIGNATURE_MAX - 3)}...` : collapsed;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/** Full declaration span [startOffset, endOffset) — no leading docs. */
|
|
31
39
|
export function declarationText(decl: Decl, source: string): string {
|
|
32
40
|
return source.slice(decl.startOffset, decl.endOffset);
|
|
@@ -350,17 +350,15 @@ export async function walkPaths(options: WalkOptions): Promise<TraversalResult>
|
|
|
350
350
|
return { entries, limit, filesVisited, sourceBytes, elapsedMs: elapsed() };
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
async function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
353
|
+
async function listChildren(
|
|
354
|
+
directory: string,
|
|
355
|
+
depth: number,
|
|
356
|
+
rules: readonly IgnoreRule[],
|
|
357
|
+
): Promise<TraversalHit[] | undefined> {
|
|
360
358
|
const names = await listNames(directory, options.cwd);
|
|
361
359
|
const children: TraversalHit[] = [];
|
|
362
360
|
for (const name of names) {
|
|
363
|
-
if (stopIfNeeded()) return;
|
|
361
|
+
if (stopIfNeeded()) return undefined;
|
|
364
362
|
const childPath = join(directory, name);
|
|
365
363
|
let stats: Stats;
|
|
366
364
|
try {
|
|
@@ -378,20 +376,34 @@ export async function walkPaths(options: WalkOptions): Promise<TraversalResult>
|
|
|
378
376
|
depth: depth + 1,
|
|
379
377
|
});
|
|
380
378
|
}
|
|
379
|
+
return sortHits(children);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function visitChild(child: TraversalHit, rules: readonly IgnoreRule[]): Promise<boolean> {
|
|
383
|
+
if (child.type === "file") return pushFile(child);
|
|
384
|
+
if (!filesOnly) entries.push(child);
|
|
385
|
+
if (child.depth >= budgets.maxDepth) {
|
|
386
|
+
limit = limit ?? "maxDepth";
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
const childRules = includeIgnored ? rules : await appendIgnoreRules(child.absolutePath, rules);
|
|
390
|
+
await walkDir(child.absolutePath, child.depth, childRules);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function walkDir(directory: string, depth: number, rules: readonly IgnoreRule[]): Promise<void> {
|
|
395
|
+
if (stopIfNeeded()) return;
|
|
396
|
+
if (depth >= budgets.maxDepth) {
|
|
397
|
+
limit = limit ?? "maxDepth";
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const children = await listChildren(directory, depth, rules);
|
|
402
|
+
if (children === undefined) return;
|
|
381
403
|
|
|
382
|
-
for (const child of
|
|
404
|
+
for (const child of children) {
|
|
383
405
|
if (stopIfNeeded()) return;
|
|
384
|
-
if (child
|
|
385
|
-
if (!(await pushFile(child))) return;
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (!filesOnly) entries.push(child);
|
|
389
|
-
if (child.depth >= budgets.maxDepth) {
|
|
390
|
-
limit = limit ?? "maxDepth";
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
const childRules = includeIgnored ? rules : await appendIgnoreRules(child.absolutePath, rules);
|
|
394
|
-
await walkDir(child.absolutePath, child.depth, childRules);
|
|
406
|
+
if (!(await visitChild(child, rules))) return;
|
|
395
407
|
}
|
|
396
408
|
}
|
|
397
409
|
|