@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.
Files changed (203) hide show
  1. package/docs/context.md +29 -20
  2. package/docs/extending-tau-agent.md +63 -24
  3. package/extensions/appshot/index.ts +112 -111
  4. package/extensions/aside/README.md +7 -0
  5. package/extensions/aside/index.ts +168 -0
  6. package/extensions/aside/panel.ts +83 -0
  7. package/extensions/bash-approval/README.md +20 -0
  8. package/extensions/bash-approval/index.ts +235 -0
  9. package/extensions/bash-approval/settings.ts +24 -0
  10. package/extensions/branch/index.ts +38 -21
  11. package/extensions/cache-diagnostics/index.ts +70 -58
  12. package/extensions/checkpoint/README.md +9 -0
  13. package/extensions/checkpoint/checkpoint-budget.ts +66 -0
  14. package/extensions/checkpoint/checkpoint.ts +279 -0
  15. package/extensions/checkpoint/index.ts +102 -0
  16. package/extensions/checkpoint/messages.ts +170 -0
  17. package/extensions/checkpoint/prompt.ts +24 -0
  18. package/extensions/checkpoint/settings.ts +27 -0
  19. package/extensions/commit/commit-effort.ts +3 -3
  20. package/extensions/commit/commit-plan.ts +2 -2
  21. package/extensions/context/README.md +12 -8
  22. package/extensions/context/definitions.ts +178 -55
  23. package/extensions/context/index.ts +192 -160
  24. package/extensions/context/panel.ts +57 -37
  25. package/extensions/context/sync.ts +139 -89
  26. package/extensions/context/validation.ts +37 -25
  27. package/extensions/effort/README.md +1 -1
  28. package/extensions/effort/index.ts +3 -3
  29. package/extensions/effort/state.ts +3 -18
  30. package/extensions/explore/guidance.ts +6 -47
  31. package/extensions/explore/index.ts +38 -53
  32. package/extensions/explore/{ast/read → read}/hook.ts +12 -7
  33. package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
  34. package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
  35. package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
  36. package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
  37. package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
  38. package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
  39. package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
  40. package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
  41. package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
  42. package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
  43. package/extensions/explore/tools/target-params.ts +9 -0
  44. package/extensions/footer/index.ts +75 -56
  45. package/extensions/handoff/index.ts +8 -30
  46. package/extensions/image-gen/index.ts +46 -40
  47. package/extensions/patch/executor.ts +190 -135
  48. package/extensions/patch/matcher.ts +82 -48
  49. package/extensions/patch/parser.ts +149 -105
  50. package/extensions/patch/render.ts +32 -39
  51. package/extensions/patch/summary.ts +30 -26
  52. package/extensions/ready/README.md +7 -0
  53. package/extensions/ready/index.ts +58 -0
  54. package/extensions/ready/model.ts +128 -0
  55. package/extensions/ready/packs.ts +272 -0
  56. package/extensions/ready/render.ts +445 -0
  57. package/extensions/ready/scan.ts +760 -0
  58. package/extensions/reference/index.ts +2 -0
  59. package/extensions/reference/panel.ts +94 -31
  60. package/extensions/review/README.md +14 -6
  61. package/extensions/review/index.ts +58 -99
  62. package/extensions/review/model.ts +13 -33
  63. package/extensions/review/session.ts +9 -23
  64. package/extensions/runtime-context/context.ts +30 -26
  65. package/extensions/script-runner/README.md +2 -2
  66. package/extensions/script-runner/index.ts +122 -82
  67. package/extensions/silent-command-runner/index.ts +38 -23
  68. package/extensions/soul/README.md +2 -2
  69. package/extensions/soul/index.ts +4 -4
  70. package/extensions/soul/prompt.ts +6 -10
  71. package/extensions/soul/settings.ts +6 -3
  72. package/extensions/subagent/agents/context-sync.md +185 -33
  73. package/extensions/subagent/agents/scout.md +2 -2
  74. package/extensions/subagent/agents/web-research.md +2 -2
  75. package/extensions/subagent/agents.ts +137 -84
  76. package/extensions/subagent/cmux-dashboard.ts +39 -30
  77. package/extensions/subagent/index.ts +79 -59
  78. package/extensions/subagent/resume.ts +1 -1
  79. package/extensions/subagent/run.ts +308 -193
  80. package/extensions/subagent/runtime.ts +781 -343
  81. package/extensions/tau/index.ts +47 -38
  82. package/extensions/tau-help/help.md +20 -8
  83. package/extensions/tool-loader/README.md +4 -2
  84. package/extensions/tool-loader/index.ts +77 -52
  85. package/extensions/web/index.ts +6 -3
  86. package/extensions/web/webfetch.ts +114 -86
  87. package/package.json +3 -3
  88. package/schemas/tau.schema.json +38 -35
  89. package/shared/bounded-text-result.ts +1 -0
  90. package/shared/checkpoint-visibility.ts +9 -0
  91. package/shared/events.ts +28 -10
  92. package/shared/isolated-session.ts +72 -30
  93. package/shared/model-effort.ts +23 -22
  94. package/shared/model-fallback/index.ts +66 -41
  95. package/shared/settings/load.ts +45 -38
  96. package/shared/temporary-output-store.ts +16 -10
  97. package/{extensions/explore → src}/ast/adapter.ts +11 -2
  98. package/{extensions/explore → src}/ast/engine.ts +54 -38
  99. package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
  100. package/{extensions/explore → src}/ast/format/context.ts +3 -6
  101. package/src/ast/format/deps.ts +94 -0
  102. package/{extensions/explore → src}/ast/format/discover.ts +2 -2
  103. package/{extensions/explore → src}/ast/format/impact.ts +9 -11
  104. package/{extensions/explore → src}/ast/format/outline.ts +2 -2
  105. package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
  106. package/{extensions/explore → src}/ast/format/show.ts +1 -1
  107. package/src/ast/format/targeted-result.ts +20 -0
  108. package/src/ast/grammars/c_sharp.wasm +0 -0
  109. package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
  110. package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
  111. package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
  112. package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
  113. package/{extensions/explore → src}/ast/ir.ts +6 -0
  114. package/src/ast/languages/csharp-file-deps.ts +90 -0
  115. package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
  116. package/src/ast/languages/dotted-index.ts +131 -0
  117. package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
  118. package/src/ast/languages/fixtures/sample.cs +53 -0
  119. package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
  120. package/src/ast/languages/fixtures/sample.go +37 -0
  121. package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
  122. package/src/ast/languages/fixtures/sample.java +40 -0
  123. package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
  124. package/src/ast/languages/fixtures/sample.kt +31 -0
  125. package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
  126. package/src/ast/languages/fixtures/sample.md +23 -0
  127. package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
  128. package/src/ast/languages/fixtures/sample.rs +54 -0
  129. package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
  130. package/src/ast/languages/fixtures/sample.swift +45 -0
  131. package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
  132. package/src/ast/languages/fixtures/sample.ts +45 -0
  133. package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
  134. package/src/ast/languages/fixtures/sample.tsx +36 -0
  135. package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
  136. package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
  137. package/{extensions/explore → src}/ast/languages/go.ts +21 -3
  138. package/{extensions/explore → src}/ast/languages/java.ts +5 -38
  139. package/src/ast/languages/jvm-file-deps.ts +135 -0
  140. package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
  141. package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
  142. package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
  143. package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
  144. package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
  145. package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
  146. package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
  147. package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
  148. package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
  149. package/{extensions/explore → src}/ast/markdown.ts +72 -47
  150. package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
  151. package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
  152. package/{extensions/explore → src}/ast/queries/context.ts +116 -80
  153. package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
  154. package/src/ast/queries/impact.ts +291 -0
  155. package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
  156. package/{extensions/explore → src}/ast/queries/show.ts +46 -42
  157. package/{extensions/explore → src}/ast/query.ts +1 -1
  158. package/{extensions/explore → src}/ast/registry.ts +1 -2
  159. package/{extensions/explore → src}/ast/scan.ts +1 -1
  160. package/src/ast/session.ts +50 -0
  161. package/{extensions/explore → src}/ast/slice.ts +8 -0
  162. package/{extensions/explore → src/ast}/traverse.ts +32 -20
  163. package/src/file-injection/index.ts +547 -0
  164. package/src/image-generation/client.ts +50 -31
  165. package/src/image-generation/index.ts +177 -134
  166. package/src/index.ts +2 -0
  167. package/src/tool-loading/index.ts +58 -0
  168. package/extensions/context/evidence.ts +0 -517
  169. package/extensions/context/projection.ts +0 -141
  170. package/extensions/context/state.ts +0 -30
  171. package/extensions/context/write-scope.ts +0 -109
  172. package/extensions/explore/ast/format/deps.ts +0 -86
  173. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  174. package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
  175. package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
  176. package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
  177. package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
  178. package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
  179. package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
  180. package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
  181. package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
  182. package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
  183. package/extensions/explore/ast/languages/odin.ts +0 -407
  184. package/extensions/explore/ast/queries/impact.ts +0 -250
  185. package/extensions/explore/outline-injection.ts +0 -151
  186. package/extensions/explore/read/autoread.ts +0 -118
  187. package/extensions/review/panel.ts +0 -128
  188. package/extensions/working-memory/README.md +0 -17
  189. package/extensions/working-memory/checkpoint.ts +0 -242
  190. package/extensions/working-memory/index.ts +0 -341
  191. package/extensions/working-memory/memory.ts +0 -235
  192. package/extensions/working-memory/render.ts +0 -185
  193. package/extensions/working-memory/settings.ts +0 -38
  194. package/extensions/working-memory/state.ts +0 -158
  195. package/shared/autoread.ts +0 -234
  196. package/shared/context-messages.ts +0 -19
  197. package/shared/outline-injection.ts +0 -56
  198. /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
  199. /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
  200. /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
  201. /package/{extensions/explore → src}/ast/identity.ts +0 -0
  202. /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
  203. /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 "../../traverse.ts";
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
- return (decl) => decl.visibility === "public" || decl.visibility === "protected";
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 "../../traverse.ts";
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
- let docsWarningEmitted = false;
221
-
222
- for (const target of unique) {
260
+ for (const target of dedupeShowTargets(targets)) {
223
261
  signal.throwIfAborted();
224
- const resolutionTarget: Target = {
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 "../traverse.ts";
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 walkDir(directory: string, depth: number, rules: readonly IgnoreRule[]): Promise<void> {
354
- if (stopIfNeeded()) return;
355
- if (depth >= budgets.maxDepth) {
356
- limit = limit ?? "maxDepth";
357
- return;
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 sortHits(children)) {
404
+ for (const child of children) {
383
405
  if (stopIfNeeded()) return;
384
- if (child.type === "file") {
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