@shanepadgett/tau-agent 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +130 -93
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -71,69 +71,94 @@ function closeOpen(stack: OpenHeading[], depth: number, endLine: number, endOffs
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
type FenceState = { char: "`" | "~"; length: number };
|
|
75
|
+
|
|
76
|
+
function closesFence(text: string, fence: FenceState): boolean {
|
|
77
|
+
const close = text.match(/^ {0,3}(`{3,}|~{3,})[ \t]*$/);
|
|
78
|
+
const marker = close?.[1];
|
|
79
|
+
return marker !== undefined && marker[0] === fence.char && marker.length >= fence.length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function openFence(text: string): FenceState | undefined {
|
|
83
|
+
const fence = text.match(OPEN_FENCE);
|
|
84
|
+
const fenceMarker = fence?.[2];
|
|
85
|
+
if (fenceMarker === undefined) return undefined;
|
|
86
|
+
const char = fenceMarker[0] === "~" ? "~" : "`";
|
|
87
|
+
return { char, length: fenceMarker.length };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseAtxHeading(text: string): { depth: number; name: string } | undefined {
|
|
91
|
+
const heading = text.match(ATX_HEADING);
|
|
92
|
+
if (heading === null) return undefined;
|
|
93
|
+
const hashes = heading[1];
|
|
94
|
+
if (hashes === undefined) return undefined;
|
|
95
|
+
const name = (heading[2] ?? "").trim();
|
|
96
|
+
if (name.length === 0) return undefined;
|
|
97
|
+
return { depth: hashes.length, name };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function makeHeadingDecl(info: LineInfo, name: string, parent: OpenHeading | undefined): Decl {
|
|
101
|
+
const qualifiedName = parent === undefined ? name : `${parent.decl.qualifiedName}.${name}`;
|
|
102
|
+
return {
|
|
103
|
+
kind: "heading",
|
|
104
|
+
name,
|
|
105
|
+
qualifiedName,
|
|
106
|
+
startLine: info.line,
|
|
107
|
+
endLine: info.line,
|
|
108
|
+
startOffset: info.startOffset,
|
|
109
|
+
endOffset: info.endOffset,
|
|
110
|
+
signatureEndOffset: info.endOffset,
|
|
111
|
+
bodyStartOffset: info.nextOffset,
|
|
112
|
+
bodyEndOffset: info.nextOffset,
|
|
113
|
+
visibility: "public",
|
|
114
|
+
exported: true,
|
|
115
|
+
children: [],
|
|
116
|
+
calls: [],
|
|
117
|
+
bases: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
74
121
|
/**
|
|
75
122
|
* ATX heading scanner. Fenced code (``` / ~~~, length ≥ 3, matching closer) is not heading material.
|
|
76
123
|
* Section range runs to the line before the next same-or-shallower heading, else EOF.
|
|
77
124
|
*/
|
|
125
|
+
function pushHeading(
|
|
126
|
+
lines: readonly LineInfo[],
|
|
127
|
+
info: LineInfo,
|
|
128
|
+
heading: { depth: number; name: string },
|
|
129
|
+
stack: OpenHeading[],
|
|
130
|
+
roots: Decl[],
|
|
131
|
+
): void {
|
|
132
|
+
const prev = info.line > 1 ? lines[info.line - 2] : undefined;
|
|
133
|
+
const closeEndLine = prev === undefined ? info.line : prev.line;
|
|
134
|
+
closeOpen(stack, heading.depth, closeEndLine, info.startOffset);
|
|
135
|
+
const parent = stack[stack.length - 1];
|
|
136
|
+
const decl = makeHeadingDecl(info, heading.name, parent);
|
|
137
|
+
if (parent === undefined) roots.push(decl);
|
|
138
|
+
else parent.decl.children.push(decl);
|
|
139
|
+
stack.push({ decl, depth: heading.depth });
|
|
140
|
+
}
|
|
141
|
+
|
|
78
142
|
export function extractMarkdown(source: string): ExtractResult {
|
|
79
143
|
const lines = splitLines(source);
|
|
80
144
|
const roots: Decl[] = [];
|
|
81
145
|
const stack: OpenHeading[] = [];
|
|
82
|
-
let inFence:
|
|
146
|
+
let inFence: FenceState | undefined;
|
|
83
147
|
|
|
84
148
|
for (const info of lines) {
|
|
85
149
|
if (inFence !== undefined) {
|
|
86
|
-
|
|
87
|
-
const marker = close?.[1];
|
|
88
|
-
if (marker !== undefined && marker[0] === inFence.char && marker.length >= inFence.length) {
|
|
89
|
-
inFence = undefined;
|
|
90
|
-
}
|
|
150
|
+
if (closesFence(info.text, inFence)) inFence = undefined;
|
|
91
151
|
continue;
|
|
92
152
|
}
|
|
93
153
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const char = fenceMarker[0] === "~" ? "~" : "`";
|
|
98
|
-
inFence = { char, length: fenceMarker.length };
|
|
154
|
+
const opened = openFence(info.text);
|
|
155
|
+
if (opened !== undefined) {
|
|
156
|
+
inFence = opened;
|
|
99
157
|
continue;
|
|
100
158
|
}
|
|
101
159
|
|
|
102
|
-
const heading = info.text
|
|
103
|
-
if (heading
|
|
104
|
-
const hashes = heading[1];
|
|
105
|
-
if (hashes === undefined) continue;
|
|
106
|
-
const depth = hashes.length;
|
|
107
|
-
const name = (heading[2] ?? "").trim();
|
|
108
|
-
if (name.length === 0) continue;
|
|
109
|
-
|
|
110
|
-
const prev = info.line > 1 ? lines[info.line - 2] : undefined;
|
|
111
|
-
const closeEndLine = prev === undefined ? info.line : prev.line;
|
|
112
|
-
closeOpen(stack, depth, closeEndLine, info.startOffset);
|
|
113
|
-
|
|
114
|
-
const parent = stack[stack.length - 1];
|
|
115
|
-
const qualifiedName = parent === undefined ? name : `${parent.decl.qualifiedName}.${name}`;
|
|
116
|
-
const decl: Decl = {
|
|
117
|
-
kind: "heading",
|
|
118
|
-
name,
|
|
119
|
-
qualifiedName,
|
|
120
|
-
startLine: info.line,
|
|
121
|
-
endLine: info.line,
|
|
122
|
-
startOffset: info.startOffset,
|
|
123
|
-
endOffset: info.endOffset,
|
|
124
|
-
signatureEndOffset: info.endOffset,
|
|
125
|
-
bodyStartOffset: info.nextOffset,
|
|
126
|
-
bodyEndOffset: info.nextOffset,
|
|
127
|
-
visibility: "public",
|
|
128
|
-
exported: true,
|
|
129
|
-
children: [],
|
|
130
|
-
calls: [],
|
|
131
|
-
bases: [],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
if (parent === undefined) roots.push(decl);
|
|
135
|
-
else parent.decl.children.push(decl);
|
|
136
|
-
stack.push({ decl, depth });
|
|
160
|
+
const heading = parseAtxHeading(info.text);
|
|
161
|
+
if (heading !== undefined) pushHeading(lines, info, heading, stack, roots);
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
const last = lines[lines.length - 1];
|
|
@@ -141,5 +166,5 @@ export function extractMarkdown(source: string): ExtractResult {
|
|
|
141
166
|
const eofOffset = last === undefined ? 0 : last.nextOffset;
|
|
142
167
|
closeOpen(stack, 1, eofLine, eofOffset);
|
|
143
168
|
|
|
144
|
-
return { decls: roots, imports: [] };
|
|
169
|
+
return { decls: roots, imports: [], fileCalls: [] };
|
|
145
170
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
-
import type { AstSearchHit, ExploreEngine } from "../engine.ts";
|
|
2
|
+
import type { AstSearchHit, ExploreEngine, FileSource } from "../engine.ts";
|
|
3
3
|
import type { Decl, DeclKind, FileIr } from "../ir.ts";
|
|
4
4
|
import { walkDecls } from "../query.ts";
|
|
5
5
|
import { scanSources, type ScanOutcome } from "../scan.ts";
|
|
6
|
-
import { formatPathForDisplay, pathResolutionError, resolveExplorePath } from "
|
|
6
|
+
import { formatPathForDisplay, pathResolutionError, resolveExplorePath } from "../traverse.ts";
|
|
7
7
|
|
|
8
8
|
const PATTERN_MAX_BYTES = 16 * 1024;
|
|
9
9
|
|
|
@@ -32,32 +32,39 @@ function assertPattern(pattern: string): void {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function requireSearchSupport(supportsSearch: boolean, label: string): void {
|
|
36
|
+
if (!supportsSearch) {
|
|
37
|
+
throw new Error(`Language does not support structural search: ${label}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pathLanguageId(engine: ExploreEngine, absolutePath: string): string {
|
|
42
|
+
const byPath = engine.registry.adapterForPath(absolutePath);
|
|
43
|
+
if (byPath === undefined) {
|
|
44
|
+
throw new Error(`Unsupported language for path: ${formatPathForDisplay(absolutePath, engine.cwd)}`);
|
|
45
|
+
}
|
|
46
|
+
return byPath.id;
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
function resolveLanguageId(
|
|
36
50
|
engine: ExploreEngine,
|
|
37
51
|
language: string | undefined,
|
|
38
52
|
absolutePath: string | undefined,
|
|
39
53
|
isDirectory: boolean,
|
|
40
54
|
): string {
|
|
41
|
-
if (isDirectory) {
|
|
42
|
-
|
|
43
|
-
throw new Error("language is required for directory targets");
|
|
44
|
-
}
|
|
55
|
+
if (isDirectory && (language === undefined || language.length === 0)) {
|
|
56
|
+
throw new Error("language is required for directory targets");
|
|
45
57
|
}
|
|
46
58
|
if (language !== undefined && language.length > 0) {
|
|
47
59
|
const adapter = engine.registry.adapterForId(language);
|
|
48
60
|
if (adapter === undefined) {
|
|
49
61
|
throw new Error(`Unregistered language: ${language}`);
|
|
50
62
|
}
|
|
51
|
-
|
|
52
|
-
throw new Error(`Language does not support structural search: ${language}`);
|
|
53
|
-
}
|
|
63
|
+
requireSearchSupport(adapter.capabilities.search, language);
|
|
54
64
|
if (absolutePath !== undefined && !isDirectory) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
throw new Error(`
|
|
58
|
-
}
|
|
59
|
-
if (byPath.id !== language) {
|
|
60
|
-
throw new Error(`language mismatch: path is ${byPath.id}, requested ${language}`);
|
|
65
|
+
const pathId = pathLanguageId(engine, absolutePath);
|
|
66
|
+
if (pathId !== language) {
|
|
67
|
+
throw new Error(`language mismatch: path is ${pathId}, requested ${language}`);
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
return language;
|
|
@@ -65,14 +72,10 @@ function resolveLanguageId(
|
|
|
65
72
|
if (absolutePath === undefined) {
|
|
66
73
|
throw new Error("language is required");
|
|
67
74
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!byPath.capabilities.search) {
|
|
73
|
-
throw new Error(`Language does not support structural search: ${byPath.id}`);
|
|
74
|
-
}
|
|
75
|
-
return byPath.id;
|
|
75
|
+
const pathId = pathLanguageId(engine, absolutePath);
|
|
76
|
+
const adapter = engine.registry.adapterForId(pathId);
|
|
77
|
+
requireSearchSupport(adapter?.capabilities.search === true, pathId);
|
|
78
|
+
return pathId;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
function innermostEnclosing(
|
|
@@ -116,6 +119,40 @@ function attachEnclosing(
|
|
|
116
119
|
}));
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
async function loadHitMeta(
|
|
123
|
+
engine: ExploreEngine,
|
|
124
|
+
absolutePath: string,
|
|
125
|
+
hitCount: number,
|
|
126
|
+
): Promise<{ ir: FileIr | undefined; parseDegraded: boolean }> {
|
|
127
|
+
if (hitCount === 0) return { ir: undefined, parseDegraded: false };
|
|
128
|
+
try {
|
|
129
|
+
const file = await engine.sourceForFile(absolutePath);
|
|
130
|
+
// Multi-hit files get enclosing scopes; single hits only need parseDegraded.
|
|
131
|
+
return {
|
|
132
|
+
ir: hitCount > 1 ? file.ir : undefined,
|
|
133
|
+
parseDegraded: file.ir.parseDegraded,
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
return { ir: undefined, parseDegraded: false };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function searchHitsInSource(
|
|
141
|
+
engine: ExploreEngine,
|
|
142
|
+
languageId: string,
|
|
143
|
+
source: string,
|
|
144
|
+
pattern: string,
|
|
145
|
+
): Promise<{ hits: AstSearchHit[]; error: string | undefined }> {
|
|
146
|
+
try {
|
|
147
|
+
return { hits: await engine.searchInSource(languageId, source, pattern), error: undefined };
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
// Invalid pattern is global — rethrow.
|
|
151
|
+
if (message.startsWith("Invalid pattern:")) throw error;
|
|
152
|
+
return { hits: [], error: message };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
119
156
|
async function searchOneFile(
|
|
120
157
|
engine: ExploreEngine,
|
|
121
158
|
absolutePath: string,
|
|
@@ -133,43 +170,106 @@ async function searchOneFile(
|
|
|
133
170
|
}
|
|
134
171
|
signal.throwIfAborted();
|
|
135
172
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
173
|
+
const searched = await searchHitsInSource(engine, languageId, source, pattern);
|
|
174
|
+
if (searched.error !== undefined) return { matches: [], error: searched.error };
|
|
175
|
+
|
|
176
|
+
const meta = await loadHitMeta(engine, absolutePath, searched.hits.length);
|
|
177
|
+
return {
|
|
178
|
+
matches: attachEnclosing(absolutePath, searched.hits, meta.ir, meta.parseDegraded, searched.hits.length > 1),
|
|
179
|
+
error: undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type DirectorySearchState = {
|
|
184
|
+
matches: AstSearchMatch[];
|
|
185
|
+
fileErrors: { path: string; message: string }[];
|
|
186
|
+
resultLimitReached: boolean;
|
|
187
|
+
scan: ScanOutcome | undefined;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** `stop` means the walk should end after this file. */
|
|
191
|
+
function absorbDirectoryHits(
|
|
192
|
+
state: DirectorySearchState,
|
|
193
|
+
path: string,
|
|
194
|
+
hits: readonly AstSearchHit[],
|
|
195
|
+
ir: FileIr,
|
|
196
|
+
resultLimit: number,
|
|
197
|
+
): "continue" | "stop" {
|
|
198
|
+
const room = resultLimit - state.matches.length;
|
|
199
|
+
if (room <= 0) {
|
|
200
|
+
if (hits.length > 0) state.resultLimitReached = true;
|
|
201
|
+
return "stop";
|
|
144
202
|
}
|
|
203
|
+
const limited = hits.length > room ? hits.slice(0, room) : hits;
|
|
204
|
+
if (hits.length > room) state.resultLimitReached = true;
|
|
205
|
+
state.matches.push(
|
|
206
|
+
...attachEnclosing(path, limited, hits.length > 1 ? ir : undefined, ir.parseDegraded, hits.length > 1),
|
|
207
|
+
);
|
|
208
|
+
if (state.matches.length >= resultLimit) {
|
|
209
|
+
state.resultLimitReached = true;
|
|
210
|
+
return "stop";
|
|
211
|
+
}
|
|
212
|
+
return "continue";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function scanOutcomeFromStep(step: IteratorResult<FileSource, ScanOutcome>, matchCount: number): ScanOutcome {
|
|
216
|
+
if (step.done) return step.value;
|
|
217
|
+
return {
|
|
218
|
+
limit: undefined,
|
|
219
|
+
filesVisited: 0,
|
|
220
|
+
sourceBytes: 0,
|
|
221
|
+
elapsedMs: 0,
|
|
222
|
+
filesEmitted: matchCount > 0 ? 1 : 0,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function searchDirectory(
|
|
227
|
+
engine: ExploreEngine,
|
|
228
|
+
absolutePath: string,
|
|
229
|
+
languageId: string,
|
|
230
|
+
pattern: string,
|
|
231
|
+
resultLimit: number,
|
|
232
|
+
signal: AbortSignal,
|
|
233
|
+
): Promise<DirectorySearchState> {
|
|
234
|
+
const state: DirectorySearchState = {
|
|
235
|
+
matches: [],
|
|
236
|
+
fileErrors: [],
|
|
237
|
+
resultLimitReached: false,
|
|
238
|
+
scan: undefined,
|
|
239
|
+
};
|
|
145
240
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
241
|
+
const generator = scanSources({
|
|
242
|
+
engine,
|
|
243
|
+
cwd: engine.cwd,
|
|
244
|
+
root: absolutePath,
|
|
245
|
+
signal,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
let step = await generator.next();
|
|
249
|
+
while (!step.done) {
|
|
250
|
+
signal.throwIfAborted();
|
|
251
|
+
const file = step.value;
|
|
252
|
+
if (file.ir.languageId !== languageId) {
|
|
253
|
+
step = await generator.next();
|
|
254
|
+
continue;
|
|
155
255
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// enclosing when disambiguating — single hit does not need it.
|
|
163
|
-
ir = undefined;
|
|
164
|
-
} catch {
|
|
165
|
-
// ignore
|
|
256
|
+
|
|
257
|
+
const searched = await searchHitsInSource(engine, languageId, file.source, pattern);
|
|
258
|
+
if (searched.error !== undefined) {
|
|
259
|
+
state.fileErrors.push({ path: file.ir.path, message: searched.error });
|
|
260
|
+
step = await generator.next();
|
|
261
|
+
continue;
|
|
166
262
|
}
|
|
263
|
+
|
|
264
|
+
if (absorbDirectoryHits(state, file.ir.path, searched.hits, file.ir, resultLimit) === "stop") break;
|
|
265
|
+
step = await generator.next();
|
|
167
266
|
}
|
|
168
267
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
268
|
+
if (signal.aborted) throw new Error("ast_search cancelled");
|
|
269
|
+
const scan = scanOutcomeFromStep(step, state.matches.length);
|
|
270
|
+
if (scan.limit === "cancelled") throw new Error("ast_search cancelled");
|
|
271
|
+
state.scan = scan;
|
|
272
|
+
return state;
|
|
173
273
|
}
|
|
174
274
|
|
|
175
275
|
/**
|
|
@@ -198,109 +298,27 @@ export async function astSearch(
|
|
|
198
298
|
const isDirectory = stats.isDirectory();
|
|
199
299
|
const languageId = resolveLanguageId(engine, language, isDirectory ? undefined : absolutePath, isDirectory);
|
|
200
300
|
|
|
201
|
-
const matches: AstSearchMatch[] = [];
|
|
202
|
-
const fileErrors: { path: string; message: string }[] = [];
|
|
203
|
-
let resultLimitReached = false;
|
|
204
|
-
let scan: ScanOutcome | undefined;
|
|
205
|
-
|
|
206
301
|
if (!isDirectory) {
|
|
207
302
|
const one = await searchOneFile(engine, absolutePath, languageId, pattern, signal);
|
|
208
|
-
if (one.error !== undefined)
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
if (one.matches.length > resultLimit) {
|
|
212
|
-
resultLimitReached = true;
|
|
213
|
-
matches.push(...one.matches.slice(0, resultLimit));
|
|
214
|
-
} else {
|
|
215
|
-
matches.push(...one.matches);
|
|
216
|
-
}
|
|
303
|
+
if (one.error !== undefined) throw new Error(one.error);
|
|
304
|
+
const truncated = one.matches.length > resultLimit;
|
|
217
305
|
return {
|
|
218
|
-
matches,
|
|
306
|
+
matches: truncated ? one.matches.slice(0, resultLimit) : one.matches,
|
|
219
307
|
resultLimit,
|
|
220
|
-
resultLimitReached,
|
|
308
|
+
resultLimitReached: truncated,
|
|
221
309
|
languageId,
|
|
222
310
|
scan: undefined,
|
|
223
|
-
fileErrors,
|
|
311
|
+
fileErrors: [],
|
|
224
312
|
};
|
|
225
313
|
}
|
|
226
314
|
|
|
227
|
-
|
|
228
|
-
const generator = scanSources({
|
|
229
|
-
engine,
|
|
230
|
-
cwd: engine.cwd,
|
|
231
|
-
root: absolutePath,
|
|
232
|
-
signal,
|
|
233
|
-
// scanSources already filters to registered languages; further filter below.
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
let step = await generator.next();
|
|
237
|
-
while (!step.done) {
|
|
238
|
-
signal.throwIfAborted();
|
|
239
|
-
const file = step.value;
|
|
240
|
-
if (file.ir.languageId !== languageId) {
|
|
241
|
-
step = await generator.next();
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Prefer source already loaded by scan (avoids second read for IR).
|
|
246
|
-
let hits: AstSearchHit[];
|
|
247
|
-
try {
|
|
248
|
-
hits = await engine.searchInSource(languageId, file.source, pattern);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
251
|
-
if (message.startsWith("Invalid pattern:")) throw error;
|
|
252
|
-
fileErrors.push({ path: file.ir.path, message });
|
|
253
|
-
step = await generator.next();
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const room = resultLimit - matches.length;
|
|
258
|
-
if (room <= 0) {
|
|
259
|
-
if (hits.length > 0) resultLimitReached = true;
|
|
260
|
-
// Still drain walk for budget outcome? Stop early for latency.
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const limited = hits.length > room ? hits.slice(0, room) : hits;
|
|
265
|
-
if (hits.length > room) resultLimitReached = true;
|
|
266
|
-
|
|
267
|
-
const withScope = attachEnclosing(
|
|
268
|
-
file.ir.path,
|
|
269
|
-
limited,
|
|
270
|
-
hits.length > 1 ? file.ir : undefined,
|
|
271
|
-
file.ir.parseDegraded,
|
|
272
|
-
hits.length > 1,
|
|
273
|
-
);
|
|
274
|
-
matches.push(...withScope);
|
|
275
|
-
|
|
276
|
-
if (matches.length >= resultLimit) {
|
|
277
|
-
resultLimitReached = true;
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
step = await generator.next();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (signal.aborted) throw new Error("ast_search cancelled");
|
|
284
|
-
|
|
285
|
-
if (step.done) {
|
|
286
|
-
scan = step.value;
|
|
287
|
-
} else {
|
|
288
|
-
scan = {
|
|
289
|
-
limit: undefined,
|
|
290
|
-
filesVisited: 0,
|
|
291
|
-
sourceBytes: 0,
|
|
292
|
-
elapsedMs: 0,
|
|
293
|
-
filesEmitted: matches.length > 0 ? 1 : 0,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
if (scan.limit === "cancelled") throw new Error("ast_search cancelled");
|
|
297
|
-
|
|
315
|
+
const directory = await searchDirectory(engine, absolutePath, languageId, pattern, resultLimit, signal);
|
|
298
316
|
return {
|
|
299
|
-
matches,
|
|
317
|
+
matches: directory.matches,
|
|
300
318
|
resultLimit,
|
|
301
|
-
resultLimitReached,
|
|
319
|
+
resultLimitReached: directory.resultLimitReached,
|
|
302
320
|
languageId,
|
|
303
|
-
scan,
|
|
304
|
-
fileErrors,
|
|
321
|
+
scan: directory.scan,
|
|
322
|
+
fileErrors: directory.fileErrors,
|
|
305
323
|
};
|
|
306
324
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pathResolutionError, resolveExplorePath } from "
|
|
1
|
+
import { pathResolutionError, resolveExplorePath } from "../traverse.ts";
|
|
2
2
|
import type { ExploreEngine } from "../engine.ts";
|
|
3
3
|
import type { Candidate } from "../identity.ts";
|
|
4
4
|
import { resolveTarget } from "../identity.ts";
|