@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.
Files changed (190) 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/branch/index.ts +38 -21
  8. package/extensions/cache-diagnostics/index.ts +70 -58
  9. package/extensions/checkpoint/README.md +9 -0
  10. package/extensions/checkpoint/checkpoint-budget.ts +79 -0
  11. package/extensions/checkpoint/checkpoint.ts +277 -0
  12. package/extensions/checkpoint/index.ts +102 -0
  13. package/extensions/checkpoint/messages.ts +169 -0
  14. package/extensions/checkpoint/prompt.ts +24 -0
  15. package/extensions/checkpoint/settings.ts +27 -0
  16. package/extensions/commit/commit-effort.ts +3 -3
  17. package/extensions/context/README.md +12 -8
  18. package/extensions/context/definitions.ts +178 -55
  19. package/extensions/context/index.ts +192 -160
  20. package/extensions/context/panel.ts +57 -37
  21. package/extensions/context/sync.ts +139 -89
  22. package/extensions/context/validation.ts +37 -25
  23. package/extensions/effort/README.md +1 -1
  24. package/extensions/effort/index.ts +3 -3
  25. package/extensions/effort/state.ts +3 -18
  26. package/extensions/explore/guidance.ts +6 -47
  27. package/extensions/explore/index.ts +38 -53
  28. package/extensions/explore/{ast/read → read}/hook.ts +12 -7
  29. package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
  30. package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
  31. package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
  32. package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
  33. package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
  34. package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
  35. package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
  36. package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
  37. package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
  38. package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
  39. package/extensions/explore/tools/target-params.ts +9 -0
  40. package/extensions/footer/index.ts +75 -56
  41. package/extensions/handoff/index.ts +8 -30
  42. package/extensions/image-gen/index.ts +46 -40
  43. package/extensions/patch/executor.ts +190 -135
  44. package/extensions/patch/matcher.ts +82 -48
  45. package/extensions/patch/parser.ts +149 -105
  46. package/extensions/patch/render.ts +32 -39
  47. package/extensions/patch/summary.ts +30 -26
  48. package/extensions/ready/README.md +7 -0
  49. package/extensions/ready/index.ts +58 -0
  50. package/extensions/ready/model.ts +128 -0
  51. package/extensions/ready/packs.ts +272 -0
  52. package/extensions/ready/render.ts +445 -0
  53. package/extensions/ready/scan.ts +760 -0
  54. package/extensions/reference/panel.ts +28 -16
  55. package/extensions/review/index.ts +63 -51
  56. package/extensions/review/panel.ts +5 -34
  57. package/extensions/runtime-context/context.ts +30 -26
  58. package/extensions/script-runner/README.md +2 -2
  59. package/extensions/script-runner/index.ts +122 -82
  60. package/extensions/silent-command-runner/index.ts +38 -23
  61. package/extensions/subagent/agents/context-sync.md +185 -33
  62. package/extensions/subagent/agents/scout.md +2 -2
  63. package/extensions/subagent/agents.ts +137 -84
  64. package/extensions/subagent/cmux-dashboard.ts +39 -30
  65. package/extensions/subagent/index.ts +79 -59
  66. package/extensions/subagent/resume.ts +1 -1
  67. package/extensions/subagent/run.ts +304 -193
  68. package/extensions/subagent/runtime.ts +781 -343
  69. package/extensions/tau/index.ts +47 -38
  70. package/extensions/tau-help/help.md +14 -6
  71. package/extensions/tool-loader/README.md +4 -2
  72. package/extensions/tool-loader/index.ts +77 -52
  73. package/extensions/web/index.ts +6 -3
  74. package/extensions/web/webfetch.ts +114 -86
  75. package/package.json +3 -3
  76. package/schemas/tau.schema.json +20 -33
  77. package/shared/bounded-text-result.ts +1 -0
  78. package/shared/checkpoint-visibility.ts +9 -0
  79. package/shared/events.ts +28 -10
  80. package/shared/isolated-session.ts +71 -29
  81. package/shared/model-effort.ts +9 -19
  82. package/shared/model-fallback/index.ts +66 -41
  83. package/shared/settings/load.ts +45 -38
  84. package/shared/temporary-output-store.ts +16 -10
  85. package/{extensions/explore → src}/ast/adapter.ts +11 -2
  86. package/{extensions/explore → src}/ast/engine.ts +54 -38
  87. package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
  88. package/{extensions/explore → src}/ast/format/context.ts +3 -6
  89. package/src/ast/format/deps.ts +94 -0
  90. package/{extensions/explore → src}/ast/format/discover.ts +2 -2
  91. package/{extensions/explore → src}/ast/format/impact.ts +9 -11
  92. package/{extensions/explore → src}/ast/format/outline.ts +2 -2
  93. package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
  94. package/{extensions/explore → src}/ast/format/show.ts +1 -1
  95. package/src/ast/format/targeted-result.ts +20 -0
  96. package/src/ast/grammars/c_sharp.wasm +0 -0
  97. package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
  98. package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
  99. package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
  100. package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
  101. package/{extensions/explore → src}/ast/ir.ts +6 -0
  102. package/src/ast/languages/csharp-file-deps.ts +90 -0
  103. package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
  104. package/src/ast/languages/dotted-index.ts +131 -0
  105. package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
  106. package/src/ast/languages/fixtures/sample.cs +53 -0
  107. package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
  108. package/src/ast/languages/fixtures/sample.go +37 -0
  109. package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
  110. package/src/ast/languages/fixtures/sample.java +40 -0
  111. package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
  112. package/src/ast/languages/fixtures/sample.kt +31 -0
  113. package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
  114. package/src/ast/languages/fixtures/sample.md +23 -0
  115. package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
  116. package/src/ast/languages/fixtures/sample.rs +54 -0
  117. package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
  118. package/src/ast/languages/fixtures/sample.swift +45 -0
  119. package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
  120. package/src/ast/languages/fixtures/sample.ts +45 -0
  121. package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
  122. package/src/ast/languages/fixtures/sample.tsx +36 -0
  123. package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
  124. package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
  125. package/{extensions/explore → src}/ast/languages/go.ts +21 -3
  126. package/{extensions/explore → src}/ast/languages/java.ts +5 -38
  127. package/src/ast/languages/jvm-file-deps.ts +135 -0
  128. package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
  129. package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
  130. package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
  131. package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
  132. package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
  133. package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
  134. package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
  135. package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
  136. package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
  137. package/{extensions/explore → src}/ast/markdown.ts +72 -47
  138. package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
  139. package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
  140. package/{extensions/explore → src}/ast/queries/context.ts +116 -80
  141. package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
  142. package/src/ast/queries/impact.ts +291 -0
  143. package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
  144. package/{extensions/explore → src}/ast/queries/show.ts +46 -42
  145. package/{extensions/explore → src}/ast/query.ts +1 -1
  146. package/{extensions/explore → src}/ast/registry.ts +1 -2
  147. package/{extensions/explore → src}/ast/scan.ts +1 -1
  148. package/src/ast/session.ts +50 -0
  149. package/{extensions/explore → src}/ast/slice.ts +8 -0
  150. package/{extensions/explore → src/ast}/traverse.ts +32 -20
  151. package/src/file-injection/index.ts +547 -0
  152. package/src/image-generation/client.ts +50 -31
  153. package/src/image-generation/index.ts +177 -134
  154. package/src/index.ts +2 -0
  155. package/src/tool-loading/index.ts +58 -0
  156. package/extensions/context/evidence.ts +0 -517
  157. package/extensions/context/projection.ts +0 -141
  158. package/extensions/context/state.ts +0 -30
  159. package/extensions/context/write-scope.ts +0 -109
  160. package/extensions/explore/ast/format/deps.ts +0 -86
  161. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  162. package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
  163. package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
  164. package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
  165. package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
  166. package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
  167. package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
  168. package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
  169. package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
  170. package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
  171. package/extensions/explore/ast/languages/odin.ts +0 -407
  172. package/extensions/explore/ast/queries/impact.ts +0 -250
  173. package/extensions/explore/outline-injection.ts +0 -151
  174. package/extensions/explore/read/autoread.ts +0 -118
  175. package/extensions/working-memory/README.md +0 -17
  176. package/extensions/working-memory/checkpoint.ts +0 -242
  177. package/extensions/working-memory/index.ts +0 -341
  178. package/extensions/working-memory/memory.ts +0 -235
  179. package/extensions/working-memory/render.ts +0 -185
  180. package/extensions/working-memory/settings.ts +0 -38
  181. package/extensions/working-memory/state.ts +0 -158
  182. package/shared/autoread.ts +0 -234
  183. package/shared/context-messages.ts +0 -19
  184. package/shared/outline-injection.ts +0 -56
  185. /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
  186. /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
  187. /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
  188. /package/{extensions/explore → src}/ast/identity.ts +0 -0
  189. /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
  190. /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
@@ -182,13 +182,19 @@ async function snapsFromCalleeSites(
182
182
  ): Promise<DeclSnap[]> {
183
183
  const out: DeclSnap[] = [];
184
184
  const seen = new Set<string>();
185
- const seenNames = new Set<string>();
186
185
  for (const site of sites) {
187
186
  signal.throwIfAborted();
188
- if (site.name.length === 0 || seenNames.has(site.name)) continue;
189
- seenNames.add(site.name);
190
- const resolution = await resolveTarget(engine, scopeDir, { name: site.name }, signal);
187
+ if (site.target === undefined) continue;
188
+ const resolution = await resolveTarget(
189
+ engine,
190
+ scopeDir,
191
+ { path: site.target.path, name: site.name, line: site.target.startLine },
192
+ signal,
193
+ );
191
194
  if (resolution.kind !== "resolved") continue;
195
+ if (site.kind === "construct" ? !isTypeLike(resolution.decl.kind) : !isCallableLike(resolution.decl.kind)) {
196
+ continue;
197
+ }
192
198
  const item: DeclSnap = {
193
199
  path: resolution.path,
194
200
  decl: resolution.decl,
@@ -261,9 +267,13 @@ function packInto(
261
267
  }
262
268
  }
263
269
 
264
- function packTarget(state: PackState, snap: DeclSnap): void {
270
+ /**
271
+ * Type-like targets print signature plus members (the `methods` group covers the
272
+ * bodies); callable targets print their own body.
273
+ */
274
+ function packTarget(state: PackState, snap: DeclSnap, targetKind: DeclKind): void {
265
275
  const left = state.budget - state.used;
266
- const preferBody = !bodyUnavailable(snap);
276
+ const preferBody = !bodyUnavailable(snap) && !isTypeLike(targetKind);
267
277
  if (preferBody) {
268
278
  const body = entryFrom(snap, "body", []);
269
279
  if (body.tokens <= left && body.text.length > 0) {
@@ -273,7 +283,7 @@ function packTarget(state: PackState, snap: DeclSnap): void {
273
283
  return;
274
284
  }
275
285
  }
276
- const flags: ContextFlag[] = ["target_omitted"];
286
+ const flags: ContextFlag[] = isTypeLike(targetKind) ? [] : ["target_omitted"];
277
287
  if (bodyUnavailable(snap)) flags.push("body_unavailable");
278
288
  const signature = entryFrom(snap, "signature", flags);
279
289
  if (signature.tokens <= left && signature.text.length > 0) {
@@ -289,6 +299,102 @@ function packTarget(state: PackState, snap: DeclSnap): void {
289
299
  * Budgeted symbol pack. Bodies/signatures via show view extraction.
290
300
  * Depth-2 is a capped second resolve+query pass — no relationship depth param.
291
301
  */
302
+ async function collectDepth2(
303
+ args: ContextQueryArgs,
304
+ state: PackState,
305
+ snaps: DeclSnap[],
306
+ mode: "callees" | "callers",
307
+ scopeDir: string,
308
+ followUps: { count: number },
309
+ ): Promise<DeclSnap[]> {
310
+ const { engine, signal } = args;
311
+ const out: DeclSnap[] = [];
312
+ for (const snap of snaps) {
313
+ if (followUps.count >= CONTEXT_FOLLOWUP_N || state.used >= state.budget) break;
314
+ signal.throwIfAborted();
315
+ followUps.count += 1;
316
+ const sites = await relSites(args, mode, {
317
+ path: snap.path,
318
+ name: snap.decl.name,
319
+ qualifiedName: snap.decl.qualifiedName,
320
+ kind: snap.decl.kind,
321
+ startLine: snap.decl.startLine,
322
+ });
323
+ const items =
324
+ mode === "callees"
325
+ ? await snapsFromCalleeSites(engine, scopeDir, sites, signal)
326
+ : await snapsFromSites(engine, sites, signal, (d) => isCallableLike(d.kind));
327
+ for (const item of items) {
328
+ if (!state.seen.has(snapKey(item))) out.push(item);
329
+ }
330
+ }
331
+ return out;
332
+ }
333
+
334
+ async function packCallableContext(
335
+ args: ContextQueryArgs,
336
+ state: PackState,
337
+ target: CompositeTarget,
338
+ scopeDir: string,
339
+ ): Promise<void> {
340
+ const { engine, signal } = args;
341
+ const calleeSites = await relSites(args, "callees", target);
342
+ const calleeSnaps = await snapsFromCalleeSites(engine, scopeDir, calleeSites, signal);
343
+ packInto(state, "callees", "direct callees", calleeSnaps, true);
344
+
345
+ const callerSites = await relSites(args, "callers", target);
346
+ const callerSnaps = await snapsFromSites(engine, callerSites, signal, (d) => isCallableLike(d.kind));
347
+ packInto(state, "callers", "direct callers", callerSnaps, false);
348
+
349
+ const followUps = { count: 0 };
350
+ const depth2Callees = await collectDepth2(args, state, calleeSnaps, "callees", scopeDir, followUps);
351
+ packInto(state, "calleesDepth2", "depth-2 callees", depth2Callees, false);
352
+ const depth2Callers = await collectDepth2(args, state, callerSnaps, "callers", scopeDir, followUps);
353
+ packInto(state, "callersDepth2", "depth-2 callers", depth2Callers, false);
354
+ }
355
+
356
+ async function packTypeContext(
357
+ args: ContextQueryArgs,
358
+ state: PackState,
359
+ target: CompositeTarget,
360
+ decl: Decl,
361
+ ir: FileIr,
362
+ source: string,
363
+ ): Promise<void> {
364
+ const { engine, signal } = args;
365
+ const implSites = await relSites(args, "implementations", target);
366
+ const implSnaps = await snapsFromSites(engine, implSites, signal, (d) => isTypeLike(d.kind));
367
+ packInto(state, "implementors", "implementors", implSnaps, true);
368
+
369
+ const methods: DeclSnap[] = [];
370
+ for (const child of decl.children) {
371
+ if (!isCallableLike(child.kind)) continue;
372
+ if (methods.length >= CONTEXT_METHOD_CAP) break;
373
+ methods.push({ path: target.path, decl: child, ir, source });
374
+ }
375
+ packInto(state, "methods", "methods", methods, true);
376
+
377
+ const dependents: DeclSnap[] = [];
378
+ for (const method of methods) {
379
+ if (state.used >= state.budget) break;
380
+ signal.throwIfAborted();
381
+ const sites = await relSites(args, "callers", {
382
+ path: method.path,
383
+ name: method.decl.name,
384
+ qualifiedName: method.decl.qualifiedName,
385
+ kind: method.decl.kind,
386
+ startLine: method.decl.startLine,
387
+ });
388
+ // Method-name matching collects unrelated same-name callers; ambiguous
389
+ // entries would spend budget correct ones need.
390
+ const certain = sites.filter((site) => site.certainty !== "ambiguous");
391
+ for (const item of await snapsFromSites(engine, certain, signal, (d) => isCallableLike(d.kind))) {
392
+ if (!state.seen.has(snapKey(item))) dependents.push(item);
393
+ }
394
+ }
395
+ packInto(state, "dependents", "dependents", dependents, false);
396
+ }
397
+
292
398
  export async function queryContext(args: ContextQueryArgs): Promise<ContextResult> {
293
399
  const { engine, signal } = args;
294
400
  if (args.budget < 1) {
@@ -317,82 +423,12 @@ export async function queryContext(args: ContextQueryArgs): Promise<ContextResul
317
423
  groups: [],
318
424
  };
319
425
 
320
- packTarget(state, targetSnap);
426
+ packTarget(state, targetSnap, kind);
321
427
 
322
428
  if (isCallableLike(kind)) {
323
- const calleeSites = await relSites(args, "callees", target);
324
- const calleeSnaps = await snapsFromCalleeSites(engine, scopeDir, calleeSites, signal);
325
- packInto(state, "callees", "direct callees", calleeSnaps, true);
326
-
327
- const callerSites = await relSites(args, "callers", target);
328
- const callerSnaps = await snapsFromSites(engine, callerSites, signal, (d) => isCallableLike(d.kind));
329
- packInto(state, "callers", "direct callers", callerSnaps, false);
330
-
331
- let followUps = 0;
332
- const depth2Callees: DeclSnap[] = [];
333
- for (const snap of calleeSnaps) {
334
- if (followUps >= CONTEXT_FOLLOWUP_N || state.used >= state.budget) break;
335
- signal.throwIfAborted();
336
- followUps += 1;
337
- const sites = await relSites(args, "callees", {
338
- path: snap.path,
339
- name: snap.decl.name,
340
- qualifiedName: snap.decl.qualifiedName,
341
- kind: snap.decl.kind,
342
- startLine: snap.decl.startLine,
343
- });
344
- for (const item of await snapsFromCalleeSites(engine, scopeDir, sites, signal)) {
345
- if (!state.seen.has(snapKey(item))) depth2Callees.push(item);
346
- }
347
- }
348
- packInto(state, "calleesDepth2", "depth-2 callees", depth2Callees, false);
349
-
350
- const depth2Callers: DeclSnap[] = [];
351
- for (const snap of callerSnaps) {
352
- if (followUps >= CONTEXT_FOLLOWUP_N || state.used >= state.budget) break;
353
- signal.throwIfAborted();
354
- followUps += 1;
355
- const sites = await relSites(args, "callers", {
356
- path: snap.path,
357
- name: snap.decl.name,
358
- qualifiedName: snap.decl.qualifiedName,
359
- kind: snap.decl.kind,
360
- startLine: snap.decl.startLine,
361
- });
362
- for (const item of await snapsFromSites(engine, sites, signal, (d) => isCallableLike(d.kind))) {
363
- if (!state.seen.has(snapKey(item))) depth2Callers.push(item);
364
- }
365
- }
366
- packInto(state, "callersDepth2", "depth-2 callers", depth2Callers, false);
429
+ await packCallableContext(args, state, target, scopeDir);
367
430
  } else {
368
- const implSites = await relSites(args, "implementations", target);
369
- const implSnaps = await snapsFromSites(engine, implSites, signal, (d) => isTypeLike(d.kind));
370
- packInto(state, "implementors", "implementors", implSnaps, true);
371
-
372
- const methods: DeclSnap[] = [];
373
- for (const child of decl.children) {
374
- if (!isCallableLike(child.kind)) continue;
375
- if (methods.length >= CONTEXT_METHOD_CAP) break;
376
- methods.push({ path: target.path, decl: child, ir, source });
377
- }
378
- packInto(state, "methods", "methods", methods, true);
379
-
380
- const dependents: DeclSnap[] = [];
381
- for (const method of methods) {
382
- if (state.used >= state.budget) break;
383
- signal.throwIfAborted();
384
- const sites = await relSites(args, "callers", {
385
- path: method.path,
386
- name: method.decl.name,
387
- qualifiedName: method.decl.qualifiedName,
388
- kind: method.decl.kind,
389
- startLine: method.decl.startLine,
390
- });
391
- for (const item of await snapsFromSites(engine, sites, signal, (d) => isCallableLike(d.kind))) {
392
- if (!state.seen.has(snapKey(item))) dependents.push(item);
393
- }
394
- }
395
- packInto(state, "dependents", "dependents", dependents, false);
431
+ await packTypeContext(args, state, target, decl, ir, source);
396
432
  }
397
433
 
398
434
  return {
@@ -4,7 +4,7 @@ import type { Decl, DeclKind, Visibility } from "../ir.ts";
4
4
  import { packageDeclKey, type PackageSurfaceHost } from "../package-surface.ts";
5
5
  import { scanSources, type ScanOutcome } from "../scan.ts";
6
6
  import { docsText, signatureText } from "../slice.ts";
7
- import { pathResolutionError, resolveExplorePath } from "../../traverse.ts";
7
+ import { pathResolutionError, resolveExplorePath } from "../traverse.ts";
8
8
 
9
9
  export type DiscoverSurface = "all" | "public" | "private" | "sourceExport" | "packageSurface";
10
10
 
@@ -54,10 +54,14 @@ function nameFields(decl: Decl): [string, string] {
54
54
  }
55
55
 
56
56
  function matchesNameQuery(kind: "exactName" | "prefixName" | "substringName", needle: string, decl: Decl): boolean {
57
+ // Every language in the sweep capitalizes types and lowercases functions, so
58
+ // non-exact matching folds case; exactName stays exact.
59
+ const folded = needle.toLowerCase();
57
60
  for (const field of nameFields(decl)) {
58
61
  if (kind === "exactName" && field === needle) return true;
59
- if (kind === "prefixName" && field.startsWith(needle)) return true;
60
- if (kind === "substringName" && field.includes(needle)) return true;
62
+ const lower = field.toLowerCase();
63
+ if (kind === "prefixName" && lower.startsWith(folded)) return true;
64
+ if (kind === "substringName" && lower.includes(folded)) return true;
61
65
  }
62
66
  return false;
63
67
  }
@@ -87,6 +91,20 @@ function fuzzyScore(query: string, text: string): number | undefined {
87
91
  return dist === undefined ? undefined : 1000 + dist;
88
92
  }
89
93
 
94
+ function levenshteinRow(prev: number[], cur: number[], b: string, ca: number, maxDist: number): number {
95
+ let rowMin = cur[0] ?? maxDist + 1;
96
+ for (let j = 1; j <= b.length; j += 1) {
97
+ const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
98
+ const del = (prev[j] ?? maxDist + 1) + 1;
99
+ const ins = (cur[j - 1] ?? maxDist + 1) + 1;
100
+ const sub = (prev[j - 1] ?? maxDist + 1) + cost;
101
+ const v = Math.min(del, ins, sub);
102
+ cur[j] = v;
103
+ if (v < rowMin) rowMin = v;
104
+ }
105
+ return rowMin;
106
+ }
107
+
90
108
  function boundedLevenshtein(a: string, b: string, maxDist: number): number | undefined {
91
109
  if (Math.abs(a.length - b.length) > maxDist) return undefined;
92
110
  const prev = Array.from({ length: b.length + 1 }, () => 0);
@@ -94,17 +112,7 @@ function boundedLevenshtein(a: string, b: string, maxDist: number): number | und
94
112
  for (let j = 0; j <= b.length; j += 1) prev[j] = j;
95
113
  for (let i = 1; i <= a.length; i += 1) {
96
114
  cur[0] = i;
97
- let rowMin = cur[0] ?? i;
98
- const ca = a.charCodeAt(i - 1);
99
- for (let j = 1; j <= b.length; j += 1) {
100
- const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
101
- const del = (prev[j] ?? maxDist + 1) + 1;
102
- const ins = (cur[j - 1] ?? maxDist + 1) + 1;
103
- const sub = (prev[j - 1] ?? maxDist + 1) + cost;
104
- const v = Math.min(del, ins, sub);
105
- cur[j] = v;
106
- if (v < rowMin) rowMin = v;
107
- }
115
+ const rowMin = levenshteinRow(prev, cur, b, a.charCodeAt(i - 1), maxDist);
108
116
  if (rowMin > maxDist) return undefined;
109
117
  for (let j = 0; j <= b.length; j += 1) prev[j] = cur[j] ?? 0;
110
118
  }
@@ -113,11 +121,14 @@ function boundedLevenshtein(a: string, b: string, maxDist: number): number | und
113
121
  }
114
122
 
115
123
  function bestFuzzyScore(query: string, decl: Decl): number | undefined {
124
+ const folded = query.toLowerCase();
116
125
  let best: number | undefined;
117
126
  for (const field of nameFields(decl)) {
118
- const score = fuzzyScore(query, field);
127
+ const score = fuzzyScore(folded, field.toLowerCase());
119
128
  if (score === undefined) continue;
120
- if (best === undefined || score < best) best = score;
129
+ // Same-case hits still sort ahead of case-folded ones inside a tier.
130
+ const adjusted = field.includes(query) ? score : score + 0.5;
131
+ if (best === undefined || adjusted < best) best = adjusted;
121
132
  }
122
133
  return best;
123
134
  }
@@ -201,6 +212,57 @@ function newBucket(query: DiscoverQuery): MatchBucket {
201
212
  };
202
213
  }
203
214
 
215
+ function consumeWork(bucket: MatchBucket, maxWork: number): boolean {
216
+ bucket.workUsed += 1;
217
+ if (bucket.workUsed <= maxWork) return true;
218
+ bucket.workLimitReached = true;
219
+ return false;
220
+ }
221
+
222
+ function considerFuzzy(
223
+ bucket: MatchBucket,
224
+ query: Extract<DiscoverQuery, { kind: "fuzzyName" }>,
225
+ path: string,
226
+ decl: Decl,
227
+ source: string,
228
+ access: string | undefined,
229
+ ): void {
230
+ if (!consumeWork(bucket, query.maxWork)) return;
231
+ const score = bestFuzzyScore(query.name, decl);
232
+ if (score === undefined) return;
233
+ const candidate = toCandidate(path, decl, source, access);
234
+ const worst = bucket.scored[bucket.scored.length - 1];
235
+ if (bucket.scored.length >= query.maxCandidates && worst !== undefined && score >= worst.score) {
236
+ bucket.candidateLimitReached = true;
237
+ return;
238
+ }
239
+ const before = bucket.scored.length;
240
+ insertScored(bucket.scored, { score, candidate }, query.maxCandidates);
241
+ if (before === query.maxCandidates) bucket.candidateLimitReached = true;
242
+ }
243
+
244
+ function considerDocumentation(
245
+ bucket: MatchBucket,
246
+ query: Extract<DiscoverQuery, { kind: "documentation" }>,
247
+ path: string,
248
+ decl: Decl,
249
+ source: string,
250
+ access: string | undefined,
251
+ ): void {
252
+ if (decl.docStartOffset === undefined || decl.docEndOffset === undefined) return;
253
+ if (!consumeWork(bucket, query.maxWork)) return;
254
+ const docs = docsText(decl, source);
255
+ if (docs === undefined) return;
256
+ const foldedDocs = docs.toLowerCase();
257
+ if (!query.terms.every((term) => foldedDocs.includes(term.toLowerCase()))) return;
258
+ if (bucket.scored.length >= query.maxCandidates) {
259
+ bucket.candidateLimitReached = true;
260
+ return;
261
+ }
262
+ bucket.scored.push({ score: bucket.scored.length, candidate: toCandidate(path, decl, source, access) });
263
+ if (bucket.scored.length >= query.maxCandidates) bucket.candidateLimitReached = true;
264
+ }
265
+
204
266
  function considerDecl(
205
267
  bucket: MatchBucket,
206
268
  query: DiscoverQuery,
@@ -231,41 +293,11 @@ function considerDecl(
231
293
  bucket.plain.push(toCandidate(path, decl, source, access));
232
294
  return;
233
295
  }
234
- case "fuzzyName": {
235
- bucket.workUsed += 1;
236
- if (bucket.workUsed > query.maxWork) {
237
- bucket.workLimitReached = true;
238
- return;
239
- }
240
- const score = bestFuzzyScore(query.name, decl);
241
- if (score === undefined) return;
242
- const candidate = toCandidate(path, decl, source, access);
243
- const worst = bucket.scored[bucket.scored.length - 1];
244
- if (bucket.scored.length >= query.maxCandidates && worst !== undefined && score >= worst.score) {
245
- bucket.candidateLimitReached = true;
246
- return;
247
- }
248
- const before = bucket.scored.length;
249
- insertScored(bucket.scored, { score, candidate }, query.maxCandidates);
250
- if (before === query.maxCandidates) bucket.candidateLimitReached = true;
296
+ case "fuzzyName":
297
+ considerFuzzy(bucket, query, path, decl, source, access);
251
298
  return;
252
- }
253
- case "documentation": {
254
- if (decl.docStartOffset === undefined || decl.docEndOffset === undefined) return;
255
- bucket.workUsed += 1;
256
- if (bucket.workUsed > query.maxWork) {
257
- bucket.workLimitReached = true;
258
- return;
259
- }
260
- const docs = docsText(decl, source);
261
- if (docs === undefined || !query.terms.every((term) => docs.includes(term))) return;
262
- if (bucket.scored.length >= query.maxCandidates) {
263
- bucket.candidateLimitReached = true;
264
- return;
265
- }
266
- bucket.scored.push({ score: bucket.scored.length, candidate: toCandidate(path, decl, source, access) });
267
- if (bucket.scored.length >= query.maxCandidates) bucket.candidateLimitReached = true;
268
- }
299
+ case "documentation":
300
+ considerDocumentation(bucket, query, path, decl, source, access);
269
301
  }
270
302
  }
271
303
 
@@ -338,68 +370,68 @@ function packageSurfaceHost(engine: ExploreEngine): PackageSurfaceHost {
338
370
  };
339
371
  }
340
372
 
341
- /** Directory-scoped declaration discovery. Language rules stay on adapters. */
342
- export async function discover(
373
+ async function discoverPackageSurface(
343
374
  engine: ExploreEngine,
344
- pathInput: string,
375
+ absolutePath: string,
345
376
  query: DiscoverQuery,
346
- surface: DiscoverSurface,
347
377
  resultLimit: number,
348
378
  signal: AbortSignal,
349
379
  ): Promise<DiscoverResult> {
350
- signal.throwIfAborted();
351
- const absolutePath = resolveExplorePath(engine.cwd, pathInput);
352
- await pathIsDirectory(absolutePath, pathInput);
353
-
354
- const bucket = newBucket(query);
355
-
356
- if (surface === "packageSurface") {
357
- const resolvers = engine.registry.packageSurfaceResolvers();
358
- if (resolvers.length === 0) {
359
- throw new Error("packageSurface capability is not available");
360
- }
361
- const host = packageSurfaceHost(engine);
362
- const packageKeys = new Set<string>();
363
- const packageAccess = new Map<string, string>();
364
- const paths = new Set<string>();
365
- let filesVisited = 0;
366
- let anyPackage = false;
367
-
368
- for (const resolve of resolvers) {
369
- signal.throwIfAborted();
370
- const graph = await resolve(absolutePath, host, signal);
371
- if (graph === undefined) continue;
372
- anyPackage = true;
373
- filesVisited += graph.filesVisited;
374
- for (const key of graph.declKeys) packageKeys.add(key);
375
- for (const [key, access] of graph.accessByDecl) packageAccess.set(key, access);
376
- for (const path of graph.paths) paths.add(path);
377
- }
378
-
379
- if (!anyPackage) {
380
- throw new Error("packageSurface: no package found for a supporting language near the scope path");
381
- }
380
+ const resolvers = engine.registry.packageSurfaceResolvers();
381
+ if (resolvers.length === 0) {
382
+ throw new Error("packageSurface capability is not available");
383
+ }
384
+ const host = packageSurfaceHost(engine);
385
+ const packageKeys = new Set<string>();
386
+ const packageAccess = new Map<string, string>();
387
+ const paths = new Set<string>();
388
+ let filesVisited = 0;
389
+ let anyPackage = false;
390
+
391
+ for (const resolve of resolvers) {
392
+ signal.throwIfAborted();
393
+ const graph = await resolve(absolutePath, host, signal);
394
+ if (graph === undefined) continue;
395
+ anyPackage = true;
396
+ filesVisited += graph.filesVisited;
397
+ for (const key of graph.declKeys) packageKeys.add(key);
398
+ for (const [key, access] of graph.accessByDecl) packageAccess.set(key, access);
399
+ for (const path of graph.paths) paths.add(path);
400
+ }
382
401
 
383
- for (const path of paths) {
384
- signal.throwIfAborted();
385
- if (bucket.workLimitReached) break;
386
- const file = await engine.sourceForFile(path);
387
- walkFileDecls(bucket, query, file, "packageSurface", packageKeys, packageAccess);
388
- }
402
+ if (!anyPackage) {
403
+ throw new Error("packageSurface: no package found for a supporting language near the scope path");
404
+ }
389
405
 
390
- const finalized = finalizeBucket(bucket, resultLimit);
391
- return {
392
- ...finalized,
393
- scan: {
394
- limit: undefined,
395
- filesVisited,
396
- sourceBytes: 0,
397
- elapsedMs: 0,
398
- filesEmitted: paths.size,
399
- },
400
- };
406
+ const bucket = newBucket(query);
407
+ for (const path of paths) {
408
+ signal.throwIfAborted();
409
+ if (bucket.workLimitReached) break;
410
+ const file = await engine.sourceForFile(path);
411
+ walkFileDecls(bucket, query, file, "packageSurface", packageKeys, packageAccess);
401
412
  }
402
413
 
414
+ return {
415
+ ...finalizeBucket(bucket, resultLimit),
416
+ scan: {
417
+ limit: undefined,
418
+ filesVisited,
419
+ sourceBytes: 0,
420
+ elapsedMs: 0,
421
+ filesEmitted: paths.size,
422
+ },
423
+ };
424
+ }
425
+
426
+ async function discoverViaScan(
427
+ engine: ExploreEngine,
428
+ absolutePath: string,
429
+ query: DiscoverQuery,
430
+ surface: DiscoverSurface,
431
+ resultLimit: number,
432
+ signal: AbortSignal,
433
+ ): Promise<DiscoverResult> {
434
+ const bucket = newBucket(query);
403
435
  const scan = scanSources({ engine, cwd: engine.cwd, root: absolutePath, signal });
404
436
  let step = await scan.next();
405
437
  let filesEmitted = 0;
@@ -421,7 +453,24 @@ export async function discover(
421
453
  filesEmitted,
422
454
  };
423
455
  if (outcome.limit === "cancelled") throw new Error("discover cancelled");
456
+ return { ...finalizeBucket(bucket, resultLimit), scan: outcome };
457
+ }
424
458
 
425
- const finalized = finalizeBucket(bucket, resultLimit);
426
- return { ...finalized, scan: outcome };
459
+ /** Directory-scoped declaration discovery. Language rules stay on adapters. */
460
+ export async function discover(
461
+ engine: ExploreEngine,
462
+ pathInput: string,
463
+ query: DiscoverQuery,
464
+ surface: DiscoverSurface,
465
+ resultLimit: number,
466
+ signal: AbortSignal,
467
+ ): Promise<DiscoverResult> {
468
+ signal.throwIfAborted();
469
+ const absolutePath = resolveExplorePath(engine.cwd, pathInput);
470
+ await pathIsDirectory(absolutePath, pathInput);
471
+
472
+ if (surface === "packageSurface") {
473
+ return discoverPackageSurface(engine, absolutePath, query, resultLimit, signal);
474
+ }
475
+ return discoverViaScan(engine, absolutePath, query, surface, resultLimit, signal);
427
476
  }