@shanepadgett/tau-agent 0.27.0 → 0.28.1
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/extensions/context/sync.ts +1 -9
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/explore/README.md +13 -21
- package/extensions/explore/ast/adapter.ts +97 -0
- package/extensions/explore/ast/engine.ts +470 -0
- package/extensions/explore/ast/format/ast-search.ts +97 -0
- package/extensions/explore/ast/format/composite.ts +26 -0
- package/extensions/explore/ast/format/context.ts +66 -0
- package/extensions/explore/ast/format/deps.ts +86 -0
- package/extensions/explore/ast/format/discover.ts +85 -0
- package/extensions/explore/ast/format/impact.ts +105 -0
- package/extensions/explore/ast/format/outline.ts +66 -0
- package/extensions/explore/ast/format/relationships.ts +81 -0
- package/extensions/explore/ast/format/show.ts +38 -0
- package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
- package/extensions/explore/ast/grammars/manifest.json +41 -0
- package/extensions/explore/ast/grammars/manifest.ts +99 -0
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/grammars/swift.wasm +0 -0
- package/extensions/explore/ast/graph/file-graph.ts +360 -0
- package/extensions/explore/ast/graph/relationships.ts +597 -0
- package/extensions/explore/ast/identity.ts +119 -0
- package/extensions/explore/ast/ir.ts +136 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
- package/extensions/explore/ast/languages/csharp.ts +493 -0
- package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
- package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
- package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
- package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
- package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
- package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
- package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
- package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
- package/extensions/explore/ast/languages/go.ts +545 -0
- package/extensions/explore/ast/languages/java.ts +412 -0
- package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
- package/extensions/explore/ast/languages/kotlin.ts +611 -0
- package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
- package/extensions/explore/ast/languages/odin.ts +407 -0
- package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
- package/extensions/explore/ast/languages/rust.ts +506 -0
- package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
- package/extensions/explore/ast/languages/swift.ts +466 -0
- package/extensions/explore/ast/languages/tree.ts +220 -0
- package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
- package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
- package/extensions/explore/ast/languages/typescript.ts +751 -0
- package/extensions/explore/ast/markdown.ts +145 -0
- package/extensions/explore/ast/package-surface.ts +42 -0
- package/extensions/explore/ast/queries/ast-search.ts +306 -0
- package/extensions/explore/ast/queries/composite-target.ts +77 -0
- package/extensions/explore/ast/queries/context.ts +406 -0
- package/extensions/explore/ast/queries/discover.ts +427 -0
- package/extensions/explore/ast/queries/impact.ts +250 -0
- package/extensions/explore/ast/queries/outline.ts +203 -0
- package/extensions/explore/ast/queries/show.ts +262 -0
- package/extensions/explore/ast/query.ts +48 -0
- package/extensions/explore/ast/read/hook.ts +125 -0
- package/extensions/explore/ast/read/policy.ts +55 -0
- package/extensions/explore/ast/registry.ts +120 -0
- package/extensions/explore/ast/scan.ts +76 -0
- package/extensions/explore/ast/slice.ts +44 -0
- package/extensions/explore/ast/tools/ast-search.ts +138 -0
- package/extensions/explore/ast/tools/context.ts +126 -0
- package/extensions/explore/ast/tools/deps.ts +72 -0
- package/extensions/explore/ast/tools/discover.ts +208 -0
- package/extensions/explore/ast/tools/impact.ts +135 -0
- package/extensions/explore/ast/tools/outline.ts +200 -0
- package/extensions/explore/ast/tools/relationships.ts +160 -0
- package/extensions/explore/ast/tools/render.ts +121 -0
- package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
- package/extensions/explore/ast/tools/show.ts +116 -0
- package/extensions/explore/guidance.ts +52 -0
- package/extensions/explore/index.ts +82 -92
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +49 -31
- package/extensions/explore/traverse.ts +254 -91
- package/extensions/handoff/index.ts +1 -1
- package/extensions/publish/index.ts +18 -85
- package/extensions/publish/publish-ui.ts +0 -4
- package/extensions/soul/README.md +7 -4
- package/extensions/soul/index.ts +11 -5
- package/extensions/soul/prompt.ts +31 -13
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +24 -55
- package/extensions/subagent/agents/scout.md +38 -115
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -4
- package/package.json +6 -4
- package/schemas/tau.schema.json +35 -30
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/events.ts +1 -1
- package/shared/full-file-knowledge.ts +49 -0
- package/extensions/explore/ast-guidance.ts +0 -67
- package/extensions/explore/ast-languages.ts +0 -61
- package/extensions/explore/ast-tools.ts +0 -1994
- package/extensions/explore/ast-worker.ts +0 -1025
- package/extensions/explore/find.ts +0 -147
- package/extensions/explore/full-file-knowledge.ts +0 -234
- package/extensions/explore/grep.ts +0 -749
- package/extensions/explore/limits.ts +0 -22
- package/extensions/explore/ls.ts +0 -113
- package/extensions/explore/orientation-state.ts +0 -280
- package/extensions/explore/path-display.ts +0 -41
- package/extensions/explore/path-tree.ts +0 -150
- package/extensions/explore/read-cache.ts +0 -273
- package/extensions/explore/read-snapshots.ts +0 -57
- package/extensions/explore/read-stats-panel.ts +0 -176
- package/extensions/explore/read-stats.ts +0 -138
- package/extensions/explore/read.ts +0 -372
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -1,1025 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { isAstLanguage, type AstLanguage } from "./ast-languages.ts";
|
|
6
|
-
|
|
7
|
-
export type { AstLanguage } from "./ast-languages.ts";
|
|
8
|
-
|
|
9
|
-
export type OutlineTarget =
|
|
10
|
-
| { kind: "file"; path: string; language: AstLanguage }
|
|
11
|
-
| { kind: "directory"; path: string }
|
|
12
|
-
| {
|
|
13
|
-
kind: "recursiveDirectory";
|
|
14
|
-
path: string;
|
|
15
|
-
budgets: RecursiveOutlineBudgets;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export interface RecursiveOutlineBudgets {
|
|
19
|
-
maxFiles: number;
|
|
20
|
-
maxSourceBytes: number;
|
|
21
|
-
maxDepth: number;
|
|
22
|
-
maxElapsedMs: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const RECURSIVE_OUTLINE_BUDGETS: RecursiveOutlineBudgets = {
|
|
26
|
-
maxFiles: 2000,
|
|
27
|
-
maxSourceBytes: 64 * 1024 * 1024,
|
|
28
|
-
maxDepth: 32,
|
|
29
|
-
maxElapsedMs: 20_000,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export interface SourcePosition {
|
|
33
|
-
line: number;
|
|
34
|
-
column: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SourceRange {
|
|
38
|
-
startByte: number;
|
|
39
|
-
endByte: number;
|
|
40
|
-
start: SourcePosition;
|
|
41
|
-
end: SourcePosition;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface OutlineEntry {
|
|
45
|
-
role: "item" | "member";
|
|
46
|
-
symbolType: string;
|
|
47
|
-
name: string;
|
|
48
|
-
qualifiedName: string;
|
|
49
|
-
range: SourceRange;
|
|
50
|
-
nameRange: SourceRange;
|
|
51
|
-
receiverRange?: SourceRange;
|
|
52
|
-
bodyRange?: SourceRange;
|
|
53
|
-
signature: string;
|
|
54
|
-
astKind: string;
|
|
55
|
-
certainty: "certain" | "recovered" | "nearRecovery";
|
|
56
|
-
certaintyReason?: string;
|
|
57
|
-
locator?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type SymbolView = "signature" | "signatureWithDocs" | "declaration" | "declarationWithImports";
|
|
61
|
-
|
|
62
|
-
export interface OutlineItem extends OutlineEntry {
|
|
63
|
-
rowKind: "package" | "import" | "declaration" | "export" | "sideEffect";
|
|
64
|
-
isImport: boolean;
|
|
65
|
-
isExported: boolean;
|
|
66
|
-
members: Array<OutlineEntry & { isPublic: boolean }>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface OutlineFileResult {
|
|
70
|
-
path: string;
|
|
71
|
-
language: AstLanguage;
|
|
72
|
-
sourceFingerprint: string;
|
|
73
|
-
byteLength: number;
|
|
74
|
-
lineCount: number;
|
|
75
|
-
diagnostics: { errorNodes: number; missingNodes: number };
|
|
76
|
-
items: OutlineItem[];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface OutlineTargetResult {
|
|
80
|
-
path: string;
|
|
81
|
-
files: OutlineFileResult[];
|
|
82
|
-
totalByteLength: number;
|
|
83
|
-
totalLineCount: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface RecursiveOutlineDiagnostic {
|
|
87
|
-
relativePath: string;
|
|
88
|
-
language: AstLanguage | undefined;
|
|
89
|
-
code: string;
|
|
90
|
-
message: string;
|
|
91
|
-
sourceFingerprint: string | undefined;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface RecursiveOutlineSummary {
|
|
95
|
-
discoveredFiles: number;
|
|
96
|
-
supportedFiles: number;
|
|
97
|
-
unsupportedFiles: number;
|
|
98
|
-
emittedFiles: number;
|
|
99
|
-
unreadableFiles: number;
|
|
100
|
-
oversizedFiles: number;
|
|
101
|
-
failedFiles: number;
|
|
102
|
-
parserDegradedFiles: number;
|
|
103
|
-
totalByteLength: number;
|
|
104
|
-
totalLineCount: number;
|
|
105
|
-
fileLimitReached: boolean;
|
|
106
|
-
sourceByteLimitReached: boolean;
|
|
107
|
-
depthLimitReached: boolean;
|
|
108
|
-
elapsedLimitReached: boolean;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface RecursiveOutlineCallbacks {
|
|
112
|
-
onFile(relativePath: string, file: OutlineFileResult): Promise<void>;
|
|
113
|
-
onDiagnostic(diagnostic: RecursiveOutlineDiagnostic): Promise<void>;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface SymbolDeclaration {
|
|
117
|
-
locator: string;
|
|
118
|
-
path: string;
|
|
119
|
-
language: AstLanguage;
|
|
120
|
-
sourceFingerprint: string;
|
|
121
|
-
declarationRange: SourceRange;
|
|
122
|
-
diagnostics: string[];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface SymbolBlock {
|
|
126
|
-
path: string;
|
|
127
|
-
returnedRange: SourceRange;
|
|
128
|
-
declarationIndexes: number[];
|
|
129
|
-
source: string;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface SymbolBatchResult {
|
|
133
|
-
declarations: SymbolDeclaration[];
|
|
134
|
-
blocks: SymbolBlock[];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export type ApiDeclarationKind =
|
|
138
|
-
| "module"
|
|
139
|
-
| "namespace"
|
|
140
|
-
| "package"
|
|
141
|
-
| "class"
|
|
142
|
-
| "method"
|
|
143
|
-
| "property"
|
|
144
|
-
| "field"
|
|
145
|
-
| "constructor"
|
|
146
|
-
| "enum"
|
|
147
|
-
| "interface"
|
|
148
|
-
| "function"
|
|
149
|
-
| "variable"
|
|
150
|
-
| "constant"
|
|
151
|
-
| "object"
|
|
152
|
-
| "enumMember"
|
|
153
|
-
| "struct"
|
|
154
|
-
| "event"
|
|
155
|
-
| "operator"
|
|
156
|
-
| "typeParameter"
|
|
157
|
-
| "heading";
|
|
158
|
-
|
|
159
|
-
export type ApiQuery =
|
|
160
|
-
| { kind: "exactName"; name: string }
|
|
161
|
-
| { kind: "prefixName"; name: string }
|
|
162
|
-
| { kind: "substringName"; name: string }
|
|
163
|
-
| { kind: "fuzzyName"; name: string; maxCandidates: number; maxWork: number }
|
|
164
|
-
| { kind: "declarationKind"; declarationKind: ApiDeclarationKind }
|
|
165
|
-
| { kind: "documentation"; terms: string[]; maxCandidates: number; maxWork: number };
|
|
166
|
-
|
|
167
|
-
export type ApiSurfaceFilter = "all" | "public" | "private" | "sourceExport" | "packageSurface";
|
|
168
|
-
|
|
169
|
-
export interface ApiCandidate {
|
|
170
|
-
locator: string;
|
|
171
|
-
language: AstLanguage;
|
|
172
|
-
sourceFingerprint: string;
|
|
173
|
-
name: string;
|
|
174
|
-
qualifiedName: string;
|
|
175
|
-
symbolType: ApiDeclarationKind;
|
|
176
|
-
signature: string;
|
|
177
|
-
definingFile: string;
|
|
178
|
-
range: SourceRange;
|
|
179
|
-
visibility: "public" | "protected" | "internal" | "packagePrivate" | "filePrivate" | "private" | "unknown";
|
|
180
|
-
sourceExport: "yes" | "no" | "unknown";
|
|
181
|
-
packageSurface: "yes" | "no" | "unknown";
|
|
182
|
-
internalOnly: "yes" | "no" | "unknown";
|
|
183
|
-
reExportChain: string[];
|
|
184
|
-
callerAccess: {
|
|
185
|
-
modulePath: string;
|
|
186
|
-
importStatement: string;
|
|
187
|
-
accessExpression: string;
|
|
188
|
-
form: "direct" | "qualified";
|
|
189
|
-
} | null;
|
|
190
|
-
provenance: "exact" | "inferred" | "ambiguous" | "unsupported";
|
|
191
|
-
certainty: "certain" | "recovered" | "nearRecovery";
|
|
192
|
-
certaintyReason: string | null;
|
|
193
|
-
uncertainty: string | null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export interface ApiDiscoverySummary {
|
|
197
|
-
filesScanned: number;
|
|
198
|
-
declarationsConsidered: number;
|
|
199
|
-
resultsReturned: number;
|
|
200
|
-
resultLimit: number;
|
|
201
|
-
omittedCandidates: number;
|
|
202
|
-
candidateLimitReached: boolean;
|
|
203
|
-
workLimitReached: boolean;
|
|
204
|
-
resolutionDiagnostics: number;
|
|
205
|
-
totalSourceBytes: number;
|
|
206
|
-
fileLimitReached: boolean;
|
|
207
|
-
sourceByteLimitReached: boolean;
|
|
208
|
-
depthLimitReached: boolean;
|
|
209
|
-
elapsedLimitReached: boolean;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface ApiDiscoveryResult {
|
|
213
|
-
path: string;
|
|
214
|
-
candidates: ApiCandidate[];
|
|
215
|
-
summary: ApiDiscoverySummary;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export interface AstSearchBindingValue {
|
|
219
|
-
range: SourceRange;
|
|
220
|
-
preview: string;
|
|
221
|
-
previewTruncated: boolean;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export interface AstSearchBinding {
|
|
225
|
-
name: string;
|
|
226
|
-
values: AstSearchBindingValue[];
|
|
227
|
-
valuesTruncated: boolean;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export interface AstSearchScope {
|
|
231
|
-
astKind: string;
|
|
232
|
-
range: SourceRange;
|
|
233
|
-
preview: string;
|
|
234
|
-
previewTruncated: boolean;
|
|
235
|
-
locator: string;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export interface AstSearchMatch {
|
|
239
|
-
relativePath: string;
|
|
240
|
-
language: AstLanguage;
|
|
241
|
-
sourceFingerprint: string;
|
|
242
|
-
range: SourceRange;
|
|
243
|
-
preview: string;
|
|
244
|
-
previewTruncated: boolean;
|
|
245
|
-
bindings: AstSearchBinding[];
|
|
246
|
-
bindingsTruncated: boolean;
|
|
247
|
-
certainty: "certain" | "recovered" | "nearRecovery";
|
|
248
|
-
certaintyReason?: string;
|
|
249
|
-
locator: string;
|
|
250
|
-
enclosingScope?: AstSearchScope;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export interface AstSearchSummary {
|
|
254
|
-
filesDiscovered: number;
|
|
255
|
-
filesFiltered: number;
|
|
256
|
-
languageFilteredFiles: number;
|
|
257
|
-
literalFilteredFiles: number;
|
|
258
|
-
filesRead: number;
|
|
259
|
-
filesParsed: number;
|
|
260
|
-
filesSearched: number;
|
|
261
|
-
unreadableFiles: number;
|
|
262
|
-
oversizedFiles: number;
|
|
263
|
-
failedFiles: number;
|
|
264
|
-
parserDegradedFiles: number;
|
|
265
|
-
sourceBytes: number;
|
|
266
|
-
matchesFound: number;
|
|
267
|
-
matchesReturned: number;
|
|
268
|
-
resultLimit: number;
|
|
269
|
-
resultLimitReached: boolean;
|
|
270
|
-
literalPrefilterApplied: boolean;
|
|
271
|
-
potentialKindPrefilterApplied: boolean;
|
|
272
|
-
diagnosticsOmitted: number;
|
|
273
|
-
fileLimitReached: boolean;
|
|
274
|
-
sourceByteLimitReached: boolean;
|
|
275
|
-
depthLimitReached: boolean;
|
|
276
|
-
elapsedLimitReached: boolean;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export interface AstSearchResult {
|
|
280
|
-
path: string;
|
|
281
|
-
language: AstLanguage;
|
|
282
|
-
pattern: string;
|
|
283
|
-
targetSourceFingerprint: string | null;
|
|
284
|
-
matches: AstSearchMatch[];
|
|
285
|
-
diagnostics: Array<{ relativePath: string; code: string; message: string }>;
|
|
286
|
-
summary: AstSearchSummary;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export type RelationshipOperation = "references" | "callers" | "callees" | "implementations" | "tests";
|
|
290
|
-
|
|
291
|
-
export interface EditableScope {
|
|
292
|
-
locator: string;
|
|
293
|
-
language: AstLanguage;
|
|
294
|
-
kind: string;
|
|
295
|
-
qualifiedIdentity: string;
|
|
296
|
-
range: SourceRange;
|
|
297
|
-
bodyRange: SourceRange | null;
|
|
298
|
-
sourceFingerprint: string;
|
|
299
|
-
certainty: "certain" | "recovered" | "nearRecovery";
|
|
300
|
-
certaintyReason: string | null;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export interface RelationshipLocation {
|
|
304
|
-
relativePath: string;
|
|
305
|
-
language: AstLanguage;
|
|
306
|
-
sourceFingerprint: string;
|
|
307
|
-
range: SourceRange;
|
|
308
|
-
relationshipKind:
|
|
309
|
-
| "reference"
|
|
310
|
-
| "typeUsage"
|
|
311
|
-
| "caller"
|
|
312
|
-
| "callee"
|
|
313
|
-
| "implementation"
|
|
314
|
-
| "override"
|
|
315
|
-
| "reExport"
|
|
316
|
-
| "test";
|
|
317
|
-
certainty: "exact" | "inferred" | "ambiguous";
|
|
318
|
-
parseCertainty: "certain" | "recovered" | "nearRecovery";
|
|
319
|
-
certaintyReason: string | null;
|
|
320
|
-
classification: "production" | "test" | "generated" | "reExport";
|
|
321
|
-
targetLocator: string;
|
|
322
|
-
targetPath: string;
|
|
323
|
-
targetSourceFingerprint: string;
|
|
324
|
-
candidateLocators: string[];
|
|
325
|
-
candidatePaths: string[];
|
|
326
|
-
candidateSourceFingerprints: string[];
|
|
327
|
-
competingCandidatesOmitted: number;
|
|
328
|
-
actionable: boolean;
|
|
329
|
-
sitePreview: string;
|
|
330
|
-
sitePreviewTruncated: boolean;
|
|
331
|
-
enclosingScope: EditableScope;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export interface RelationshipResult {
|
|
335
|
-
path: string;
|
|
336
|
-
operation: RelationshipOperation;
|
|
337
|
-
targetName: string;
|
|
338
|
-
targetLocator: string;
|
|
339
|
-
relationships: RelationshipLocation[];
|
|
340
|
-
summary: {
|
|
341
|
-
filesScanned: number;
|
|
342
|
-
sourceBytes: number;
|
|
343
|
-
parserDegradedFiles: number;
|
|
344
|
-
relationshipsFound: number;
|
|
345
|
-
relationshipsReturned: number;
|
|
346
|
-
resultLimit: number;
|
|
347
|
-
resultLimitReached: boolean;
|
|
348
|
-
ambiguousRelationships: number;
|
|
349
|
-
diagnostics: number;
|
|
350
|
-
fileLimitReached: boolean;
|
|
351
|
-
sourceByteLimitReached: boolean;
|
|
352
|
-
depthLimitReached: boolean;
|
|
353
|
-
elapsedLimitReached: boolean;
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export type EditOperation =
|
|
358
|
-
| { kind: "replaceDeclaration"; source: string }
|
|
359
|
-
| { kind: "replaceBody"; body: string }
|
|
360
|
-
| { kind: "insertDeclaration"; position: "before" | "after"; source: string }
|
|
361
|
-
| {
|
|
362
|
-
kind: "renameDeclaration";
|
|
363
|
-
newName: string;
|
|
364
|
-
scope: { kind: "file" } | { kind: "repository"; path: string };
|
|
365
|
-
includeInferred: boolean;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
export interface PlannedEdit {
|
|
369
|
-
range: SourceRange;
|
|
370
|
-
replacement: string;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export interface EditFilePlan {
|
|
374
|
-
path: string;
|
|
375
|
-
expectedFingerprint: string;
|
|
376
|
-
source: string;
|
|
377
|
-
edits: PlannedEdit[];
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export interface EditPlanResult {
|
|
381
|
-
files: EditFilePlan[];
|
|
382
|
-
skippedImpacts: Array<{
|
|
383
|
-
path: string;
|
|
384
|
-
range: SourceRange;
|
|
385
|
-
reason: "ambiguous" | "uncertainParse" | "inferredNotApproved";
|
|
386
|
-
candidateLocators: string[];
|
|
387
|
-
candidatePaths: string[];
|
|
388
|
-
}>;
|
|
389
|
-
freshLocators: Array<{
|
|
390
|
-
locator: string;
|
|
391
|
-
path: string;
|
|
392
|
-
name: string;
|
|
393
|
-
sourceFingerprint: string;
|
|
394
|
-
}>;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export interface AstClient {
|
|
398
|
-
getGeneration(): number;
|
|
399
|
-
outline(
|
|
400
|
-
target: OutlineTarget,
|
|
401
|
-
includePrivate: boolean,
|
|
402
|
-
includeDocs: boolean,
|
|
403
|
-
names: string[],
|
|
404
|
-
signal: AbortSignal | undefined,
|
|
405
|
-
): Promise<OutlineTargetResult>;
|
|
406
|
-
outlineRecursive(
|
|
407
|
-
path: string,
|
|
408
|
-
includePrivate: boolean,
|
|
409
|
-
includeDocs: boolean,
|
|
410
|
-
names: string[],
|
|
411
|
-
callbacks: RecursiveOutlineCallbacks,
|
|
412
|
-
signal: AbortSignal | undefined,
|
|
413
|
-
): Promise<RecursiveOutlineSummary>;
|
|
414
|
-
symbol(
|
|
415
|
-
locators: string[],
|
|
416
|
-
view: SymbolView,
|
|
417
|
-
contextLines: number,
|
|
418
|
-
signal: AbortSignal | undefined,
|
|
419
|
-
): Promise<SymbolBatchResult>;
|
|
420
|
-
discoverApi(
|
|
421
|
-
path: string,
|
|
422
|
-
query: ApiQuery,
|
|
423
|
-
surface: ApiSurfaceFilter,
|
|
424
|
-
resultLimit: number,
|
|
425
|
-
signal: AbortSignal | undefined,
|
|
426
|
-
): Promise<ApiDiscoveryResult>;
|
|
427
|
-
search(
|
|
428
|
-
path: string,
|
|
429
|
-
language: AstLanguage,
|
|
430
|
-
pattern: string,
|
|
431
|
-
resultLimit: number,
|
|
432
|
-
signal: AbortSignal | undefined,
|
|
433
|
-
): Promise<AstSearchResult>;
|
|
434
|
-
relationships(
|
|
435
|
-
path: string,
|
|
436
|
-
locator: string,
|
|
437
|
-
relationship: RelationshipOperation,
|
|
438
|
-
resultLimit: number,
|
|
439
|
-
signal: AbortSignal | undefined,
|
|
440
|
-
): Promise<RelationshipResult>;
|
|
441
|
-
planEdit(locator: string, edit: EditOperation, signal: AbortSignal | undefined): Promise<EditPlanResult>;
|
|
442
|
-
shutdown(): Promise<void>;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
type WorkerRequestPayload =
|
|
446
|
-
| { operation: "handshake" }
|
|
447
|
-
| {
|
|
448
|
-
operation: "outline";
|
|
449
|
-
target: OutlineTarget;
|
|
450
|
-
includePrivate: boolean;
|
|
451
|
-
includeDocs: boolean;
|
|
452
|
-
names: string[];
|
|
453
|
-
}
|
|
454
|
-
| {
|
|
455
|
-
operation: "symbol";
|
|
456
|
-
locators: string[];
|
|
457
|
-
view: SymbolView;
|
|
458
|
-
contextLines: number;
|
|
459
|
-
}
|
|
460
|
-
| {
|
|
461
|
-
operation: "apiDiscover";
|
|
462
|
-
path: string;
|
|
463
|
-
budgets: RecursiveOutlineBudgets;
|
|
464
|
-
query: ApiQuery;
|
|
465
|
-
surface: ApiSurfaceFilter;
|
|
466
|
-
resultLimit: number;
|
|
467
|
-
}
|
|
468
|
-
| {
|
|
469
|
-
operation: "astSearch";
|
|
470
|
-
path: string;
|
|
471
|
-
language: AstLanguage;
|
|
472
|
-
budgets: RecursiveOutlineBudgets;
|
|
473
|
-
pattern: string;
|
|
474
|
-
resultLimit: number;
|
|
475
|
-
}
|
|
476
|
-
| {
|
|
477
|
-
operation: "relationships";
|
|
478
|
-
path: string;
|
|
479
|
-
budgets: RecursiveOutlineBudgets;
|
|
480
|
-
locator: string;
|
|
481
|
-
relationship: RelationshipOperation;
|
|
482
|
-
resultLimit: number;
|
|
483
|
-
}
|
|
484
|
-
| {
|
|
485
|
-
operation: "planEdit";
|
|
486
|
-
locator: string;
|
|
487
|
-
edit: EditOperation;
|
|
488
|
-
budgets: RecursiveOutlineBudgets;
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
interface WorkerResponse {
|
|
492
|
-
requestId: number;
|
|
493
|
-
protocolVersion: number;
|
|
494
|
-
success: boolean;
|
|
495
|
-
result?: Record<string, unknown>;
|
|
496
|
-
error?: { code?: string; message?: string; sourceFingerprint?: string };
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
export class AstWorkerError extends Error {
|
|
500
|
-
readonly code: string;
|
|
501
|
-
readonly sourceFingerprint: string | undefined;
|
|
502
|
-
|
|
503
|
-
constructor(code: string, message: string, sourceFingerprint: string | undefined = undefined) {
|
|
504
|
-
super(message);
|
|
505
|
-
this.name = "AstWorkerError";
|
|
506
|
-
this.code = code;
|
|
507
|
-
this.sourceFingerprint = sourceFingerprint;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
interface PendingUnaryRequest {
|
|
512
|
-
kind: "unary";
|
|
513
|
-
resolve(value: Record<string, unknown>): void;
|
|
514
|
-
reject(error: Error): void;
|
|
515
|
-
removeAbortListener(): void;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
interface PendingStreamRequest {
|
|
519
|
-
kind: "recursiveOutline";
|
|
520
|
-
started: boolean;
|
|
521
|
-
callbacks: RecursiveOutlineCallbacks;
|
|
522
|
-
resolve(value: RecursiveOutlineSummary): void;
|
|
523
|
-
reject(error: Error): void;
|
|
524
|
-
removeAbortListener(): void;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
type PendingRequest = PendingUnaryRequest | PendingStreamRequest;
|
|
528
|
-
|
|
529
|
-
const PROTOCOL_VERSION = 13;
|
|
530
|
-
const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
531
|
-
const STDERR_BYTES = 16 * 1024;
|
|
532
|
-
const HANDSHAKE_TIMEOUT_MS = 2000;
|
|
533
|
-
|
|
534
|
-
export type AstWorkerResolution = { command: string } | { error: Error };
|
|
535
|
-
|
|
536
|
-
export function resolveAstWorkerCommand(
|
|
537
|
-
packageRoot: string,
|
|
538
|
-
platform: NodeJS.Platform,
|
|
539
|
-
arch: string,
|
|
540
|
-
): AstWorkerResolution {
|
|
541
|
-
const packagedCommand = join(packageRoot, "native-bin", "darwin-arm64", "tau-ast");
|
|
542
|
-
if (platform === "darwin" && arch === "arm64" && existsSync(packagedCommand)) return { command: packagedCommand };
|
|
543
|
-
|
|
544
|
-
const sourceRoot = join(packageRoot, "native", "tau-ast");
|
|
545
|
-
if (existsSync(join(sourceRoot, "Cargo.toml")))
|
|
546
|
-
return {
|
|
547
|
-
command: join(sourceRoot, "target", "release", `tau-ast${platform === "win32" ? ".exe" : ""}`),
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
if (platform === "darwin" && arch === "arm64")
|
|
551
|
-
return {
|
|
552
|
-
error: new Error(
|
|
553
|
-
"tau-ast is missing from this @shanepadgett/tau-agent installation. Reinstall the package before using api_discover, ast_search, outline, or symbol.",
|
|
554
|
-
),
|
|
555
|
-
};
|
|
556
|
-
return {
|
|
557
|
-
error: new Error(
|
|
558
|
-
`Packaged AST tools currently require an Apple Silicon Mac (darwin-arm64); this host is ${platform}-${arch}.`,
|
|
559
|
-
),
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function resolveDefaultAstWorkerCommand(): AstWorkerResolution {
|
|
564
|
-
return resolveAstWorkerCommand(fileURLToPath(new URL("../../", import.meta.url)), process.platform, process.arch);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
export class AstWorkerClient implements AstClient {
|
|
568
|
-
private readonly command: string | undefined;
|
|
569
|
-
private readonly args: readonly string[];
|
|
570
|
-
private child: ChildProcessWithoutNullStreams | undefined;
|
|
571
|
-
private startPromise: Promise<void> | undefined;
|
|
572
|
-
private pending = new Map<number, PendingRequest>();
|
|
573
|
-
private nextRequestId = 1;
|
|
574
|
-
private incoming = Buffer.alloc(0);
|
|
575
|
-
private readonly drainingChildren = new Set<ChildProcessWithoutNullStreams>();
|
|
576
|
-
private stderr = "";
|
|
577
|
-
private generation = 0;
|
|
578
|
-
private capabilities: readonly AstLanguage[] | undefined;
|
|
579
|
-
|
|
580
|
-
constructor(command: string | undefined = undefined, args: readonly string[] = []) {
|
|
581
|
-
this.command = command;
|
|
582
|
-
this.args = args;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
getGeneration(): number {
|
|
586
|
-
return this.generation;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
private resolveCommand(): AstWorkerResolution {
|
|
590
|
-
return this.command ? { command: this.command } : resolveDefaultAstWorkerCommand();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
async supportedLanguages(): Promise<readonly AstLanguage[]> {
|
|
594
|
-
await this.ensureStarted();
|
|
595
|
-
if (!this.capabilities) throw new Error("tau-ast handshake omitted supported languages");
|
|
596
|
-
return this.capabilities;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async outline(
|
|
600
|
-
target: OutlineTarget,
|
|
601
|
-
includePrivate: boolean,
|
|
602
|
-
includeDocs: boolean,
|
|
603
|
-
names: string[],
|
|
604
|
-
signal: AbortSignal | undefined,
|
|
605
|
-
): Promise<OutlineTargetResult> {
|
|
606
|
-
const result = await this.request({ operation: "outline", target, includePrivate, includeDocs, names }, signal);
|
|
607
|
-
if (result.kind !== "outline") throw new Error("tau-ast returned the wrong result for outline");
|
|
608
|
-
return result as unknown as OutlineTargetResult;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
async outlineRecursive(
|
|
612
|
-
path: string,
|
|
613
|
-
includePrivate: boolean,
|
|
614
|
-
includeDocs: boolean,
|
|
615
|
-
names: string[],
|
|
616
|
-
callbacks: RecursiveOutlineCallbacks,
|
|
617
|
-
signal: AbortSignal | undefined,
|
|
618
|
-
): Promise<RecursiveOutlineSummary> {
|
|
619
|
-
await this.ensureStarted();
|
|
620
|
-
const target: OutlineTarget = {
|
|
621
|
-
kind: "recursiveDirectory",
|
|
622
|
-
path,
|
|
623
|
-
budgets: RECURSIVE_OUTLINE_BUDGETS,
|
|
624
|
-
};
|
|
625
|
-
return this.sendRecursive(
|
|
626
|
-
{ operation: "outline", target, includePrivate, includeDocs, names },
|
|
627
|
-
callbacks,
|
|
628
|
-
signal,
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
async symbol(
|
|
633
|
-
locators: string[],
|
|
634
|
-
view: SymbolView,
|
|
635
|
-
contextLines: number,
|
|
636
|
-
signal: AbortSignal | undefined,
|
|
637
|
-
): Promise<SymbolBatchResult> {
|
|
638
|
-
const result = await this.request({ operation: "symbol", locators, view, contextLines }, signal);
|
|
639
|
-
if (result.kind !== "symbol") throw new Error("tau-ast returned the wrong result for symbol");
|
|
640
|
-
return result as unknown as SymbolBatchResult;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
async discoverApi(
|
|
644
|
-
path: string,
|
|
645
|
-
query: ApiQuery,
|
|
646
|
-
surface: ApiSurfaceFilter,
|
|
647
|
-
resultLimit: number,
|
|
648
|
-
signal: AbortSignal | undefined,
|
|
649
|
-
): Promise<ApiDiscoveryResult> {
|
|
650
|
-
const result = await this.request(
|
|
651
|
-
{ operation: "apiDiscover", path, budgets: RECURSIVE_OUTLINE_BUDGETS, query, surface, resultLimit },
|
|
652
|
-
signal,
|
|
653
|
-
);
|
|
654
|
-
if (result.kind !== "apiDiscovery") throw new Error("tau-ast returned the wrong result for API discovery");
|
|
655
|
-
return result as unknown as ApiDiscoveryResult;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
async search(
|
|
659
|
-
path: string,
|
|
660
|
-
language: AstLanguage,
|
|
661
|
-
pattern: string,
|
|
662
|
-
resultLimit: number,
|
|
663
|
-
signal: AbortSignal | undefined,
|
|
664
|
-
): Promise<AstSearchResult> {
|
|
665
|
-
const result = await this.request(
|
|
666
|
-
{ operation: "astSearch", path, language, budgets: RECURSIVE_OUTLINE_BUDGETS, pattern, resultLimit },
|
|
667
|
-
signal,
|
|
668
|
-
);
|
|
669
|
-
if (result.kind !== "astSearch") throw new Error("tau-ast returned the wrong result for ast_search");
|
|
670
|
-
return result as unknown as AstSearchResult;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
async relationships(
|
|
674
|
-
path: string,
|
|
675
|
-
locator: string,
|
|
676
|
-
relationship: RelationshipOperation,
|
|
677
|
-
resultLimit: number,
|
|
678
|
-
signal: AbortSignal | undefined,
|
|
679
|
-
): Promise<RelationshipResult> {
|
|
680
|
-
const result = await this.request(
|
|
681
|
-
{ operation: "relationships", path, budgets: RECURSIVE_OUTLINE_BUDGETS, locator, relationship, resultLimit },
|
|
682
|
-
signal,
|
|
683
|
-
);
|
|
684
|
-
if (result.kind !== "relationships") throw new Error("tau-ast returned the wrong result for relationships");
|
|
685
|
-
return result as unknown as RelationshipResult;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
async planEdit(locator: string, edit: EditOperation, signal: AbortSignal | undefined): Promise<EditPlanResult> {
|
|
689
|
-
const result = await this.request(
|
|
690
|
-
{ operation: "planEdit", locator, edit, budgets: RECURSIVE_OUTLINE_BUDGETS },
|
|
691
|
-
signal,
|
|
692
|
-
);
|
|
693
|
-
if (result.kind !== "editPlan") throw new Error("tau-ast returned the wrong result for edit planning");
|
|
694
|
-
return result as unknown as EditPlanResult;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
async shutdown(): Promise<void> {
|
|
698
|
-
const child = this.child;
|
|
699
|
-
if (!child) return;
|
|
700
|
-
this.child = undefined;
|
|
701
|
-
this.startPromise = undefined;
|
|
702
|
-
this.capabilities = undefined;
|
|
703
|
-
this.rejectPending(new Error("tau-ast worker shut down"));
|
|
704
|
-
child.stdin.end();
|
|
705
|
-
if (child.exitCode !== null) return;
|
|
706
|
-
await Promise.race([
|
|
707
|
-
new Promise<void>((resolve) => child.once("exit", () => resolve())),
|
|
708
|
-
new Promise<void>((resolve) => setTimeout(resolve, 250)),
|
|
709
|
-
]);
|
|
710
|
-
if (child.exitCode === null) child.kill();
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
private async request(
|
|
714
|
-
request: WorkerRequestPayload,
|
|
715
|
-
signal: AbortSignal | undefined,
|
|
716
|
-
): Promise<Record<string, unknown>> {
|
|
717
|
-
await this.ensureStarted();
|
|
718
|
-
return this.send(request, signal);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
private async ensureStarted(): Promise<void> {
|
|
722
|
-
if (this.startPromise) {
|
|
723
|
-
await this.startPromise;
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
if (this.child) return;
|
|
727
|
-
this.startPromise ??= this.start().finally(() => {
|
|
728
|
-
this.startPromise = undefined;
|
|
729
|
-
});
|
|
730
|
-
await this.startPromise;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
private async start(): Promise<void> {
|
|
734
|
-
const resolution = this.resolveCommand();
|
|
735
|
-
if ("error" in resolution) throw resolution.error;
|
|
736
|
-
const child = spawn(resolution.command, this.args, {
|
|
737
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
738
|
-
});
|
|
739
|
-
this.generation += 1;
|
|
740
|
-
this.child = child;
|
|
741
|
-
this.incoming = Buffer.alloc(0);
|
|
742
|
-
this.stderr = "";
|
|
743
|
-
child.stdout.on("data", (chunk: Buffer) => this.receive(child, chunk));
|
|
744
|
-
child.stderr.on("data", (chunk: Buffer) => {
|
|
745
|
-
this.stderr = Buffer.from(`${this.stderr}${chunk.toString("utf8")}`)
|
|
746
|
-
.subarray(-STDERR_BYTES)
|
|
747
|
-
.toString("utf8");
|
|
748
|
-
});
|
|
749
|
-
child.on("error", (error) => this.fail(child, new Error(`Failed to start tau-ast: ${error.message}`)));
|
|
750
|
-
child.on("exit", (code, signal) => {
|
|
751
|
-
if (this.child !== child) return;
|
|
752
|
-
const suffix = this.stderr.trim() ? `: ${this.stderr.trim()}` : "";
|
|
753
|
-
this.fail(child, new Error(`tau-ast exited (${signal ?? code ?? "unknown"})${suffix}`), false);
|
|
754
|
-
});
|
|
755
|
-
const handshakeTimeout = setTimeout(
|
|
756
|
-
() => this.fail(child, new Error(`tau-ast handshake timed out after ${HANDSHAKE_TIMEOUT_MS}ms`)),
|
|
757
|
-
HANDSHAKE_TIMEOUT_MS,
|
|
758
|
-
);
|
|
759
|
-
try {
|
|
760
|
-
const result = await this.send({ operation: "handshake" }, undefined);
|
|
761
|
-
if (result.kind !== "handshake") throw new Error("tau-ast handshake returned the wrong result");
|
|
762
|
-
if (!Array.isArray(result.supportedLanguages)) {
|
|
763
|
-
throw new Error("tau-ast handshake omitted supported languages");
|
|
764
|
-
}
|
|
765
|
-
this.capabilities = result.supportedLanguages.filter(isAstLanguage);
|
|
766
|
-
} catch (error) {
|
|
767
|
-
const failure = error instanceof Error ? error : new Error(String(error));
|
|
768
|
-
this.fail(child, failure);
|
|
769
|
-
throw failure;
|
|
770
|
-
} finally {
|
|
771
|
-
clearTimeout(handshakeTimeout);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
private send(request: WorkerRequestPayload, signal: AbortSignal | undefined): Promise<Record<string, unknown>> {
|
|
776
|
-
return this.sendPending(request, signal, (resolve, reject, removeAbortListener) => ({
|
|
777
|
-
kind: "unary",
|
|
778
|
-
resolve,
|
|
779
|
-
reject,
|
|
780
|
-
removeAbortListener,
|
|
781
|
-
}));
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
private sendRecursive(
|
|
785
|
-
request: WorkerRequestPayload,
|
|
786
|
-
callbacks: RecursiveOutlineCallbacks,
|
|
787
|
-
signal: AbortSignal | undefined,
|
|
788
|
-
): Promise<RecursiveOutlineSummary> {
|
|
789
|
-
return this.sendPending(request, signal, (resolve, reject, removeAbortListener) => ({
|
|
790
|
-
kind: "recursiveOutline",
|
|
791
|
-
started: false,
|
|
792
|
-
callbacks,
|
|
793
|
-
resolve,
|
|
794
|
-
reject,
|
|
795
|
-
removeAbortListener,
|
|
796
|
-
}));
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
private sendPending<T>(
|
|
800
|
-
request: WorkerRequestPayload,
|
|
801
|
-
signal: AbortSignal | undefined,
|
|
802
|
-
createPending: (
|
|
803
|
-
resolve: (value: T) => void,
|
|
804
|
-
reject: (error: Error) => void,
|
|
805
|
-
removeAbortListener: () => void,
|
|
806
|
-
) => PendingRequest,
|
|
807
|
-
): Promise<T> {
|
|
808
|
-
const child = this.child;
|
|
809
|
-
if (!child) return Promise.reject(new Error("tau-ast worker is not running"));
|
|
810
|
-
if (signal?.aborted) return Promise.reject(new Error("tau-ast request cancelled"));
|
|
811
|
-
const requestId = this.nextRequestId++;
|
|
812
|
-
const payload = Buffer.from(JSON.stringify({ ...request, requestId, protocolVersion: PROTOCOL_VERSION }));
|
|
813
|
-
const frame = Buffer.allocUnsafe(payload.length + 4);
|
|
814
|
-
frame.writeUInt32BE(payload.length, 0);
|
|
815
|
-
payload.copy(frame, 4);
|
|
816
|
-
|
|
817
|
-
return new Promise<T>((resolve, reject) => {
|
|
818
|
-
const onAbort = (): void => this.fail(child, new Error("tau-ast request cancelled"));
|
|
819
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
820
|
-
this.pending.set(
|
|
821
|
-
requestId,
|
|
822
|
-
createPending(resolve, reject, () => signal?.removeEventListener("abort", onAbort)),
|
|
823
|
-
);
|
|
824
|
-
child.stdin.write(frame, (error) => {
|
|
825
|
-
if (error) this.fail(child, new Error(`Failed to write tau-ast request: ${error.message}`));
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
private receive(child: ChildProcessWithoutNullStreams, chunk: Buffer): void {
|
|
831
|
-
if (this.child !== child) return;
|
|
832
|
-
this.incoming = Buffer.concat([this.incoming, chunk]);
|
|
833
|
-
void this.drain(child);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
private async drain(child: ChildProcessWithoutNullStreams): Promise<void> {
|
|
837
|
-
if (this.drainingChildren.has(child) || this.child !== child) return;
|
|
838
|
-
this.drainingChildren.add(child);
|
|
839
|
-
try {
|
|
840
|
-
while (this.child === child && this.incoming.length >= 4) {
|
|
841
|
-
const length = this.incoming.readUInt32BE(0);
|
|
842
|
-
if (length > MAX_FRAME_BYTES) {
|
|
843
|
-
this.fail(child, new Error(`tau-ast response frame exceeds ${MAX_FRAME_BYTES} bytes`));
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
if (this.incoming.length < length + 4) return;
|
|
847
|
-
const payload = this.incoming.subarray(4, length + 4);
|
|
848
|
-
this.incoming = this.incoming.subarray(length + 4);
|
|
849
|
-
let response: WorkerResponse;
|
|
850
|
-
try {
|
|
851
|
-
response = JSON.parse(payload.toString("utf8")) as WorkerResponse;
|
|
852
|
-
} catch (error) {
|
|
853
|
-
this.fail(child, new Error(`tau-ast returned malformed JSON: ${String(error)}`));
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
const pending = this.pending.get(response.requestId);
|
|
857
|
-
if (!pending) {
|
|
858
|
-
this.fail(child, new Error(`tau-ast returned unknown request id ${String(response.requestId)}`));
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
if (response.protocolVersion !== PROTOCOL_VERSION) {
|
|
862
|
-
const error = new Error(`tau-ast response used protocol ${response.protocolVersion}`);
|
|
863
|
-
this.fail(child, error);
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
if (!response.success) {
|
|
867
|
-
this.pending.delete(response.requestId);
|
|
868
|
-
pending.removeAbortListener();
|
|
869
|
-
pending.reject(
|
|
870
|
-
new AstWorkerError(
|
|
871
|
-
response.error?.code ?? "worker_error",
|
|
872
|
-
response.error?.message ?? response.error?.code ?? "tau-ast request failed",
|
|
873
|
-
response.error?.sourceFingerprint,
|
|
874
|
-
),
|
|
875
|
-
);
|
|
876
|
-
continue;
|
|
877
|
-
}
|
|
878
|
-
if (!response.result || typeof response.result !== "object") {
|
|
879
|
-
this.fail(child, new Error("tau-ast response omitted its result"));
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
if (pending.kind === "unary") {
|
|
883
|
-
this.pending.delete(response.requestId);
|
|
884
|
-
pending.removeAbortListener();
|
|
885
|
-
pending.resolve(response.result);
|
|
886
|
-
continue;
|
|
887
|
-
}
|
|
888
|
-
const kind = response.result.kind;
|
|
889
|
-
if (kind === "recursiveStart") {
|
|
890
|
-
if (pending.started || typeof response.result.path !== "string" || !isRecord(response.result.budgets)) {
|
|
891
|
-
this.fail(child, new Error("tau-ast recursive outline returned more than one start frame"));
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
pending.started = true;
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
if (!pending.started) {
|
|
898
|
-
this.fail(child, new Error("tau-ast recursive outline omitted its start frame"));
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
if (kind === "recursiveFile") {
|
|
902
|
-
const file = parseRecursiveFile(response.result);
|
|
903
|
-
if (!file) {
|
|
904
|
-
this.fail(child, new Error("tau-ast recursive file frame is malformed"));
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
await pending.callbacks.onFile(response.result.relativePath as string, file);
|
|
908
|
-
if (this.child !== child) return;
|
|
909
|
-
continue;
|
|
910
|
-
}
|
|
911
|
-
if (kind === "recursiveDiagnostic") {
|
|
912
|
-
const diagnostic = parseRecursiveDiagnostic(response.result);
|
|
913
|
-
if (!diagnostic) {
|
|
914
|
-
this.fail(child, new Error("tau-ast recursive diagnostic frame is malformed"));
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
await pending.callbacks.onDiagnostic(diagnostic);
|
|
918
|
-
if (this.child !== child) return;
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
if (kind === "recursiveComplete") {
|
|
922
|
-
const summary = parseRecursiveSummary(response.result);
|
|
923
|
-
if (!summary) {
|
|
924
|
-
this.fail(child, new Error("tau-ast recursive completion frame is malformed"));
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
this.pending.delete(response.requestId);
|
|
928
|
-
pending.removeAbortListener();
|
|
929
|
-
pending.resolve(summary);
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
this.fail(child, new Error(`tau-ast recursive outline returned unexpected frame ${String(kind)}`));
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
} catch (error) {
|
|
936
|
-
this.fail(child, error instanceof Error ? error : new Error(String(error)));
|
|
937
|
-
} finally {
|
|
938
|
-
this.drainingChildren.delete(child);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
private fail(child: ChildProcessWithoutNullStreams, error: Error, kill = true): void {
|
|
943
|
-
if (this.child !== child) return;
|
|
944
|
-
this.child = undefined;
|
|
945
|
-
this.generation += 1;
|
|
946
|
-
this.incoming = Buffer.alloc(0);
|
|
947
|
-
this.capabilities = undefined;
|
|
948
|
-
this.rejectPending(error);
|
|
949
|
-
if (kill && child.exitCode === null) child.kill();
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
private rejectPending(error: Error): void {
|
|
953
|
-
for (const request of this.pending.values()) {
|
|
954
|
-
request.removeAbortListener();
|
|
955
|
-
request.reject(error);
|
|
956
|
-
}
|
|
957
|
-
this.pending.clear();
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
function parseRecursiveFile(result: Record<string, unknown>): OutlineFileResult | undefined {
|
|
962
|
-
if (typeof result.relativePath !== "string" || !isRecord(result.file)) return undefined;
|
|
963
|
-
const file = result.file;
|
|
964
|
-
if (
|
|
965
|
-
typeof file.path !== "string" ||
|
|
966
|
-
!isAstLanguage(file.language) ||
|
|
967
|
-
typeof file.sourceFingerprint !== "string" ||
|
|
968
|
-
!isCount(file.byteLength) ||
|
|
969
|
-
!isCount(file.lineCount) ||
|
|
970
|
-
!isRecord(file.diagnostics) ||
|
|
971
|
-
!isCount(file.diagnostics.errorNodes) ||
|
|
972
|
-
!isCount(file.diagnostics.missingNodes) ||
|
|
973
|
-
!Array.isArray(file.items)
|
|
974
|
-
) {
|
|
975
|
-
return undefined;
|
|
976
|
-
}
|
|
977
|
-
return file as unknown as OutlineFileResult;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
function parseRecursiveDiagnostic(result: Record<string, unknown>): RecursiveOutlineDiagnostic | undefined {
|
|
981
|
-
if (
|
|
982
|
-
typeof result.relativePath !== "string" ||
|
|
983
|
-
typeof result.code !== "string" ||
|
|
984
|
-
typeof result.message !== "string" ||
|
|
985
|
-
(result.language !== undefined && !isAstLanguage(result.language)) ||
|
|
986
|
-
(result.sourceFingerprint !== undefined && typeof result.sourceFingerprint !== "string")
|
|
987
|
-
) {
|
|
988
|
-
return undefined;
|
|
989
|
-
}
|
|
990
|
-
return {
|
|
991
|
-
relativePath: result.relativePath,
|
|
992
|
-
language: result.language as AstLanguage | undefined,
|
|
993
|
-
code: result.code,
|
|
994
|
-
message: result.message,
|
|
995
|
-
sourceFingerprint: result.sourceFingerprint as string | undefined,
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
function parseRecursiveSummary(result: Record<string, unknown>): RecursiveOutlineSummary | undefined {
|
|
1000
|
-
const counts = [
|
|
1001
|
-
"discoveredFiles",
|
|
1002
|
-
"supportedFiles",
|
|
1003
|
-
"unsupportedFiles",
|
|
1004
|
-
"emittedFiles",
|
|
1005
|
-
"unreadableFiles",
|
|
1006
|
-
"oversizedFiles",
|
|
1007
|
-
"failedFiles",
|
|
1008
|
-
"parserDegradedFiles",
|
|
1009
|
-
"totalByteLength",
|
|
1010
|
-
"totalLineCount",
|
|
1011
|
-
] as const;
|
|
1012
|
-
const limits = ["fileLimitReached", "sourceByteLimitReached", "depthLimitReached", "elapsedLimitReached"] as const;
|
|
1013
|
-
if (counts.some((name) => !isCount(result[name])) || limits.some((name) => typeof result[name] !== "boolean")) {
|
|
1014
|
-
return undefined;
|
|
1015
|
-
}
|
|
1016
|
-
return result as unknown as RecursiveOutlineSummary;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1020
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
function isCount(value: unknown): value is number {
|
|
1024
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1025
|
-
}
|