@shanepadgett/tau-agent 0.26.0 → 0.28.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/subagents.md +1 -1
- package/extensions/appshot/README.md +1 -1
- package/extensions/appshot/index.ts +5 -11
- package/extensions/context/sync.ts +1 -9
- package/extensions/context-pruning/README.md +2 -2
- package/extensions/context-pruning/index.ts +58 -57
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/context-pruning/render.ts +51 -39
- package/extensions/context-pruning/settings.ts +4 -5
- package/extensions/explore/README.md +13 -14
- 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 +130 -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 +716 -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 +31 -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 +91 -45
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +64 -0
- package/extensions/explore/traverse.ts +260 -86
- package/extensions/handoff/index.ts +1 -1
- package/extensions/patch/executor.ts +95 -1
- package/extensions/patch/index.ts +1 -0
- 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 +30 -14
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +44 -24
- package/extensions/subagent/agents/scout.md +53 -34
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -2
- package/package.json +6 -5
- package/schemas/tau.schema.json +51 -6
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/bounded-text-result.ts +304 -0
- package/shared/events.ts +1 -0
- package/shared/full-file-knowledge.ts +49 -0
- package/shared/temporary-output-store.ts +275 -0
- package/extensions/explore/ast-tools.ts +0 -828
- package/extensions/explore/ast-worker.ts +0 -356
- 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/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 -154
- package/extensions/explore/read-stats.ts +0 -120
- package/extensions/explore/read.ts +0 -335
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
import type { LanguageAdapter, LanguageCapabilities } from "./adapter.ts";
|
|
3
|
+
import { csharpAdapter } from "./languages/csharp.ts";
|
|
4
|
+
import { goAdapter } from "./languages/go.ts";
|
|
5
|
+
import { javaAdapter } from "./languages/java.ts";
|
|
6
|
+
import { kotlinAdapter } from "./languages/kotlin.ts";
|
|
7
|
+
import { odinAdapter } from "./languages/odin.ts";
|
|
8
|
+
import { rustAdapter } from "./languages/rust.ts";
|
|
9
|
+
import { swiftAdapter } from "./languages/swift.ts";
|
|
10
|
+
import { tsxAdapter, typescriptAdapter } from "./languages/typescript.ts";
|
|
11
|
+
import { extractMarkdown } from "./markdown.ts";
|
|
12
|
+
import type { PackageSurfaceResolver } from "./package-surface.ts";
|
|
13
|
+
|
|
14
|
+
export type LanguageAdvertisement = {
|
|
15
|
+
id: string;
|
|
16
|
+
extensions: readonly string[];
|
|
17
|
+
capabilities: LanguageCapabilities;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type AdapterRegistry = {
|
|
21
|
+
register(adapter: LanguageAdapter): void;
|
|
22
|
+
adapterForPath(path: string): LanguageAdapter | undefined;
|
|
23
|
+
adapterForId(languageId: string): LanguageAdapter | undefined;
|
|
24
|
+
registeredLanguages(): LanguageAdvertisement[];
|
|
25
|
+
/** Deduped package-surface resolvers from adapters that declare the capability. */
|
|
26
|
+
packageSurfaceResolvers(): readonly PackageSurfaceResolver[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const MARKDOWN_EXTENSIONS = [".md", ".markdown", ".mdown"] as const;
|
|
30
|
+
|
|
31
|
+
const MARKDOWN_CAPABILITIES: LanguageCapabilities = {
|
|
32
|
+
shape: true,
|
|
33
|
+
search: false,
|
|
34
|
+
fileDeps: false,
|
|
35
|
+
callEdges: false,
|
|
36
|
+
packageSurface: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function markdownAdapter(): LanguageAdapter {
|
|
40
|
+
return {
|
|
41
|
+
mode: "source",
|
|
42
|
+
id: "markdown",
|
|
43
|
+
extensions: MARKDOWN_EXTENSIONS,
|
|
44
|
+
capabilities: MARKDOWN_CAPABILITIES,
|
|
45
|
+
importNoiseIdentifiers: new Set(),
|
|
46
|
+
extract: extractMarkdown,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createRegistry(seed: readonly LanguageAdapter[] = []): AdapterRegistry {
|
|
51
|
+
const byId = new Map<string, LanguageAdapter>();
|
|
52
|
+
const byExtension = new Map<string, LanguageAdapter>();
|
|
53
|
+
|
|
54
|
+
const register = (adapter: LanguageAdapter): void => {
|
|
55
|
+
if (byId.has(adapter.id)) {
|
|
56
|
+
throw new Error(`Language adapter already registered: ${adapter.id}`);
|
|
57
|
+
}
|
|
58
|
+
for (const ext of adapter.extensions) {
|
|
59
|
+
const key = ext.toLowerCase();
|
|
60
|
+
if (!key.startsWith(".")) {
|
|
61
|
+
throw new Error(`Language adapter ${adapter.id}: extension must start with '.': ${ext}`);
|
|
62
|
+
}
|
|
63
|
+
const existing = byExtension.get(key);
|
|
64
|
+
if (existing !== undefined) {
|
|
65
|
+
throw new Error(`Extension ${key} already registered by ${existing.id}; cannot register ${adapter.id}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
byId.set(adapter.id, adapter);
|
|
69
|
+
for (const ext of adapter.extensions) {
|
|
70
|
+
byExtension.set(ext.toLowerCase(), adapter);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
for (const adapter of seed) register(adapter);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
register,
|
|
78
|
+
adapterForPath(path: string): LanguageAdapter | undefined {
|
|
79
|
+
return byExtension.get(extname(path).toLowerCase());
|
|
80
|
+
},
|
|
81
|
+
adapterForId(languageId: string): LanguageAdapter | undefined {
|
|
82
|
+
return byId.get(languageId);
|
|
83
|
+
},
|
|
84
|
+
registeredLanguages(): LanguageAdvertisement[] {
|
|
85
|
+
return [...byId.values()].map((adapter) => ({
|
|
86
|
+
id: adapter.id,
|
|
87
|
+
extensions: adapter.extensions,
|
|
88
|
+
capabilities: adapter.capabilities,
|
|
89
|
+
}));
|
|
90
|
+
},
|
|
91
|
+
packageSurfaceResolvers(): readonly PackageSurfaceResolver[] {
|
|
92
|
+
const out: PackageSurfaceResolver[] = [];
|
|
93
|
+
const seen = new Set<PackageSurfaceResolver>();
|
|
94
|
+
for (const adapter of byId.values()) {
|
|
95
|
+
if (!adapter.capabilities.packageSurface) continue;
|
|
96
|
+
const resolver = adapter.resolvePackageSurface;
|
|
97
|
+
if (resolver === undefined || seen.has(resolver)) continue;
|
|
98
|
+
seen.add(resolver);
|
|
99
|
+
out.push(resolver);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Session registry: Markdown + grammar adapters. */
|
|
107
|
+
export function createDefaultRegistry(): AdapterRegistry {
|
|
108
|
+
return createRegistry([
|
|
109
|
+
markdownAdapter(),
|
|
110
|
+
typescriptAdapter(),
|
|
111
|
+
tsxAdapter(),
|
|
112
|
+
goAdapter(),
|
|
113
|
+
rustAdapter(),
|
|
114
|
+
csharpAdapter(),
|
|
115
|
+
javaAdapter(),
|
|
116
|
+
kotlinAdapter(),
|
|
117
|
+
swiftAdapter(),
|
|
118
|
+
odinAdapter(),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ExploreEngine, FileSource } from "./engine.ts";
|
|
2
|
+
import { DEFAULT_TRAVERSAL_BUDGETS, type TraversalBudgets, type TraversalLimit, walkPaths } from "../traverse.ts";
|
|
3
|
+
|
|
4
|
+
export type ScanOptions = {
|
|
5
|
+
engine: ExploreEngine;
|
|
6
|
+
cwd: string;
|
|
7
|
+
root: string;
|
|
8
|
+
budgets?: Partial<TraversalBudgets>;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
includeHidden?: boolean;
|
|
11
|
+
includeIgnored?: boolean;
|
|
12
|
+
includeNoise?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Structured stop reason — not prose. */
|
|
16
|
+
export type ScanOutcome = {
|
|
17
|
+
limit: TraversalLimit | undefined;
|
|
18
|
+
filesVisited: number;
|
|
19
|
+
sourceBytes: number;
|
|
20
|
+
elapsedMs: number;
|
|
21
|
+
/** Files that produced IR (registered language only). */
|
|
22
|
+
filesEmitted: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Budgeted ignore-aware multi-file IR+bytes production.
|
|
27
|
+
* Yields one FileSource per supported file; cooperative macrotask between files.
|
|
28
|
+
* Generator return value is the structured outcome (limit / counters).
|
|
29
|
+
*/
|
|
30
|
+
export async function* scanSources(options: ScanOptions): AsyncGenerator<FileSource, ScanOutcome> {
|
|
31
|
+
const started = Date.now();
|
|
32
|
+
const maxElapsedMs = options.budgets?.maxElapsedMs ?? DEFAULT_TRAVERSAL_BUDGETS.maxElapsedMs;
|
|
33
|
+
let filesEmitted = 0;
|
|
34
|
+
|
|
35
|
+
const walk = await walkPaths({
|
|
36
|
+
cwd: options.cwd,
|
|
37
|
+
root: options.root,
|
|
38
|
+
filesOnly: true,
|
|
39
|
+
includeHidden: options.includeHidden,
|
|
40
|
+
includeIgnored: options.includeIgnored,
|
|
41
|
+
includeNoise: options.includeNoise,
|
|
42
|
+
budgets: options.budgets,
|
|
43
|
+
signal: options.signal,
|
|
44
|
+
// Budgets count parseable files only — skip binaries/unregistered extensions.
|
|
45
|
+
matchFile: (hit) => options.engine.registry.adapterForPath(hit.absolutePath) !== undefined,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const elapsed = () => Date.now() - started;
|
|
49
|
+
|
|
50
|
+
const finish = (limit: TraversalLimit | undefined): ScanOutcome => ({
|
|
51
|
+
limit: limit ?? walk.limit,
|
|
52
|
+
filesVisited: walk.filesVisited,
|
|
53
|
+
sourceBytes: walk.sourceBytes,
|
|
54
|
+
elapsedMs: elapsed(),
|
|
55
|
+
filesEmitted,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
for (const hit of walk.entries) {
|
|
59
|
+
if (options.signal?.aborted) return finish("cancelled");
|
|
60
|
+
if (elapsed() >= maxElapsedMs) return finish("maxElapsedMs");
|
|
61
|
+
|
|
62
|
+
const source = await options.engine.sourceForFile(hit.absolutePath);
|
|
63
|
+
|
|
64
|
+
// Do not emit work that finished after cancel/budget crossed during sourceForFile.
|
|
65
|
+
if (options.signal?.aborted) return finish("cancelled");
|
|
66
|
+
if (elapsed() >= maxElapsedMs) return finish("maxElapsedMs");
|
|
67
|
+
|
|
68
|
+
filesEmitted += 1;
|
|
69
|
+
yield source;
|
|
70
|
+
await new Promise<void>((resolve) => {
|
|
71
|
+
setImmediate(resolve);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return finish(walk.limit);
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Decl } from "./ir.ts";
|
|
2
|
+
|
|
3
|
+
// All offsets are UTF-16 code units into the decoded source string (ir.ts).
|
|
4
|
+
|
|
5
|
+
/** Decl signature: [startOffset, signatureEndOffset) — no docs, no body. */
|
|
6
|
+
export function signatureText(decl: Decl, source: string): string {
|
|
7
|
+
return source.slice(decl.startOffset, decl.signatureEndOffset);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Attached doc comment text, or undefined when none. */
|
|
11
|
+
export function docsText(decl: Decl, source: string): string | undefined {
|
|
12
|
+
if (decl.docStartOffset === undefined || decl.docEndOffset === undefined) return undefined;
|
|
13
|
+
const text = source.slice(decl.docStartOffset, decl.docEndOffset);
|
|
14
|
+
return text.length === 0 ? undefined : text;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Full declaration span [startOffset, endOffset) — no leading docs. */
|
|
18
|
+
export function declarationText(decl: Decl, source: string): string {
|
|
19
|
+
return source.slice(decl.startOffset, decl.endOffset);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Docs (if any) + signature, joined as in source when docs sit immediately above.
|
|
24
|
+
* When docs exist, uses [docStartOffset, signatureEndOffset); else signature only.
|
|
25
|
+
*/
|
|
26
|
+
export function signatureWithDocsText(decl: Decl, source: string): string {
|
|
27
|
+
if (decl.docStartOffset === undefined || decl.docEndOffset === undefined) {
|
|
28
|
+
return signatureText(decl, source);
|
|
29
|
+
}
|
|
30
|
+
return source.slice(decl.docStartOffset, decl.signatureEndOffset);
|
|
31
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type, type Static } from "typebox";
|
|
3
|
+
import { BoundedTextResultBuilder } from "../../../../shared/bounded-text-result.ts";
|
|
4
|
+
import type { TemporaryOutputStore } from "../../../../shared/temporary-output-store.ts";
|
|
5
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
6
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
7
|
+
import {
|
|
8
|
+
formatAstSearchEmpty,
|
|
9
|
+
formatAstSearchFile,
|
|
10
|
+
formatAstSearchFooters,
|
|
11
|
+
groupAstSearchByFile,
|
|
12
|
+
} from "../format/ast-search.ts";
|
|
13
|
+
import { astSearch } from "../queries/ast-search.ts";
|
|
14
|
+
import { formatPathForDisplay, stripLeadingAt } from "../../traverse.ts";
|
|
15
|
+
import { ExploreCallComponent, renderExploreResult, type ExploreToolDetails } from "./render.ts";
|
|
16
|
+
|
|
17
|
+
const PATTERN_MAX_BYTES = 16 * 1024;
|
|
18
|
+
|
|
19
|
+
const astSearchParams = Type.Object(
|
|
20
|
+
{
|
|
21
|
+
path: Type.String({ description: "File or directory to search" }),
|
|
22
|
+
pattern: Type.String({
|
|
23
|
+
minLength: 1,
|
|
24
|
+
maxLength: PATTERN_MAX_BYTES,
|
|
25
|
+
description:
|
|
26
|
+
"ast-grep pattern (e.g. 'console.log($ARG)', 'foo($$$ARGS)', 'if ($COND) { $$$BODY }'). $NAME binds one node; $$$NAME binds a sibling sequence.",
|
|
27
|
+
}),
|
|
28
|
+
language: Type.Optional(
|
|
29
|
+
Type.String({
|
|
30
|
+
description:
|
|
31
|
+
"Engine-registered language id (typescript, tsx, go, rust, c_sharp, java, kotlin, swift, odin). Required for directories; must match file extension when both given.",
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
resultLimit: Type.Integer({ minimum: 1, maximum: 100 }),
|
|
35
|
+
},
|
|
36
|
+
{ additionalProperties: false },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
type AstSearchParams = Static<typeof astSearchParams>;
|
|
40
|
+
|
|
41
|
+
type AstSearchExecuteResult = {
|
|
42
|
+
content: Array<{ type: "text"; text: string }>;
|
|
43
|
+
details: ExploreToolDetails;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function optionVariants(params: AstSearchParams): string[] {
|
|
47
|
+
const lang = params.language !== undefined && params.language.length > 0 ? params.language : "lang?";
|
|
48
|
+
const limit = params.resultLimit === undefined ? "limit" : `limit=${params.resultLimit}`;
|
|
49
|
+
const pattern =
|
|
50
|
+
params.pattern === undefined
|
|
51
|
+
? "pattern"
|
|
52
|
+
: params.pattern.length > 24
|
|
53
|
+
? `${params.pattern.slice(0, 21)}...`
|
|
54
|
+
: params.pattern;
|
|
55
|
+
return [`[${lang} ${limit} ${pattern}]`];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createAstSearchTool(
|
|
59
|
+
rowState: ToolRowStateStore,
|
|
60
|
+
temporaryOutput: TemporaryOutputStore,
|
|
61
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
62
|
+
) {
|
|
63
|
+
return defineTool<typeof astSearchParams, ExploreToolDetails>({
|
|
64
|
+
name: "ast_search",
|
|
65
|
+
label: "ast_search",
|
|
66
|
+
description:
|
|
67
|
+
"Find code shapes with ast-grep patterns in one file or a tree. Use $NAME for one node and $$$NAME for sibling sequences (e.g. console.log($ARG), foo($$$ARGS)). Search only — no writes. Prefer over grep when structure matters.",
|
|
68
|
+
promptSnippet: "Structural pattern search with ast-grep metavariables",
|
|
69
|
+
promptGuidelines: [
|
|
70
|
+
"Use ast_search for code shapes; harness grep for literal text.",
|
|
71
|
+
"Pass language for directory targets (required). On a file, language is optional when the extension maps to a registered search language.",
|
|
72
|
+
"Patterns follow ast-grep rules: $NAME one node, $$$NAME sequence, $_ anonymous. Keep resultLimit tight.",
|
|
73
|
+
"No rewrite/write path — edits go through harness patch/edit/write.",
|
|
74
|
+
],
|
|
75
|
+
parameters: astSearchParams,
|
|
76
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
77
|
+
const engine = engineFor(ctx.cwd);
|
|
78
|
+
const abort = signal ?? new AbortController().signal;
|
|
79
|
+
const result = await astSearch(
|
|
80
|
+
engine,
|
|
81
|
+
params.path,
|
|
82
|
+
params.pattern,
|
|
83
|
+
params.language,
|
|
84
|
+
params.resultLimit,
|
|
85
|
+
abort,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
89
|
+
try {
|
|
90
|
+
const groups = groupAstSearchByFile(result.matches);
|
|
91
|
+
for (const group of groups) {
|
|
92
|
+
abort.throwIfAborted();
|
|
93
|
+
await builder.appendBlock(
|
|
94
|
+
group.path,
|
|
95
|
+
formatPathForDisplay(group.path, engine.cwd),
|
|
96
|
+
formatAstSearchFile(group.path, group.matches, engine.cwd),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (groups.length === 0) {
|
|
100
|
+
await builder.appendBlock(undefined, "empty", formatAstSearchEmpty());
|
|
101
|
+
}
|
|
102
|
+
const footer = formatAstSearchFooters(result, engine.cwd);
|
|
103
|
+
if (footer !== undefined) {
|
|
104
|
+
abort.throwIfAborted();
|
|
105
|
+
await builder.appendRequiredBlock("ast_search limits", footer);
|
|
106
|
+
}
|
|
107
|
+
const bounded = await builder.finish();
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: bounded.content }],
|
|
110
|
+
details: {
|
|
111
|
+
declarationCount: result.matches.length,
|
|
112
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
113
|
+
truncated: bounded.overflow.truncated,
|
|
114
|
+
},
|
|
115
|
+
} satisfies AstSearchExecuteResult;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
await builder.abort();
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
renderCall(args, theme, context) {
|
|
122
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
123
|
+
const targets = [stripLeadingAt(args.path ?? "")];
|
|
124
|
+
const options = optionVariants(args);
|
|
125
|
+
const component =
|
|
126
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
127
|
+
new ExploreCallComponent(rowState, context.toolCallId, "ast_search", targets, options, theme);
|
|
128
|
+
component.targetVariants = targets;
|
|
129
|
+
component.optionVariants = options;
|
|
130
|
+
component.theme = theme;
|
|
131
|
+
return component;
|
|
132
|
+
},
|
|
133
|
+
renderResult(result, options, theme, context) {
|
|
134
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
135
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { BoundedTextResultBuilder } from "../../../../shared/bounded-text-result.ts";
|
|
4
|
+
import { loadTauExtensionSettings } from "../../../../shared/settings/load.ts";
|
|
5
|
+
import type { TemporaryOutputStore } from "../../../../shared/temporary-output-store.ts";
|
|
6
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
7
|
+
import exploreSettings from "../../settings.ts";
|
|
8
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
9
|
+
import { contextSectionBlocks, formatContextResult } from "../format/context.ts";
|
|
10
|
+
import type { ExploreFileGraph } from "../graph/file-graph.ts";
|
|
11
|
+
import { queryContext } from "../queries/context.ts";
|
|
12
|
+
import { stripLeadingAt } from "../../traverse.ts";
|
|
13
|
+
import { ExploreCallComponent, renderExploreResult, type ExploreToolDetails } from "./render.ts";
|
|
14
|
+
|
|
15
|
+
const contextParams = Type.Object(
|
|
16
|
+
{
|
|
17
|
+
path: Type.String({ description: "Directory scope (repo/package/subtree)" }),
|
|
18
|
+
targetPath: Type.Optional(Type.String({ description: "Defining file when known" })),
|
|
19
|
+
name: Type.String({ minLength: 1, description: "Declaration name; may be dotted (Type.method)" }),
|
|
20
|
+
line: Type.Optional(Type.Integer({ minimum: 1, description: "1-indexed line to pin one candidate" })),
|
|
21
|
+
budget: Type.Optional(
|
|
22
|
+
Type.Integer({
|
|
23
|
+
minimum: 1,
|
|
24
|
+
description: "Token pack budget (default from explore.context.defaultBudgetTokens)",
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
},
|
|
28
|
+
{ additionalProperties: false },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export function createContextTool(
|
|
32
|
+
rowState: ToolRowStateStore,
|
|
33
|
+
temporaryOutput: TemporaryOutputStore,
|
|
34
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
35
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
36
|
+
) {
|
|
37
|
+
return defineTool<typeof contextParams, ExploreToolDetails>({
|
|
38
|
+
name: "context",
|
|
39
|
+
label: "context",
|
|
40
|
+
description:
|
|
41
|
+
"Budgeted pack to understand one symbol in one call: target body, nearby callees/callers or type members/implementors. Raise budget if truncated. Use impact for full blast radius.",
|
|
42
|
+
promptSnippet: "Budgeted read pack for one symbol",
|
|
43
|
+
promptGuidelines: [
|
|
44
|
+
"Use when you need bodies/signatures around one symbol under a token budget.",
|
|
45
|
+
"Raise budget if entries are signature-only or truncated.",
|
|
46
|
+
"Use impact for full dependents list; context is a sample pack.",
|
|
47
|
+
"Callable and type targets only.",
|
|
48
|
+
],
|
|
49
|
+
parameters: contextParams,
|
|
50
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
51
|
+
const engine = engineFor(ctx.cwd);
|
|
52
|
+
const graph = graphFor(ctx.cwd);
|
|
53
|
+
const abort = signal ?? new AbortController().signal;
|
|
54
|
+
const settings = await loadTauExtensionSettings(ctx, exploreSettings);
|
|
55
|
+
const budget = params.budget ?? settings.context.defaultBudgetTokens;
|
|
56
|
+
const result = await queryContext({
|
|
57
|
+
engine,
|
|
58
|
+
graph,
|
|
59
|
+
scopePath: params.path,
|
|
60
|
+
targetPath: params.targetPath,
|
|
61
|
+
name: params.name,
|
|
62
|
+
line: params.line,
|
|
63
|
+
budget,
|
|
64
|
+
signal: abort,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (result.kind !== "resolved") {
|
|
68
|
+
const text = formatContextResult(result, engine.cwd);
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text }],
|
|
71
|
+
details: {
|
|
72
|
+
declarationCount: 0,
|
|
73
|
+
returnedBytes: Buffer.byteLength(text),
|
|
74
|
+
truncated: false,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
80
|
+
try {
|
|
81
|
+
const blocks = contextSectionBlocks(result, engine.cwd);
|
|
82
|
+
for (const block of blocks) {
|
|
83
|
+
abort.throwIfAborted();
|
|
84
|
+
if (block.id === "footer") {
|
|
85
|
+
await builder.appendRequiredBlock(block.label, block.text);
|
|
86
|
+
} else {
|
|
87
|
+
await builder.appendBlock(block.id, block.label, block.text);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const bounded = await builder.finish();
|
|
91
|
+
const declarationCount = result.groups.reduce((sum, group) => sum + group.entries.length, 0);
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: bounded.content }],
|
|
94
|
+
details: {
|
|
95
|
+
declarationCount,
|
|
96
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
97
|
+
truncated: bounded.overflow.truncated || result.truncated,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
await builder.abort();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
renderCall(args, theme, context) {
|
|
106
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
107
|
+
const name = args.name ?? "?";
|
|
108
|
+
const scope = stripLeadingAt(args.path ?? "");
|
|
109
|
+
const pin = args.targetPath !== undefined ? stripLeadingAt(args.targetPath) : "";
|
|
110
|
+
const targets = pin.length > 0 ? [`${name} @ ${pin}`] : [name];
|
|
111
|
+
const budget = args.budget === undefined ? "budget=default" : `budget=${args.budget}`;
|
|
112
|
+
const options = [`[scope=${scope} ${budget}]`];
|
|
113
|
+
const component =
|
|
114
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
115
|
+
new ExploreCallComponent(rowState, context.toolCallId, "context", targets, options, theme);
|
|
116
|
+
component.targetVariants = targets;
|
|
117
|
+
component.optionVariants = options;
|
|
118
|
+
component.theme = theme;
|
|
119
|
+
return component;
|
|
120
|
+
},
|
|
121
|
+
renderResult(result, options, theme, context) {
|
|
122
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
123
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { truncateBoundedHead } from "../../../../shared/bounded-text-result.ts";
|
|
4
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
5
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
6
|
+
import { formatDepsEmpty, formatDepsResult } from "../format/deps.ts";
|
|
7
|
+
import type { ExploreFileGraph } from "../graph/file-graph.ts";
|
|
8
|
+
import { stripLeadingAt } from "../../traverse.ts";
|
|
9
|
+
import { ExploreCallComponent, renderExploreResult, type ExploreToolDetails } from "./render.ts";
|
|
10
|
+
|
|
11
|
+
const depsParams = Type.Object(
|
|
12
|
+
{
|
|
13
|
+
path: Type.String({ description: "Source file" }),
|
|
14
|
+
depth: Type.Optional(Type.Integer({ minimum: 1, description: "Traversal depth (default 1)" })),
|
|
15
|
+
resultLimit: Type.Integer({ minimum: 1, maximum: 100, description: "Max dependent entries to return" }),
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false },
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function createDepsTool(
|
|
21
|
+
rowState: ToolRowStateStore,
|
|
22
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
23
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
24
|
+
) {
|
|
25
|
+
return defineTool<typeof depsParams, ExploreToolDetails>({
|
|
26
|
+
name: "deps",
|
|
27
|
+
label: "deps",
|
|
28
|
+
description:
|
|
29
|
+
"File-level: what this file imports/depends on. Depth > 1 walks the internal import graph (BFS). External packages are listed last.",
|
|
30
|
+
promptSnippet: "List file imports and dependencies",
|
|
31
|
+
promptGuidelines: [
|
|
32
|
+
"Use for file-level dependency orientation, not symbol callees.",
|
|
33
|
+
"Increase depth only when transitive internal imports matter.",
|
|
34
|
+
"Tighten resultLimit when output is large.",
|
|
35
|
+
],
|
|
36
|
+
parameters: depsParams,
|
|
37
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
38
|
+
const engine = engineFor(ctx.cwd);
|
|
39
|
+
const graph = graphFor(ctx.cwd);
|
|
40
|
+
const abort = signal ?? new AbortController().signal;
|
|
41
|
+
const depth = params.depth ?? 1;
|
|
42
|
+
const result = await graph.deps(params.path, depth, params.resultLimit, abort);
|
|
43
|
+
const text = formatDepsResult(result, engine.cwd, formatDepsEmpty());
|
|
44
|
+
const bounded = truncateBoundedHead(text);
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: bounded.content }],
|
|
47
|
+
details: {
|
|
48
|
+
declarationCount: result.hits.length,
|
|
49
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
50
|
+
truncated: bounded.truncated,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
renderCall(args, theme, context) {
|
|
55
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
56
|
+
const targets = [stripLeadingAt(args.path ?? "")];
|
|
57
|
+
const depth = args.depth ?? 1;
|
|
58
|
+
const options = [`[depth=${depth} limit=${args.resultLimit ?? "?"}]`];
|
|
59
|
+
const component =
|
|
60
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
61
|
+
new ExploreCallComponent(rowState, context.toolCallId, "deps", targets, options, theme);
|
|
62
|
+
component.targetVariants = targets;
|
|
63
|
+
component.optionVariants = options;
|
|
64
|
+
component.theme = theme;
|
|
65
|
+
return component;
|
|
66
|
+
},
|
|
67
|
+
renderResult(result, options, theme, context) {
|
|
68
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
69
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|