@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,160 @@
|
|
|
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 { formatRelationshipResult } from "../format/relationships.ts";
|
|
7
|
+
import type { ExploreFileGraph } from "../graph/file-graph.ts";
|
|
8
|
+
import { queryRelationships, type RelationshipOp } from "../graph/relationships.ts";
|
|
9
|
+
import { stripLeadingAt } from "../../traverse.ts";
|
|
10
|
+
import { ExploreCallComponent, renderExploreResult, type ExploreToolDetails } from "./render.ts";
|
|
11
|
+
|
|
12
|
+
const relationshipParams = Type.Object(
|
|
13
|
+
{
|
|
14
|
+
path: Type.String({ description: "Directory scope (repo/package/subtree)" }),
|
|
15
|
+
targetPath: Type.Optional(Type.String({ description: "Defining file when known" })),
|
|
16
|
+
name: Type.String({ minLength: 1, description: "Declaration name; may be dotted (Type.method)" }),
|
|
17
|
+
line: Type.Optional(Type.Integer({ minimum: 1, description: "1-indexed line to pin one candidate" })),
|
|
18
|
+
resultLimit: Type.Integer({ minimum: 1, maximum: 100, description: "Max sites to return" }),
|
|
19
|
+
},
|
|
20
|
+
{ additionalProperties: false },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function createRelationshipTool(
|
|
24
|
+
op: RelationshipOp,
|
|
25
|
+
description: string,
|
|
26
|
+
promptSnippet: string,
|
|
27
|
+
promptGuidelines: string[],
|
|
28
|
+
rowState: ToolRowStateStore,
|
|
29
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
30
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
31
|
+
) {
|
|
32
|
+
return defineTool<typeof relationshipParams, ExploreToolDetails>({
|
|
33
|
+
name: op,
|
|
34
|
+
label: op,
|
|
35
|
+
description,
|
|
36
|
+
promptSnippet,
|
|
37
|
+
promptGuidelines,
|
|
38
|
+
parameters: relationshipParams,
|
|
39
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
40
|
+
const engine = engineFor(ctx.cwd);
|
|
41
|
+
const graph = graphFor(ctx.cwd);
|
|
42
|
+
const abort = signal ?? new AbortController().signal;
|
|
43
|
+
const result = await queryRelationships({
|
|
44
|
+
engine,
|
|
45
|
+
graph,
|
|
46
|
+
scopePath: params.path,
|
|
47
|
+
op,
|
|
48
|
+
targetPath: params.targetPath,
|
|
49
|
+
name: params.name,
|
|
50
|
+
line: params.line,
|
|
51
|
+
resultLimit: params.resultLimit,
|
|
52
|
+
signal: abort,
|
|
53
|
+
});
|
|
54
|
+
const text = formatRelationshipResult(result, engine.cwd);
|
|
55
|
+
const bounded = truncateBoundedHead(text);
|
|
56
|
+
const siteCount = result.kind === "resolved" ? result.sites.length : 0;
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: bounded.content }],
|
|
59
|
+
details: {
|
|
60
|
+
declarationCount: siteCount,
|
|
61
|
+
returnedBytes: Buffer.byteLength(bounded.content),
|
|
62
|
+
truncated: bounded.truncated,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
renderCall(args, theme, context) {
|
|
67
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
68
|
+
const name = args.name ?? "?";
|
|
69
|
+
const scope = stripLeadingAt(args.path ?? "");
|
|
70
|
+
const pin = args.targetPath !== undefined ? stripLeadingAt(args.targetPath) : "";
|
|
71
|
+
const targets = pin.length > 0 ? [`${name} @ ${pin}`] : [name];
|
|
72
|
+
const options = [`[scope=${scope} limit=${args.resultLimit ?? "?"}]`];
|
|
73
|
+
const component =
|
|
74
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
75
|
+
new ExploreCallComponent(rowState, context.toolCallId, op, targets, options, theme);
|
|
76
|
+
component.targetVariants = targets;
|
|
77
|
+
component.optionVariants = options;
|
|
78
|
+
component.theme = theme;
|
|
79
|
+
return component;
|
|
80
|
+
},
|
|
81
|
+
renderResult(result, options, theme, context) {
|
|
82
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
83
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createCallersTool(
|
|
89
|
+
rowState: ToolRowStateStore,
|
|
90
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
91
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
92
|
+
) {
|
|
93
|
+
return createRelationshipTool(
|
|
94
|
+
"callers",
|
|
95
|
+
"Syntactic call sites of a declaration. For types: constructions and implementors.",
|
|
96
|
+
"Find callers of a symbol",
|
|
97
|
+
[
|
|
98
|
+
"Use after selecting a change target when inbound uses affect the plan.",
|
|
99
|
+
"Disambiguate with targetPath or line when the name is common.",
|
|
100
|
+
"Treat ambiguous sites as non-actionable until tightened.",
|
|
101
|
+
],
|
|
102
|
+
rowState,
|
|
103
|
+
engineFor,
|
|
104
|
+
graphFor,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function createCalleesTool(
|
|
109
|
+
rowState: ToolRowStateStore,
|
|
110
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
111
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
112
|
+
) {
|
|
113
|
+
return createRelationshipTool(
|
|
114
|
+
"callees",
|
|
115
|
+
"Direct callees inside a declaration body. For types: ancestor types from heritage.",
|
|
116
|
+
"Find callees of a symbol",
|
|
117
|
+
["Direct only — no depth parameter.", "Use to skim outbound dependencies before editing a body."],
|
|
118
|
+
rowState,
|
|
119
|
+
engineFor,
|
|
120
|
+
graphFor,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createReferencesTool(
|
|
125
|
+
rowState: ToolRowStateStore,
|
|
126
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
127
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
128
|
+
) {
|
|
129
|
+
return createRelationshipTool(
|
|
130
|
+
"references",
|
|
131
|
+
"Direct references: calls, constructions, imports, and heritage mentions of a declaration.",
|
|
132
|
+
"Find references to a symbol",
|
|
133
|
+
[
|
|
134
|
+
"Includes import and re-export-style bindings when classified.",
|
|
135
|
+
"Not a full IDE rename set — respect certainty labels.",
|
|
136
|
+
],
|
|
137
|
+
rowState,
|
|
138
|
+
engineFor,
|
|
139
|
+
graphFor,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createImplementationsTool(
|
|
144
|
+
rowState: ToolRowStateStore,
|
|
145
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
146
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
147
|
+
) {
|
|
148
|
+
return createRelationshipTool(
|
|
149
|
+
"implementations",
|
|
150
|
+
"Syntactic inheritance/implementation sites and conservative same-name overrides.",
|
|
151
|
+
"Find implementations of a type or method",
|
|
152
|
+
[
|
|
153
|
+
"For types: heritage clauses naming the target.",
|
|
154
|
+
"For methods: same-name methods on types that extend/implement the owner (conservative).",
|
|
155
|
+
],
|
|
156
|
+
rowState,
|
|
157
|
+
engineFor,
|
|
158
|
+
graphFor,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { formatSize, keyHint, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
3
|
+
import { formatToolRowTitle, type ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
4
|
+
|
|
5
|
+
export type ExploreToolDetails = {
|
|
6
|
+
declarationCount: number;
|
|
7
|
+
returnedBytes: number;
|
|
8
|
+
truncated: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* TUI title width fallbacks for a list: full join, progressively shorter with `,+N`, then summary.
|
|
13
|
+
* Callers wrap each variant with path/option chrome as needed.
|
|
14
|
+
*/
|
|
15
|
+
export function shrinkingListVariants(items: readonly string[], summary: string, empty = ""): string[] {
|
|
16
|
+
if (items.length === 0) return [empty];
|
|
17
|
+
const variants: string[] = [];
|
|
18
|
+
for (let shown = items.length; shown >= 1; shown -= 1) {
|
|
19
|
+
const omitted = items.length - shown;
|
|
20
|
+
const head = items.slice(0, shown).join(",");
|
|
21
|
+
variants.push(omitted > 0 ? `${head},+${omitted}` : head);
|
|
22
|
+
}
|
|
23
|
+
variants.push(summary);
|
|
24
|
+
return variants;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Compact call row: `tool → target [options]` with left-truncating target variants. */
|
|
28
|
+
export class ExploreCallComponent implements Component {
|
|
29
|
+
private readonly rowState: ToolRowStateStore;
|
|
30
|
+
private readonly rowId: string;
|
|
31
|
+
private readonly tool: string;
|
|
32
|
+
targetVariants: string[];
|
|
33
|
+
optionVariants: string[];
|
|
34
|
+
theme: Theme;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
rowState: ToolRowStateStore,
|
|
38
|
+
rowId: string,
|
|
39
|
+
tool: string,
|
|
40
|
+
targetVariants: string[],
|
|
41
|
+
optionVariants: string[],
|
|
42
|
+
theme: Theme,
|
|
43
|
+
) {
|
|
44
|
+
this.rowState = rowState;
|
|
45
|
+
this.rowId = rowId;
|
|
46
|
+
this.tool = tool;
|
|
47
|
+
this.targetVariants = targetVariants;
|
|
48
|
+
this.optionVariants = optionVariants;
|
|
49
|
+
this.theme = theme;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render(width: number): string[] {
|
|
53
|
+
if (width <= 0) return [];
|
|
54
|
+
const prefixWidth = visibleWidth(`${this.tool} → `);
|
|
55
|
+
const shortestTarget = this.targetVariants.at(-1) ?? "";
|
|
56
|
+
const minimumTargetWidth = Math.min(12, visibleWidth(shortestTarget));
|
|
57
|
+
const options =
|
|
58
|
+
this.optionVariants.find(
|
|
59
|
+
(candidate) => prefixWidth + minimumTargetWidth + (candidate ? visibleWidth(candidate) + 1 : 0) <= width,
|
|
60
|
+
) ??
|
|
61
|
+
this.optionVariants.at(-1) ??
|
|
62
|
+
"";
|
|
63
|
+
const optionsWidth = options ? visibleWidth(options) + 1 : 0;
|
|
64
|
+
const targetWidth = Math.max(1, width - prefixWidth - optionsWidth);
|
|
65
|
+
const target = this.targetVariants.find((candidate) => visibleWidth(candidate) <= targetWidth) ?? shortestTarget;
|
|
66
|
+
const displayTarget = truncateLeft(target, targetWidth);
|
|
67
|
+
const line =
|
|
68
|
+
formatToolRowTitle(this.rowState, this.rowId, this.tool, this.theme) +
|
|
69
|
+
this.theme.fg("toolOutput", " → ") +
|
|
70
|
+
this.theme.fg("accent", displayTarget) +
|
|
71
|
+
(options ? ` ${this.theme.fg("muted", options)}` : "");
|
|
72
|
+
return [truncateToWidth(line, width, "")];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
invalidate(): void {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function truncateLeft(text: string, width: number): string {
|
|
79
|
+
if (width <= 0) return "";
|
|
80
|
+
if (visibleWidth(text) <= width) return text;
|
|
81
|
+
if (width === 1) return "…";
|
|
82
|
+
let suffix = "";
|
|
83
|
+
for (const character of Array.from(text).reverse()) {
|
|
84
|
+
if (visibleWidth(`…${character}${suffix}`) > width) break;
|
|
85
|
+
suffix = character + suffix;
|
|
86
|
+
}
|
|
87
|
+
return `…${suffix}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function renderExploreResult(
|
|
91
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ExploreToolDetails },
|
|
92
|
+
expanded: boolean,
|
|
93
|
+
theme: Theme,
|
|
94
|
+
context: { lastComponent?: Component; isError: boolean },
|
|
95
|
+
): Text {
|
|
96
|
+
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
97
|
+
if (!expanded && !context.isError) {
|
|
98
|
+
const count = result.details?.declarationCount ?? 0;
|
|
99
|
+
const noun = count === 1 ? "declaration" : "declarations";
|
|
100
|
+
const bytes = result.details === undefined ? "" : `, ${formatSize(result.details.returnedBytes)} returned`;
|
|
101
|
+
text.setText(
|
|
102
|
+
theme.fg("muted", `${count} ${noun}${bytes} (`) +
|
|
103
|
+
keyHint("app.tools.expand", "to expand") +
|
|
104
|
+
theme.fg("muted", ")"),
|
|
105
|
+
);
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
108
|
+
const output = result.content
|
|
109
|
+
.filter((item): item is { type: string; text: string } => item.type === "text" && typeof item.text === "string")
|
|
110
|
+
.map((item) => item.text)
|
|
111
|
+
.join("\n");
|
|
112
|
+
text.setText(
|
|
113
|
+
output
|
|
114
|
+
? output
|
|
115
|
+
.split("\n")
|
|
116
|
+
.map((line) => theme.fg("toolOutput", line))
|
|
117
|
+
.join("\n")
|
|
118
|
+
: "",
|
|
119
|
+
);
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
@@ -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 { formatDepsResult, formatReverseDepsEmpty } 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 reverseDepsParams = 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 importer entries to return" }),
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false },
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function createReverseDepsTool(
|
|
21
|
+
rowState: ToolRowStateStore,
|
|
22
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
23
|
+
graphFor: (cwd: string) => ExploreFileGraph,
|
|
24
|
+
) {
|
|
25
|
+
return defineTool<typeof reverseDepsParams, ExploreToolDetails>({
|
|
26
|
+
name: "reverse_deps",
|
|
27
|
+
label: "reverse_deps",
|
|
28
|
+
description:
|
|
29
|
+
"File-level: who imports this file within the inferred project scope. Depth > 1 walks transitive importers (BFS).",
|
|
30
|
+
promptSnippet: "List files that import this file",
|
|
31
|
+
promptGuidelines: [
|
|
32
|
+
"Use for file-level reverse dependencies, not symbol callers.",
|
|
33
|
+
"Scope is session cwd when the file is under it, else nearest git root.",
|
|
34
|
+
"Tighten resultLimit on large packages.",
|
|
35
|
+
],
|
|
36
|
+
parameters: reverseDepsParams,
|
|
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.reverseDeps(params.path, depth, params.resultLimit, abort);
|
|
43
|
+
const text = formatDepsResult(result, engine.cwd, formatReverseDepsEmpty());
|
|
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, "reverse_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
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { Type, type Static } from "typebox";
|
|
5
|
+
import { truncateBoundedHead } from "../../../../shared/bounded-text-result.ts";
|
|
6
|
+
import type { ToolRowStateStore } from "../../../../shared/tool-row-state.ts";
|
|
7
|
+
import type { ExploreEngine } from "../engine.ts";
|
|
8
|
+
import { formatShowBatch } from "../format/show.ts";
|
|
9
|
+
import { showTargets, type ShowTargetInput } from "../queries/show.ts";
|
|
10
|
+
import { stripLeadingAt } from "../../traverse.ts";
|
|
11
|
+
import { ExploreCallComponent, renderExploreResult, shrinkingListVariants, type ExploreToolDetails } from "./render.ts";
|
|
12
|
+
|
|
13
|
+
const showTargetSchema = Type.Object(
|
|
14
|
+
{
|
|
15
|
+
path: Type.String({ description: "File containing the declaration" }),
|
|
16
|
+
name: Type.String({ minLength: 1, description: "Declaration name; may be dotted (Type.method)" }),
|
|
17
|
+
line: Type.Optional(Type.Integer({ minimum: 1, description: "1-indexed line covered by the declaration range" })),
|
|
18
|
+
},
|
|
19
|
+
{ additionalProperties: false },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const showParams = Type.Object(
|
|
23
|
+
{
|
|
24
|
+
targets: Type.Array(showTargetSchema, {
|
|
25
|
+
minItems: 1,
|
|
26
|
+
description: "One or more path+name targets (optional line to disambiguate)",
|
|
27
|
+
}),
|
|
28
|
+
view: StringEnum(["signature", "signatureWithDocs", "declaration", "declarationWithImports"] as const, {
|
|
29
|
+
description:
|
|
30
|
+
"signature omits docs and bodies; signatureWithDocs adds attached docs; declaration is exact source; declarationWithImports adds import statements that mention identifiers in the declaration",
|
|
31
|
+
}),
|
|
32
|
+
contextLines: Type.Optional(
|
|
33
|
+
Type.Integer({
|
|
34
|
+
minimum: 0,
|
|
35
|
+
description: "Lines of source context before and after each declaration (view=declaration only)",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
},
|
|
39
|
+
{ additionalProperties: false },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
type ShowParams = Static<typeof showParams>;
|
|
43
|
+
|
|
44
|
+
function showTargetVariants(targets: readonly ShowTargetInput[]): string[] {
|
|
45
|
+
if (targets.length === 0) return ["targets"];
|
|
46
|
+
const fileCount = new Set(targets.map((target) => stripLeadingAt(target.path))).size;
|
|
47
|
+
if (fileCount === 1) {
|
|
48
|
+
const first = targets[0];
|
|
49
|
+
const file = first === undefined ? "file" : basename(stripLeadingAt(first.path));
|
|
50
|
+
return shrinkingListVariants(
|
|
51
|
+
targets.map((target) => target.name),
|
|
52
|
+
`${targets.length} symbols`,
|
|
53
|
+
).map((part) => `${file}: ${part}`);
|
|
54
|
+
}
|
|
55
|
+
return shrinkingListVariants(
|
|
56
|
+
targets.map((target) => `${target.name}@${basename(stripLeadingAt(target.path))}`),
|
|
57
|
+
`${targets.length} symbols in ${fileCount} files`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function showOptionLabel(params: ShowParams): string {
|
|
62
|
+
const context =
|
|
63
|
+
params.contextLines === undefined || params.view !== "declaration" ? "" : ` context=${params.contextLines}`;
|
|
64
|
+
return `[${params.view}${context}]`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createShowTool(rowState: ToolRowStateStore, engineFor: (cwd: string) => ExploreEngine) {
|
|
68
|
+
return defineTool<typeof showParams, ExploreToolDetails>({
|
|
69
|
+
name: "show",
|
|
70
|
+
label: "show",
|
|
71
|
+
description:
|
|
72
|
+
"Return signatures, documented signatures, exact declarations, or declarations with related imports for path+name targets. Ambiguous or missing targets fail the whole batch with candidates — no partial bodies.",
|
|
73
|
+
promptSnippet: "Retrieve signatures or exact declarations for path+name targets",
|
|
74
|
+
promptGuidelines: [
|
|
75
|
+
"Prefer signature when the contract is enough; use signatureWithDocs only when attached docs matter.",
|
|
76
|
+
"Use declaration for exact implementation source; declarationWithImports when edit context needs imports.",
|
|
77
|
+
"Disambiguate with path and optional line. Do not guess among candidates.",
|
|
78
|
+
"If the batch exceeds the model-output budget, request fewer targets — show never truncates bodies.",
|
|
79
|
+
],
|
|
80
|
+
parameters: showParams,
|
|
81
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
82
|
+
const engine = engineFor(ctx.cwd);
|
|
83
|
+
const abort = signal ?? new AbortController().signal;
|
|
84
|
+
const batch = await showTargets(engine, params.targets, params.view, params.contextLines, abort);
|
|
85
|
+
const text = formatShowBatch(batch, engine.cwd);
|
|
86
|
+
const bounded = truncateBoundedHead(text);
|
|
87
|
+
if (bounded.truncated) {
|
|
88
|
+
throw new Error("show result exceeded the model-output budget. Request fewer targets.");
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text }],
|
|
92
|
+
details: {
|
|
93
|
+
declarationCount: batch.blocks.length,
|
|
94
|
+
returnedBytes: Buffer.byteLength(text),
|
|
95
|
+
truncated: false,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
renderCall(args, theme, context) {
|
|
100
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
101
|
+
const targets = showTargetVariants(args.targets ?? []);
|
|
102
|
+
const options = [showOptionLabel(args)];
|
|
103
|
+
const component =
|
|
104
|
+
(context.lastComponent as ExploreCallComponent | undefined) ??
|
|
105
|
+
new ExploreCallComponent(rowState, context.toolCallId, "show", targets, options, theme);
|
|
106
|
+
component.targetVariants = targets;
|
|
107
|
+
component.optionVariants = options;
|
|
108
|
+
component.theme = theme;
|
|
109
|
+
return component;
|
|
110
|
+
},
|
|
111
|
+
renderResult(result, options, theme, context) {
|
|
112
|
+
rowState.watch(context.toolCallId, context.invalidate);
|
|
113
|
+
return renderExploreResult(result, options.expanded, theme, context);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { ExploreEngine } from "./ast/engine.ts";
|
|
3
|
+
import { walkPaths } from "./traverse.ts";
|
|
4
|
+
|
|
5
|
+
const GUIDANCE_SCAN_MAX_ENTRIES = 4096;
|
|
6
|
+
|
|
7
|
+
export function registerExploreGuidance(pi: ExtensionAPI, engineFor: (cwd: string) => ExploreEngine): void {
|
|
8
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
9
|
+
try {
|
|
10
|
+
const engine = engineFor(ctx.cwd);
|
|
11
|
+
const advertised = engine.registry
|
|
12
|
+
.registeredLanguages()
|
|
13
|
+
.filter((language) => language.capabilities.shape)
|
|
14
|
+
.map((language) => language.id);
|
|
15
|
+
const advertisedSet = new Set(advertised);
|
|
16
|
+
const detected = new Set<string>();
|
|
17
|
+
|
|
18
|
+
await walkPaths({
|
|
19
|
+
cwd: engine.cwd,
|
|
20
|
+
root: ".",
|
|
21
|
+
filesOnly: true,
|
|
22
|
+
budgets: { maxFiles: GUIDANCE_SCAN_MAX_ENTRIES },
|
|
23
|
+
onFile(hit) {
|
|
24
|
+
const language = engine.registry.adapterForPath(hit.absolutePath)?.id;
|
|
25
|
+
if (language !== undefined && advertisedSet.has(language)) detected.add(language);
|
|
26
|
+
return detected.size < advertisedSet.size;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const backed = advertised.filter((language) => detected.has(language));
|
|
31
|
+
if (backed.length === 0) return undefined;
|
|
32
|
+
|
|
33
|
+
const guidance = `## Explore source policy
|
|
34
|
+
Structurally backed languages detected in this workspace: ${backed.map((language) => `\`${language}\``).join(", ")}.
|
|
35
|
+
|
|
36
|
+
- Use the same workflow for every backed language. Identify the job type before loading deeply: locate, reuse, edit, explain, or debug.
|
|
37
|
+
- Ask the smallest sufficient query and stop when it answers the question.
|
|
38
|
+
- Prefer \`outline\` for unfamiliar trees and known packages. A large full harness \`read\` returns an outline; use ranged \`read\` or \`show\` for bodies.
|
|
39
|
+
- Prefer \`discover\` when reuse is the goal and the path is unknown. Search package/public surfaces first.
|
|
40
|
+
- Bind targets with path + name (+ line when needed). Use \`show\` views cheapest to richest: \`signature\`, \`signatureWithDocs\`, \`declaration\`, \`declarationWithImports\`.
|
|
41
|
+
- Run \`impact\` before a non-trivial symbol change. Use \`context\` to understand one symbol in one bounded pack.
|
|
42
|
+
- After selecting a target, use \`callers\`, \`callees\`, \`references\`, or \`implementations\` for focused relationship questions.
|
|
43
|
+
- Use \`ast_search\` for code shapes and harness \`grep\` for literals. Use harness \`read\` for formatting, comments, gaps, and unsupported files.
|
|
44
|
+
- List and search paths with harness \`ls\`, \`find\`, and \`grep\`. Edit only with harness patch/edit/write tools.
|
|
45
|
+
- Trust structural hits as exact path, line, and source text. Do not ask for scores or engine metadata.`;
|
|
46
|
+
|
|
47
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${guidance}` };
|
|
48
|
+
} catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,57 +1,103 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { onTauEvent } from "../../shared/events.
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
3
|
+
import { onTauEvent } from "../../shared/events.ts";
|
|
4
|
+
import { createTemporaryOutputStore } from "../../shared/temporary-output-store.ts";
|
|
5
|
+
import { createToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
6
|
+
import { createExploreEngine, type ExploreEngine } from "./ast/engine.ts";
|
|
7
|
+
import { createFileGraph, type ExploreFileGraph } from "./ast/graph/file-graph.ts";
|
|
8
|
+
import { registerReadOutlineHook } from "./ast/read/hook.ts";
|
|
9
|
+
import { createAstSearchTool } from "./ast/tools/ast-search.ts";
|
|
10
|
+
import { createContextTool } from "./ast/tools/context.ts";
|
|
11
|
+
import { createDepsTool } from "./ast/tools/deps.ts";
|
|
12
|
+
import { createDiscoverTool } from "./ast/tools/discover.ts";
|
|
13
|
+
import { createImpactTool } from "./ast/tools/impact.ts";
|
|
14
|
+
import { createOutlineTool } from "./ast/tools/outline.ts";
|
|
15
|
+
import {
|
|
16
|
+
createCalleesTool,
|
|
17
|
+
createCallersTool,
|
|
18
|
+
createImplementationsTool,
|
|
19
|
+
createReferencesTool,
|
|
20
|
+
} from "./ast/tools/relationships.ts";
|
|
21
|
+
import { createReverseDepsTool } from "./ast/tools/reverse-deps.ts";
|
|
22
|
+
import { createShowTool } from "./ast/tools/show.ts";
|
|
23
|
+
import { registerExploreGuidance } from "./guidance.ts";
|
|
24
|
+
import { registerExploreAutoread } from "./read/autoread.ts";
|
|
15
25
|
|
|
16
26
|
export default function exploreExtension(pi: ExtensionAPI): void {
|
|
17
27
|
const rowState = createToolRowStateStore(pi, "explore.tool-row-state");
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
28
|
+
const temporaryOutput = createTemporaryOutputStore();
|
|
29
|
+
let engine: ExploreEngine | undefined;
|
|
30
|
+
let graph: ExploreFileGraph | undefined;
|
|
31
|
+
|
|
32
|
+
const engineFor = (cwd: string): ExploreEngine => {
|
|
33
|
+
const absoluteCwd = resolve(cwd);
|
|
34
|
+
if (engine !== undefined && engine.cwd === absoluteCwd) return engine;
|
|
35
|
+
engine?.shutdown();
|
|
36
|
+
graph?.clear();
|
|
37
|
+
engine = createExploreEngine({ cwd: absoluteCwd });
|
|
38
|
+
graph = createFileGraph(engine);
|
|
39
|
+
return engine;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const graphFor = (cwd: string): ExploreFileGraph => {
|
|
43
|
+
engineFor(cwd);
|
|
44
|
+
if (graph === undefined) {
|
|
45
|
+
throw new Error("Explore file graph failed to initialize");
|
|
46
|
+
}
|
|
47
|
+
return graph;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
pi.registerTool(createOutlineTool(rowState, temporaryOutput, engineFor));
|
|
51
|
+
pi.registerTool(createShowTool(rowState, engineFor));
|
|
52
|
+
pi.registerTool(createDiscoverTool(rowState, temporaryOutput, engineFor));
|
|
53
|
+
pi.registerTool(createAstSearchTool(rowState, temporaryOutput, engineFor));
|
|
54
|
+
pi.registerTool(createDepsTool(rowState, engineFor, graphFor));
|
|
55
|
+
pi.registerTool(createReverseDepsTool(rowState, engineFor, graphFor));
|
|
56
|
+
pi.registerTool(createCallersTool(rowState, engineFor, graphFor));
|
|
57
|
+
pi.registerTool(createCalleesTool(rowState, engineFor, graphFor));
|
|
58
|
+
pi.registerTool(createReferencesTool(rowState, engineFor, graphFor));
|
|
59
|
+
pi.registerTool(createImplementationsTool(rowState, engineFor, graphFor));
|
|
60
|
+
pi.registerTool(createImpactTool(rowState, temporaryOutput, engineFor, graphFor));
|
|
61
|
+
pi.registerTool(createContextTool(rowState, temporaryOutput, engineFor, graphFor));
|
|
62
|
+
registerReadOutlineHook(pi, engineFor);
|
|
63
|
+
registerExploreAutoread(pi, rowState, engineFor);
|
|
64
|
+
registerExploreGuidance(pi, engineFor);
|
|
65
|
+
|
|
66
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
67
|
+
await temporaryOutput.shutdown();
|
|
68
|
+
await temporaryOutput.start();
|
|
40
69
|
rowState.clear();
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
engine?.shutdown();
|
|
71
|
+
graph?.clear();
|
|
72
|
+
engine = createExploreEngine({ cwd: ctx.cwd });
|
|
73
|
+
graph = createFileGraph(engine);
|
|
43
74
|
});
|
|
75
|
+
|
|
44
76
|
onTauEvent(pi, "explore.ast", "tau:file-mutation.applied", (event) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
if (engine === undefined) return;
|
|
78
|
+
const paths = event.changes.flatMap((change) => {
|
|
79
|
+
const main = resolve(event.cwd, change.path);
|
|
80
|
+
if (change.move === undefined) return [main];
|
|
81
|
+
return [main, resolve(event.cwd, change.move.from), resolve(event.cwd, change.move.to)];
|
|
82
|
+
});
|
|
83
|
+
engine.invalidate(paths);
|
|
84
|
+
const topologyChanged = event.changes.some(
|
|
85
|
+
(change) => change.kind === "add" || change.kind === "delete" || change.move !== undefined,
|
|
86
|
+
);
|
|
87
|
+
if (topologyChanged) graph?.clear();
|
|
88
|
+
else graph?.invalidate(paths);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
pi.on("session_tree", () => {
|
|
92
|
+
engine?.clear();
|
|
93
|
+
graph?.clear();
|
|
50
94
|
});
|
|
51
|
-
|
|
52
|
-
pi.on("session_tree", () => readSnapshots.clear());
|
|
95
|
+
|
|
53
96
|
pi.on("session_shutdown", async () => {
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
engine?.shutdown();
|
|
98
|
+
engine = undefined;
|
|
99
|
+
graph?.clear();
|
|
100
|
+
graph = undefined;
|
|
101
|
+
await temporaryOutput.shutdown();
|
|
56
102
|
});
|
|
57
103
|
}
|