@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,208 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Type, type Static } from "typebox";
|
|
4
|
+
import { BoundedTextResultBuilder } from "../../../../shared/bounded-text-result.ts";
|
|
5
|
+
import type { TemporaryOutputStore } from "../../../../shared/temporary-output-store.ts";
|
|
6
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
7
|
+
import type { DeclKind } from "../ir.ts";
|
|
8
|
+
import {
|
|
9
|
+
formatDiscoverEmpty,
|
|
10
|
+
formatDiscoverFile,
|
|
11
|
+
formatDiscoverFooters,
|
|
12
|
+
groupDiscoverByFile,
|
|
13
|
+
} from "../format/discover.ts";
|
|
14
|
+
import { discover, type DiscoverQuery, type DiscoverSurface } from "../queries/discover.ts";
|
|
15
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
16
|
+
import { formatPathForDisplay, stripLeadingAt } from "../../traverse.ts";
|
|
17
|
+
import { ExploreCallComponent, renderExploreResult, shrinkingListVariants, type ExploreToolDetails } from "./render.ts";
|
|
18
|
+
|
|
19
|
+
const declarationKinds = [
|
|
20
|
+
"module",
|
|
21
|
+
"namespace",
|
|
22
|
+
"package",
|
|
23
|
+
"class",
|
|
24
|
+
"method",
|
|
25
|
+
"property",
|
|
26
|
+
"field",
|
|
27
|
+
"constructor",
|
|
28
|
+
"enum",
|
|
29
|
+
"interface",
|
|
30
|
+
"type",
|
|
31
|
+
"typeAlias",
|
|
32
|
+
"function",
|
|
33
|
+
"variable",
|
|
34
|
+
"constant",
|
|
35
|
+
"object",
|
|
36
|
+
"enumMember",
|
|
37
|
+
"struct",
|
|
38
|
+
"event",
|
|
39
|
+
"operator",
|
|
40
|
+
"typeParameter",
|
|
41
|
+
"heading",
|
|
42
|
+
] as const satisfies readonly DeclKind[];
|
|
43
|
+
|
|
44
|
+
const discoverParams = Type.Object(
|
|
45
|
+
{
|
|
46
|
+
path: Type.String({ description: "Repository, package, or subtree directory" }),
|
|
47
|
+
query: Type.Union([
|
|
48
|
+
Type.Object(
|
|
49
|
+
{ kind: Type.Literal("exactName"), name: Type.String({ minLength: 1 }) },
|
|
50
|
+
{ additionalProperties: false },
|
|
51
|
+
),
|
|
52
|
+
Type.Object(
|
|
53
|
+
{ kind: Type.Literal("prefixName"), name: Type.String({ minLength: 1 }) },
|
|
54
|
+
{ additionalProperties: false },
|
|
55
|
+
),
|
|
56
|
+
Type.Object(
|
|
57
|
+
{ kind: Type.Literal("substringName"), name: Type.String({ minLength: 1 }) },
|
|
58
|
+
{ additionalProperties: false },
|
|
59
|
+
),
|
|
60
|
+
Type.Object(
|
|
61
|
+
{
|
|
62
|
+
kind: Type.Literal("fuzzyName"),
|
|
63
|
+
name: Type.String({ minLength: 1 }),
|
|
64
|
+
maxCandidates: Type.Integer({ minimum: 1, maximum: 10_000 }),
|
|
65
|
+
maxWork: Type.Integer({ minimum: 1, maximum: 1_000_000 }),
|
|
66
|
+
},
|
|
67
|
+
{ additionalProperties: false },
|
|
68
|
+
),
|
|
69
|
+
Type.Object(
|
|
70
|
+
{
|
|
71
|
+
kind: Type.Literal("declarationKind"),
|
|
72
|
+
declarationKind: StringEnum(declarationKinds),
|
|
73
|
+
},
|
|
74
|
+
{ additionalProperties: false },
|
|
75
|
+
),
|
|
76
|
+
Type.Object(
|
|
77
|
+
{
|
|
78
|
+
kind: Type.Literal("documentation"),
|
|
79
|
+
terms: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
80
|
+
maxCandidates: Type.Integer({ minimum: 1, maximum: 10_000 }),
|
|
81
|
+
maxWork: Type.Integer({ minimum: 1, maximum: 1_000_000 }),
|
|
82
|
+
},
|
|
83
|
+
{ additionalProperties: false },
|
|
84
|
+
),
|
|
85
|
+
]),
|
|
86
|
+
surface: StringEnum(["all", "public", "private", "sourceExport", "packageSurface"] as const, {
|
|
87
|
+
description: "Declaration or export surface to search",
|
|
88
|
+
}),
|
|
89
|
+
resultLimit: Type.Integer({ minimum: 1, maximum: 100 }),
|
|
90
|
+
},
|
|
91
|
+
{ additionalProperties: false },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
type DiscoverParams = Static<typeof discoverParams>;
|
|
95
|
+
|
|
96
|
+
type DiscoverExecuteResult = {
|
|
97
|
+
content: Array<{ type: "text"; text: string }>;
|
|
98
|
+
details: ExploreToolDetails;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function formatQueryLabel(query: DiscoverParams["query"]): string {
|
|
102
|
+
switch (query.kind) {
|
|
103
|
+
case "exactName":
|
|
104
|
+
case "prefixName":
|
|
105
|
+
case "substringName":
|
|
106
|
+
case "fuzzyName":
|
|
107
|
+
return `${query.kind}=${query.name}`;
|
|
108
|
+
case "declarationKind":
|
|
109
|
+
return `kind=${query.declarationKind}`;
|
|
110
|
+
case "documentation":
|
|
111
|
+
return `docs=${query.terms.join(",")}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function discoverOptionVariants(params: DiscoverParams): string[] {
|
|
116
|
+
const query = params.query;
|
|
117
|
+
const queryLabels =
|
|
118
|
+
query === undefined
|
|
119
|
+
? ["query"]
|
|
120
|
+
: query.kind === "documentation"
|
|
121
|
+
? shrinkingListVariants(query.terms, `${query.terms.length} terms`).map((part) => `docs=${part}`)
|
|
122
|
+
: [formatQueryLabel(query)];
|
|
123
|
+
const surface = params.surface ?? "surface";
|
|
124
|
+
const limit = params.resultLimit === undefined ? "limit" : `limit=${params.resultLimit}`;
|
|
125
|
+
return queryLabels.map((label) => `[${label} ${surface} ${limit}]`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function createDiscoverTool(
|
|
129
|
+
rowState: ToolRowStateStore,
|
|
130
|
+
temporaryOutput: TemporaryOutputStore,
|
|
131
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
132
|
+
) {
|
|
133
|
+
return defineTool<typeof discoverParams, ExploreToolDetails>({
|
|
134
|
+
name: "discover",
|
|
135
|
+
label: "discover",
|
|
136
|
+
description:
|
|
137
|
+
"Find reusable declarations across a repository, package, or subtree by name, kind, or documentation. Signatures only — no bodies. Prefer package/public surfaces when looking for imports.",
|
|
138
|
+
promptSnippet: "Find reusable declarations across a repo/package/subtree when path is unknown",
|
|
139
|
+
promptGuidelines: [
|
|
140
|
+
"Use discover when reuse intent is known but the declaration path or exact name is not.",
|
|
141
|
+
"Choose exactly one query kind. Keep fuzzy or documentation work limits narrow.",
|
|
142
|
+
"Use packageSurface when the caller needs a supported public import path (languages with packageSurface capability).",
|
|
143
|
+
"Follow up with show using path+name(+line) when a candidate needs closer inspection.",
|
|
144
|
+
],
|
|
145
|
+
parameters: discoverParams,
|
|
146
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
147
|
+
const engine = engineFor(ctx.cwd);
|
|
148
|
+
const abort = signal ?? new AbortController().signal;
|
|
149
|
+
const result = await discover(
|
|
150
|
+
engine,
|
|
151
|
+
params.path,
|
|
152
|
+
params.query as DiscoverQuery,
|
|
153
|
+
params.surface as DiscoverSurface,
|
|
154
|
+
params.resultLimit,
|
|
155
|
+
abort,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
159
|
+
try {
|
|
160
|
+
const groups = groupDiscoverByFile(result.candidates);
|
|
161
|
+
for (const group of groups) {
|
|
162
|
+
abort.throwIfAborted();
|
|
163
|
+
await builder.appendBlock(
|
|
164
|
+
group.path,
|
|
165
|
+
formatPathForDisplay(group.path, engine.cwd),
|
|
166
|
+
formatDiscoverFile(group.path, group.candidates, engine.cwd),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (groups.length === 0) {
|
|
170
|
+
await builder.appendBlock(undefined, "empty", formatDiscoverEmpty());
|
|
171
|
+
}
|
|
172
|
+
const footer = formatDiscoverFooters(result);
|
|
173
|
+
if (footer !== undefined) {
|
|
174
|
+
abort.throwIfAborted();
|
|
175
|
+
await builder.appendRequiredBlock("discover limits", footer);
|
|
176
|
+
}
|
|
177
|
+
const bounded = await builder.finish();
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: bounded.content }],
|
|
180
|
+
details: {
|
|
181
|
+
declarationCount: result.candidates.length,
|
|
182
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
183
|
+
truncated: bounded.overflow.truncated,
|
|
184
|
+
},
|
|
185
|
+
} satisfies DiscoverExecuteResult;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
await builder.abort();
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
renderCall(args, theme, context) {
|
|
192
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
193
|
+
const targets = [stripLeadingAt(args.path ?? "")];
|
|
194
|
+
const options = discoverOptionVariants(args);
|
|
195
|
+
const component =
|
|
196
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
197
|
+
new ExploreCallComponent(rowState, context.toolCallId, "discover", targets, options, theme);
|
|
198
|
+
component.targetVariants = targets;
|
|
199
|
+
component.optionVariants = options;
|
|
200
|
+
component.theme = theme;
|
|
201
|
+
return component;
|
|
202
|
+
},
|
|
203
|
+
renderResult(result, options, theme, context) {
|
|
204
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
205
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { BoundedTextResultBuilder } from "../../../../shared/bounded-text-result.ts";
|
|
5
|
+
import type { TemporaryOutputStore } from "../../../../shared/temporary-output-store.ts";
|
|
6
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
7
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
8
|
+
import { formatImpactResult, impactSectionBlocks } from "../format/impact.ts";
|
|
9
|
+
import type { ExploreFileGraph } from "../graph/file-graph.ts";
|
|
10
|
+
import { IMPACT_DEPTH_DEFAULT, IMPACT_DEPTH_MAX, queryImpact, type ImpactMode } from "../queries/impact.ts";
|
|
11
|
+
import { stripLeadingAt } from "../../traverse.ts";
|
|
12
|
+
import { ExploreCallComponent, renderExploreResult, type ExploreToolDetails } from "./render.ts";
|
|
13
|
+
|
|
14
|
+
const impactParams = Type.Object(
|
|
15
|
+
{
|
|
16
|
+
path: Type.String({ description: "Directory scope (repo/package/subtree)" }),
|
|
17
|
+
targetPath: Type.Optional(Type.String({ description: "Defining file when known" })),
|
|
18
|
+
name: Type.String({ minLength: 1, description: "Declaration name; may be dotted (Type.method)" }),
|
|
19
|
+
line: Type.Optional(Type.Integer({ minimum: 1, description: "1-indexed line to pin one candidate" })),
|
|
20
|
+
depth: Type.Optional(
|
|
21
|
+
Type.Integer({
|
|
22
|
+
minimum: 1,
|
|
23
|
+
maximum: IMPACT_DEPTH_MAX,
|
|
24
|
+
description: `File-transitive reverse depth (default ${IMPACT_DEPTH_DEFAULT}, max ${IMPACT_DEPTH_MAX}). Symbol hops are always 1.`,
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
mode: Type.Optional(
|
|
28
|
+
StringEnum(["all", "deps", "dependents"] as const, {
|
|
29
|
+
description: "all = both sides; deps = callees+imports; dependents = callers+importers+transitive",
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
{ additionalProperties: false },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export function createImpactTool(
|
|
37
|
+
rowState: ToolRowStateStore,
|
|
38
|
+
temporaryOutput: TemporaryOutputStore,
|
|
39
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
40
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
41
|
+
) {
|
|
42
|
+
return defineTool<typeof impactParams, ExploreToolDetails>({
|
|
43
|
+
name: "impact",
|
|
44
|
+
label: "impact",
|
|
45
|
+
description:
|
|
46
|
+
"One-call blast radius for a symbol: callees, callers, file imports, file importers, transitive file dependents. Narrow path scope. depth is file-transitive only.",
|
|
47
|
+
promptSnippet: "Blast radius before a non-trivial edit",
|
|
48
|
+
promptGuidelines: [
|
|
49
|
+
"Use before a non-trivial edit to see symbol and nearby file dependents.",
|
|
50
|
+
"Narrow path scope — whole-repo impact is not the goal.",
|
|
51
|
+
"depth applies only to reverse file dependents (not multi-hop symbol callers).",
|
|
52
|
+
"mode=deps for outbound only; mode=dependents for inbound only.",
|
|
53
|
+
"Callable and type targets only.",
|
|
54
|
+
],
|
|
55
|
+
parameters: impactParams,
|
|
56
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
57
|
+
const engine = engineFor(ctx.cwd);
|
|
58
|
+
const graph = graphFor(ctx.cwd);
|
|
59
|
+
const abort = signal ?? new AbortController().signal;
|
|
60
|
+
const depth = params.depth ?? IMPACT_DEPTH_DEFAULT;
|
|
61
|
+
const mode = (params.mode ?? "all") as ImpactMode;
|
|
62
|
+
const result = await queryImpact({
|
|
63
|
+
engine,
|
|
64
|
+
graph,
|
|
65
|
+
scopePath: params.path,
|
|
66
|
+
targetPath: params.targetPath,
|
|
67
|
+
name: params.name,
|
|
68
|
+
line: params.line,
|
|
69
|
+
depth,
|
|
70
|
+
mode,
|
|
71
|
+
signal: abort,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (result.kind !== "resolved") {
|
|
75
|
+
const text = formatImpactResult(result, engine.cwd);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text }],
|
|
78
|
+
details: {
|
|
79
|
+
declarationCount: 0,
|
|
80
|
+
returnedBytes: Buffer.byteLength(text),
|
|
81
|
+
truncated: false,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
87
|
+
try {
|
|
88
|
+
const blocks = impactSectionBlocks(result, engine.cwd);
|
|
89
|
+
let count = 0;
|
|
90
|
+
for (const block of blocks) {
|
|
91
|
+
abort.throwIfAborted();
|
|
92
|
+
if (block.id === "footer") {
|
|
93
|
+
await builder.appendRequiredBlock(block.label, block.text);
|
|
94
|
+
} else {
|
|
95
|
+
await builder.appendBlock(block.id, block.label, block.text);
|
|
96
|
+
}
|
|
97
|
+
count += 1;
|
|
98
|
+
}
|
|
99
|
+
const bounded = await builder.finish();
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: bounded.content }],
|
|
102
|
+
details: {
|
|
103
|
+
declarationCount: count,
|
|
104
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
105
|
+
truncated: bounded.overflow.truncated,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
await builder.abort();
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
renderCall(args, theme, context) {
|
|
114
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
115
|
+
const name = args.name ?? "?";
|
|
116
|
+
const scope = stripLeadingAt(args.path ?? "");
|
|
117
|
+
const pin = args.targetPath !== undefined ? stripLeadingAt(args.targetPath) : "";
|
|
118
|
+
const targets = pin.length > 0 ? [`${name} @ ${pin}`] : [name];
|
|
119
|
+
const depth = args.depth ?? IMPACT_DEPTH_DEFAULT;
|
|
120
|
+
const mode = args.mode ?? "all";
|
|
121
|
+
const options = [`[scope=${scope} depth=${depth} mode=${mode}]`];
|
|
122
|
+
const component =
|
|
123
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
124
|
+
new ExploreCallComponent(rowState, context.toolCallId, "impact", targets, options, theme);
|
|
125
|
+
component.targetVariants = targets;
|
|
126
|
+
component.optionVariants = options;
|
|
127
|
+
component.theme = theme;
|
|
128
|
+
return component;
|
|
129
|
+
},
|
|
130
|
+
renderResult(result, options, theme, context) {
|
|
131
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
132
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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 { formatOutlineBudgetFooter, formatOutlineEmpty, formatOutlineFile } from "../format/outline.ts";
|
|
8
|
+
import { outlinePath, outlineRecursive, type OutlineFileView, type OutlineOptions } from "../queries/outline.ts";
|
|
9
|
+
import { formatPathForDisplay, stripLeadingAt } from "../../traverse.ts";
|
|
10
|
+
import { ExploreCallComponent, renderExploreResult, shrinkingListVariants, type ExploreToolDetails } from "./render.ts";
|
|
11
|
+
|
|
12
|
+
const outlineParams = Type.Object(
|
|
13
|
+
{
|
|
14
|
+
path: Type.String({
|
|
15
|
+
description: "Supported source file, package directory, or subtree",
|
|
16
|
+
}),
|
|
17
|
+
includePrivate: Type.Optional(
|
|
18
|
+
Type.Boolean({ description: "Include private declarations and members (default false)" }),
|
|
19
|
+
),
|
|
20
|
+
includeDocs: Type.Optional(
|
|
21
|
+
Type.Boolean({ description: "Include attached documentation comments (default false)" }),
|
|
22
|
+
),
|
|
23
|
+
names: Type.Optional(
|
|
24
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
25
|
+
minItems: 1,
|
|
26
|
+
description: "Exact declaration name or qualifiedName filters",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
recursive: Type.Optional(
|
|
30
|
+
Type.Boolean({ description: "Recursively outline every supported source file below a directory" }),
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
{ additionalProperties: false },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
type OutlineParams = Static<typeof outlineParams>;
|
|
37
|
+
|
|
38
|
+
type OutlineExecuteResult = {
|
|
39
|
+
content: Array<{ type: "text"; text: string }>;
|
|
40
|
+
details: ExploreToolDetails;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function outlineOptions(params: OutlineParams): OutlineOptions {
|
|
44
|
+
return {
|
|
45
|
+
includePrivate: params.includePrivate === true,
|
|
46
|
+
includeDocs: params.includeDocs === true,
|
|
47
|
+
names: params.names ?? [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function outlineOptionVariants(params: OutlineParams): string[] {
|
|
52
|
+
const fixed = [
|
|
53
|
+
...(params.recursive === true ? ["recursive"] : []),
|
|
54
|
+
...(params.includePrivate === true ? ["private"] : []),
|
|
55
|
+
...(params.includeDocs === true ? ["docs"] : []),
|
|
56
|
+
];
|
|
57
|
+
const names = params.names ?? [];
|
|
58
|
+
if (names.length === 0) return [fixed.length > 0 ? `[${fixed.join(" ")}]` : ""];
|
|
59
|
+
return shrinkingListVariants(names, String(names.length)).map(
|
|
60
|
+
(part) => `[${[...fixed, `names=${part}`].join(" ")}]`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function emptyMessage(file: OutlineFileView, names: readonly string[]): string {
|
|
65
|
+
const base = formatOutlineEmpty(names);
|
|
66
|
+
if (!file.parseDegraded) return base;
|
|
67
|
+
return `warning: parser recovered with errors\n${base}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function withBoundedOutline(
|
|
71
|
+
temporaryOutput: TemporaryOutputStore,
|
|
72
|
+
signal: AbortSignal,
|
|
73
|
+
run: (builder: BoundedTextResultBuilder) => Promise<number>,
|
|
74
|
+
): Promise<OutlineExecuteResult> {
|
|
75
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
76
|
+
try {
|
|
77
|
+
const declarationCount = await run(builder);
|
|
78
|
+
signal.throwIfAborted();
|
|
79
|
+
const bounded = await builder.finish();
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: bounded.content }],
|
|
82
|
+
details: {
|
|
83
|
+
declarationCount,
|
|
84
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
85
|
+
truncated: bounded.overflow.truncated,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
await builder.abort();
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createOutlineTool(
|
|
95
|
+
rowState: ToolRowStateStore,
|
|
96
|
+
temporaryOutput: TemporaryOutputStore,
|
|
97
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
98
|
+
) {
|
|
99
|
+
return defineTool<typeof outlineParams, ExploreToolDetails>({
|
|
100
|
+
name: "outline",
|
|
101
|
+
label: "outline",
|
|
102
|
+
description:
|
|
103
|
+
"Inspect declarations in one supported source file, one non-recursive package directory, or a recursive mixed-language subtree without returning implementation bodies. Line ranges support follow-up show or ranged read.",
|
|
104
|
+
promptSnippet: "Inspect declarations and structure without reading implementation bodies",
|
|
105
|
+
promptGuidelines: [
|
|
106
|
+
"Set recursive=true to orient an unfamiliar repository or subtree across supported languages.",
|
|
107
|
+
"Leave includePrivate off for surface discovery; enable it only for targeted internal implementation work.",
|
|
108
|
+
"Leave includeDocs off when names and signatures answer the question; enable it only when documentation comments matter.",
|
|
109
|
+
"Use show with path+name (and optional line) when exact signature or declaration source is needed.",
|
|
110
|
+
],
|
|
111
|
+
parameters: outlineParams,
|
|
112
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
113
|
+
const engine = engineFor(ctx.cwd);
|
|
114
|
+
const options = outlineOptions(params);
|
|
115
|
+
const abort = signal ?? new AbortController().signal;
|
|
116
|
+
|
|
117
|
+
if (params.recursive === true) {
|
|
118
|
+
return withBoundedOutline(temporaryOutput, abort, async (builder) => {
|
|
119
|
+
let declarationCount = 0;
|
|
120
|
+
let emitted = 0;
|
|
121
|
+
const stream = outlineRecursive(engine, params.path, options, abort);
|
|
122
|
+
let step = await stream.next();
|
|
123
|
+
while (!step.done) {
|
|
124
|
+
abort.throwIfAborted();
|
|
125
|
+
const file = step.value;
|
|
126
|
+
declarationCount += file.rows.length;
|
|
127
|
+
emitted += 1;
|
|
128
|
+
await builder.appendBlock(
|
|
129
|
+
file.path,
|
|
130
|
+
formatPathForDisplay(file.path, engine.cwd),
|
|
131
|
+
formatOutlineFile(file, engine.cwd, true),
|
|
132
|
+
);
|
|
133
|
+
step = await stream.next();
|
|
134
|
+
}
|
|
135
|
+
if (emitted === 0) {
|
|
136
|
+
await builder.appendBlock(undefined, "empty", formatOutlineEmpty(options.names));
|
|
137
|
+
}
|
|
138
|
+
const footer = formatOutlineBudgetFooter(step.value);
|
|
139
|
+
if (footer !== undefined) {
|
|
140
|
+
abort.throwIfAborted();
|
|
141
|
+
await builder.appendRequiredBlock("outline limits", footer);
|
|
142
|
+
}
|
|
143
|
+
return declarationCount;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const result = await outlinePath(engine, params.path, options, abort);
|
|
148
|
+
|
|
149
|
+
if (result.mode === "file") {
|
|
150
|
+
const text =
|
|
151
|
+
result.file.rows.length === 0
|
|
152
|
+
? emptyMessage(result.file, options.names)
|
|
153
|
+
: formatOutlineFile(result.file, engine.cwd, false);
|
|
154
|
+
return withBoundedOutline(temporaryOutput, abort, async (builder) => {
|
|
155
|
+
abort.throwIfAborted();
|
|
156
|
+
await builder.appendBlock(result.file.path, formatPathForDisplay(result.file.path, engine.cwd), text);
|
|
157
|
+
return result.file.rows.length;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (result.files.length === 0) {
|
|
162
|
+
return withBoundedOutline(temporaryOutput, abort, async (builder) => {
|
|
163
|
+
abort.throwIfAborted();
|
|
164
|
+
await builder.appendBlock(undefined, "empty", formatOutlineEmpty(options.names));
|
|
165
|
+
return 0;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return withBoundedOutline(temporaryOutput, abort, async (builder) => {
|
|
170
|
+
let declarationCount = 0;
|
|
171
|
+
for (const file of result.files) {
|
|
172
|
+
abort.throwIfAborted();
|
|
173
|
+
declarationCount += file.rows.length;
|
|
174
|
+
await builder.appendBlock(
|
|
175
|
+
file.path,
|
|
176
|
+
formatPathForDisplay(file.path, engine.cwd),
|
|
177
|
+
formatOutlineFile(file, engine.cwd, true),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return declarationCount;
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
renderCall(args, theme, context) {
|
|
184
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
185
|
+
const targets = [stripLeadingAt(args.path ?? "")];
|
|
186
|
+
const options = outlineOptionVariants(args);
|
|
187
|
+
const component =
|
|
188
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
189
|
+
new ExploreCallComponent(rowState, context.toolCallId, "outline", targets, options, theme);
|
|
190
|
+
component.targetVariants = targets;
|
|
191
|
+
component.optionVariants = options;
|
|
192
|
+
component.theme = theme;
|
|
193
|
+
return component;
|
|
194
|
+
},
|
|
195
|
+
renderResult(result, options, theme, context) {
|
|
196
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
197
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|