@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,66 @@
|
|
|
1
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
2
|
+
import type { ContextEntry, ContextGroup, ContextResult, ContextTarget } from "../queries/context.ts";
|
|
3
|
+
import { compositeSectionBlocks, type CompositeBlock } from "./composite.ts";
|
|
4
|
+
import { formatCandidateList } from "./relationships.ts";
|
|
5
|
+
|
|
6
|
+
function formatTargetHeader(target: ContextTarget, budget: number, used: number, cwd: string): string {
|
|
7
|
+
const path = formatPathForDisplay(target.path, cwd);
|
|
8
|
+
return `${target.qualifiedName || target.name} ${target.kind} ${path}:${target.startLine} budget=${budget} used=${used}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatEntry(entry: ContextEntry, cwd: string): string {
|
|
12
|
+
const path = formatPathForDisplay(entry.path, cwd);
|
|
13
|
+
const flags = entry.flags.length > 0 ? ` ${entry.flags.join(",")}` : "";
|
|
14
|
+
const head = `${path}:${entry.startLine} ${entry.kind} ${entry.qualifiedName || entry.name} ~${entry.tokens}tok ${entry.view}${flags}`;
|
|
15
|
+
return `${head}\n${entry.text}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatGroup(group: ContextGroup, cwd: string): string {
|
|
19
|
+
const lines = [`${group.label}:`];
|
|
20
|
+
for (const entry of group.entries) {
|
|
21
|
+
lines.push(formatEntry(entry, cwd));
|
|
22
|
+
lines.push("");
|
|
23
|
+
}
|
|
24
|
+
if (lines.at(-1) === "") lines.pop();
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatResolved(result: Extract<ContextResult, { kind: "resolved" }>, cwd: string): string {
|
|
29
|
+
const lines = [formatTargetHeader(result.target, result.budget, result.used, cwd)];
|
|
30
|
+
if (result.groups.length === 0) {
|
|
31
|
+
lines.push("No context entries.");
|
|
32
|
+
} else {
|
|
33
|
+
for (const group of result.groups) {
|
|
34
|
+
lines.push("");
|
|
35
|
+
lines.push(formatGroup(group, cwd));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (result.truncated) {
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push("truncated: budget exhausted or entry skipped");
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatContextResult(result: ContextResult, cwd: string): string {
|
|
46
|
+
if (result.kind === "error") return result.message;
|
|
47
|
+
if (result.kind === "notFound") return "No matching declaration.";
|
|
48
|
+
if (result.kind === "candidates") return formatCandidateList(result.candidates, cwd);
|
|
49
|
+
return formatResolved(result, cwd);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function contextSectionBlocks(
|
|
53
|
+
result: Extract<ContextResult, { kind: "resolved" }>,
|
|
54
|
+
cwd: string,
|
|
55
|
+
): CompositeBlock[] {
|
|
56
|
+
return compositeSectionBlocks({
|
|
57
|
+
header: formatTargetHeader(result.target, result.budget, result.used, cwd),
|
|
58
|
+
emptyLabel: "No context entries.",
|
|
59
|
+
sections: result.groups.map((group) => ({
|
|
60
|
+
id: group.id,
|
|
61
|
+
label: group.label,
|
|
62
|
+
text: formatGroup(group, cwd),
|
|
63
|
+
})),
|
|
64
|
+
footers: result.truncated ? ["truncated: budget exhausted or entry skipped"] : [],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { FileDepHit, FileDepQueryResult } from "../graph/file-graph.ts";
|
|
2
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
3
|
+
|
|
4
|
+
function depthPrefix(depth: number, showDepth: boolean): string {
|
|
5
|
+
return showDepth ? `d${depth} ` : "";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Factor shared directory prefixes into a small indent tree. */
|
|
9
|
+
function formatInternalTree(
|
|
10
|
+
paths: readonly { depth: number; path: string }[],
|
|
11
|
+
cwd: string,
|
|
12
|
+
showDepth: boolean,
|
|
13
|
+
): string[] {
|
|
14
|
+
if (paths.length === 0) return [];
|
|
15
|
+
const rows = paths.map((hit) => ({
|
|
16
|
+
depth: hit.depth,
|
|
17
|
+
display: formatPathForDisplay(hit.path, cwd),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Common directory prefix across displays (posix-ish split on /).
|
|
21
|
+
const split = rows.map((row) => row.display.split("/"));
|
|
22
|
+
let common = 0;
|
|
23
|
+
const first = split[0];
|
|
24
|
+
if (first !== undefined && split.length > 1) {
|
|
25
|
+
while (common < first.length - 1) {
|
|
26
|
+
const part = first[common];
|
|
27
|
+
if (part === undefined) break;
|
|
28
|
+
if (!split.every((parts) => parts[common] === part)) break;
|
|
29
|
+
common += 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
if (common > 0 && first !== undefined) {
|
|
35
|
+
lines.push(first.slice(0, common).join("/"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
39
|
+
const row = rows[i];
|
|
40
|
+
const parts = split[i];
|
|
41
|
+
if (row === undefined || parts === undefined) continue;
|
|
42
|
+
const leaf = common > 0 ? parts.slice(common).join("/") : row.display;
|
|
43
|
+
const indent = common > 0 ? " " : "";
|
|
44
|
+
lines.push(`${indent}${depthPrefix(row.depth, showDepth)}${leaf}`);
|
|
45
|
+
}
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatDepsResult(result: FileDepQueryResult, cwd: string, emptyLabel: string): string {
|
|
50
|
+
const showDepth = result.depth > 1;
|
|
51
|
+
const internal = result.hits
|
|
52
|
+
.filter((hit): hit is FileDepHit & { path: string } => hit.path !== undefined)
|
|
53
|
+
.map((hit) => ({ depth: hit.depth, path: hit.path }));
|
|
54
|
+
const external = result.hits
|
|
55
|
+
.filter((hit): hit is FileDepHit & { externalId: string } => hit.externalId !== undefined)
|
|
56
|
+
.map((hit) => ({ depth: hit.depth, id: hit.externalId }));
|
|
57
|
+
|
|
58
|
+
if (internal.length === 0 && external.length === 0) {
|
|
59
|
+
return emptyLabel;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
lines.push(...formatInternalTree(internal, cwd, showDepth));
|
|
64
|
+
|
|
65
|
+
if (external.length > 0) {
|
|
66
|
+
if (lines.length > 0) lines.push("external:");
|
|
67
|
+
else lines.push("external:");
|
|
68
|
+
for (const hit of external) {
|
|
69
|
+
lines.push(` ${depthPrefix(hit.depth, showDepth)}${hit.id}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (result.resultLimitReached) {
|
|
74
|
+
lines.push(`omitted (result limit ${result.resultLimit})`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function formatDepsEmpty(): string {
|
|
81
|
+
return "No file dependencies";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatReverseDepsEmpty(): string {
|
|
85
|
+
return "No reverse file dependencies";
|
|
86
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { DiscoverCandidate, DiscoverResult } from "../queries/discover.ts";
|
|
2
|
+
import type { ScanOutcome } from "../scan.ts";
|
|
3
|
+
import type { TraversalLimit } from "../../traverse.ts";
|
|
4
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
5
|
+
|
|
6
|
+
function lineRange(startLine: number, endLine: number): string {
|
|
7
|
+
return startLine === endLine ? `L${startLine}` : `L${startLine}-${endLine}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatCandidateRows(candidate: DiscoverCandidate): string[] {
|
|
11
|
+
const lines: string[] = [];
|
|
12
|
+
const signature = candidate.signature.length > 0 ? candidate.signature : candidate.name;
|
|
13
|
+
const sigLines = signature.split("\n");
|
|
14
|
+
const first = sigLines[0] ?? candidate.name;
|
|
15
|
+
lines.push(`${lineRange(candidate.startLine, candidate.endLine)} ${candidate.kind}: ${first}`);
|
|
16
|
+
for (let i = 1; i < sigLines.length; i += 1) {
|
|
17
|
+
lines.push(` ${sigLines[i] ?? ""}`);
|
|
18
|
+
}
|
|
19
|
+
if (candidate.access !== undefined) {
|
|
20
|
+
lines.push(` ${candidate.access}`);
|
|
21
|
+
}
|
|
22
|
+
return lines;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One complete file group (path header + candidates). */
|
|
26
|
+
export function formatDiscoverFile(path: string, candidates: readonly DiscoverCandidate[], cwd: string): string {
|
|
27
|
+
const lines = [formatPathForDisplay(path, cwd)];
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
lines.push(...formatCandidateRows(candidate));
|
|
30
|
+
}
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatDiscoverEmpty(): string {
|
|
35
|
+
return "No matching declarations";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function limitLabel(limit: TraversalLimit): string {
|
|
39
|
+
switch (limit) {
|
|
40
|
+
case "maxFiles":
|
|
41
|
+
return "file count";
|
|
42
|
+
case "maxSourceBytes":
|
|
43
|
+
return "source bytes";
|
|
44
|
+
case "maxDepth":
|
|
45
|
+
return "depth";
|
|
46
|
+
case "maxElapsedMs":
|
|
47
|
+
return "elapsed time";
|
|
48
|
+
case "cancelled":
|
|
49
|
+
return "cancelled";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Footers only when something was cut or a budget tripped. */
|
|
54
|
+
export function formatDiscoverFooters(result: DiscoverResult): string | undefined {
|
|
55
|
+
const parts: string[] = [];
|
|
56
|
+
if (result.resultLimitReached) {
|
|
57
|
+
parts.push(`candidates omitted (result limit ${result.resultLimit})`);
|
|
58
|
+
}
|
|
59
|
+
if (result.workLimitReached) parts.push("query work");
|
|
60
|
+
if (result.candidateLimitReached) parts.push("query candidates");
|
|
61
|
+
const scan: ScanOutcome | undefined = result.scan;
|
|
62
|
+
if (scan?.limit !== undefined && scan.limit !== "cancelled") {
|
|
63
|
+
parts.push(limitLabel(scan.limit));
|
|
64
|
+
}
|
|
65
|
+
if (parts.length === 0) return undefined;
|
|
66
|
+
return `limits reached: ${parts.join(", ")}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Group candidates by defining file (stable path order from caller). */
|
|
70
|
+
export function groupDiscoverByFile(
|
|
71
|
+
candidates: readonly DiscoverCandidate[],
|
|
72
|
+
): { path: string; candidates: DiscoverCandidate[] }[] {
|
|
73
|
+
const groups: { path: string; candidates: DiscoverCandidate[] }[] = [];
|
|
74
|
+
const indexByPath = new Map<string, number>();
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const existing = indexByPath.get(candidate.path);
|
|
77
|
+
if (existing === undefined) {
|
|
78
|
+
indexByPath.set(candidate.path, groups.length);
|
|
79
|
+
groups.push({ path: candidate.path, candidates: [candidate] });
|
|
80
|
+
} else {
|
|
81
|
+
groups[existing]?.candidates.push(candidate);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return groups;
|
|
85
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
2
|
+
import type { ImpactFileRow, ImpactResult, ImpactSection, ImpactSiteRow, ImpactTarget } from "../queries/impact.ts";
|
|
3
|
+
import { compositeSectionBlocks, type CompositeBlock } from "./composite.ts";
|
|
4
|
+
import { formatCandidateList } from "./relationships.ts";
|
|
5
|
+
|
|
6
|
+
function formatTargetHeader(target: ImpactTarget, cwd: string): string {
|
|
7
|
+
const path = formatPathForDisplay(target.path, cwd);
|
|
8
|
+
return `${target.qualifiedName || target.name} ${target.kind} ${path}:${target.startLine}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatSiteRow(row: ImpactSiteRow): string {
|
|
12
|
+
const certainty = row.certainty === "exact" ? "" : ` ${row.certainty}`;
|
|
13
|
+
const preview = row.preview.length > 0 ? ` ${row.preview}` : "";
|
|
14
|
+
const name = row.name.length > 0 ? ` ${row.name}` : "";
|
|
15
|
+
return ` L${row.line} ${row.siteKind}${name}${certainty}${preview}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatFileRow(row: ImpactFileRow, cwd: string, showDepth: boolean): string {
|
|
19
|
+
const depth = showDepth ? `d${row.depth} ` : "";
|
|
20
|
+
if (row.path !== undefined) {
|
|
21
|
+
return ` ${depth}${formatPathForDisplay(row.path, cwd)}`;
|
|
22
|
+
}
|
|
23
|
+
return ` ${depth}${row.externalId ?? "?"}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatSection(section: ImpactSection, cwd: string): string[] {
|
|
27
|
+
const lines = [`${section.label}:`];
|
|
28
|
+
const showDepth = section.id === "transitiveDependents";
|
|
29
|
+
const siteRows = section.rows.filter((row): row is ImpactSiteRow => row.kind === "site");
|
|
30
|
+
const fileRows = section.rows.filter((row): row is ImpactFileRow => row.kind === "file");
|
|
31
|
+
|
|
32
|
+
if (siteRows.length > 0) {
|
|
33
|
+
let currentPath = "";
|
|
34
|
+
for (const row of siteRows) {
|
|
35
|
+
if (row.path !== currentPath) {
|
|
36
|
+
currentPath = row.path;
|
|
37
|
+
lines.push(formatPathForDisplay(row.path, cwd));
|
|
38
|
+
}
|
|
39
|
+
lines.push(formatSiteRow(row));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fileRows.length > 0) {
|
|
44
|
+
const internal = fileRows.filter((row) => row.path !== undefined);
|
|
45
|
+
const external = fileRows.filter((row) => row.externalId !== undefined);
|
|
46
|
+
for (const row of internal) {
|
|
47
|
+
lines.push(formatFileRow(row, cwd, showDepth));
|
|
48
|
+
}
|
|
49
|
+
if (external.length > 0) {
|
|
50
|
+
lines.push(" external:");
|
|
51
|
+
for (const row of external) {
|
|
52
|
+
lines.push(` ${formatFileRow(row, cwd, showDepth).trimStart()}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (section.omitted) {
|
|
58
|
+
lines.push(` omitted (limit ${section.limit})`);
|
|
59
|
+
}
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatResolved(result: Extract<ImpactResult, { kind: "resolved" }>, cwd: string): string {
|
|
64
|
+
const lines = [formatTargetHeader(result.target, cwd)];
|
|
65
|
+
if (result.sections.length === 0) {
|
|
66
|
+
lines.push("No impact edges.");
|
|
67
|
+
} else {
|
|
68
|
+
for (const section of result.sections) {
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push(...formatSection(section, cwd));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const note of result.notes) {
|
|
74
|
+
lines.push(note);
|
|
75
|
+
}
|
|
76
|
+
if (result.parseDegraded) {
|
|
77
|
+
lines.push("parser: degraded on at least one file");
|
|
78
|
+
}
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function formatImpactResult(result: ImpactResult, cwd: string): string {
|
|
83
|
+
if (result.kind === "error") return result.message;
|
|
84
|
+
if (result.kind === "notFound") return "No matching declaration.";
|
|
85
|
+
if (result.kind === "candidates") return formatCandidateList(result.candidates, cwd);
|
|
86
|
+
return formatResolved(result, cwd);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function impactSectionBlocks(
|
|
90
|
+
result: Extract<ImpactResult, { kind: "resolved" }>,
|
|
91
|
+
cwd: string,
|
|
92
|
+
): CompositeBlock[] {
|
|
93
|
+
const footers = [...result.notes];
|
|
94
|
+
if (result.parseDegraded) footers.push("parser: degraded on at least one file");
|
|
95
|
+
return compositeSectionBlocks({
|
|
96
|
+
header: formatTargetHeader(result.target, cwd),
|
|
97
|
+
emptyLabel: "No impact edges.",
|
|
98
|
+
sections: result.sections.map((section) => ({
|
|
99
|
+
id: section.id,
|
|
100
|
+
label: section.label,
|
|
101
|
+
text: formatSection(section, cwd).join("\n"),
|
|
102
|
+
})),
|
|
103
|
+
footers,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { OutlineFileView, OutlineRow } from "../queries/outline.ts";
|
|
2
|
+
import type { ScanOutcome } from "../scan.ts";
|
|
3
|
+
import type { TraversalLimit } from "../../traverse.ts";
|
|
4
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
5
|
+
|
|
6
|
+
function lineRange(startLine: number, endLine: number): string {
|
|
7
|
+
return startLine === endLine ? `L${startLine}` : `L${startLine}-${endLine}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatRow(row: OutlineRow): string[] {
|
|
11
|
+
const indent = " ".repeat(row.depth);
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
if (row.docs !== undefined && row.docs.length > 0) {
|
|
14
|
+
for (const docLine of row.docs.replace(/\s+$/u, "").split("\n")) {
|
|
15
|
+
lines.push(`${indent}${docLine}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const signature = row.signature.replace(/\s+$/u, "");
|
|
19
|
+
const sigLines = signature.length > 0 ? signature.split("\n") : [row.name];
|
|
20
|
+
const first = sigLines[0] ?? row.name;
|
|
21
|
+
lines.push(`${indent}${lineRange(row.startLine, row.endLine)}: ${first}`);
|
|
22
|
+
for (let i = 1; i < sigLines.length; i += 1) {
|
|
23
|
+
lines.push(`${indent}${sigLines[i] ?? ""}`);
|
|
24
|
+
}
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One complete outline unit (optional path header + rows). */
|
|
29
|
+
export function formatOutlineFile(view: OutlineFileView, cwd: string, includePathHeader: boolean): string {
|
|
30
|
+
const lines: string[] = [];
|
|
31
|
+
if (includePathHeader) lines.push(formatPathForDisplay(view.path, cwd));
|
|
32
|
+
if (view.parseDegraded) {
|
|
33
|
+
lines.push("warning: parser recovered with errors");
|
|
34
|
+
}
|
|
35
|
+
if (view.rows.length === 0) {
|
|
36
|
+
lines.push("No declarations");
|
|
37
|
+
} else {
|
|
38
|
+
for (const row of view.rows) lines.push(...formatRow(row));
|
|
39
|
+
}
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function formatOutlineEmpty(names: readonly string[]): string {
|
|
44
|
+
return names.length > 0 ? "No matching declarations" : "No declarations";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function limitLabel(limit: TraversalLimit): string {
|
|
48
|
+
switch (limit) {
|
|
49
|
+
case "maxFiles":
|
|
50
|
+
return "file count";
|
|
51
|
+
case "maxSourceBytes":
|
|
52
|
+
return "source bytes";
|
|
53
|
+
case "maxDepth":
|
|
54
|
+
return "depth";
|
|
55
|
+
case "maxElapsedMs":
|
|
56
|
+
return "elapsed time";
|
|
57
|
+
case "cancelled":
|
|
58
|
+
return "cancelled";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Budget/limit footer line when a recursive scan stopped early. */
|
|
63
|
+
export function formatOutlineBudgetFooter(outcome: ScanOutcome): string | undefined {
|
|
64
|
+
if (outcome.limit === undefined || outcome.limit === "cancelled") return undefined;
|
|
65
|
+
return `limits reached: ${limitLabel(outcome.limit)}`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Candidate } from "../identity.ts";
|
|
2
|
+
import type { RelationshipQueryResult, RelationshipSite, RelationshipTarget } from "../graph/relationships.ts";
|
|
3
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
4
|
+
|
|
5
|
+
function formatCompetitors(competitors: readonly Candidate[], cwd: string): string[] {
|
|
6
|
+
const lines: string[] = [];
|
|
7
|
+
for (const c of competitors) {
|
|
8
|
+
const path = formatPathForDisplay(c.path, cwd);
|
|
9
|
+
const sig = c.signature.replace(/\s+/gu, " ").trim();
|
|
10
|
+
const short = sig.length > 80 ? `${sig.slice(0, 77)}...` : sig;
|
|
11
|
+
lines.push(` - ${path}:${c.startLine} ${c.kind} ${c.qualifiedName || c.name} ${short}`);
|
|
12
|
+
}
|
|
13
|
+
return lines;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function formatSiteRow(site: RelationshipSite): string {
|
|
17
|
+
const certainty = site.certainty === "exact" ? "" : ` ${site.certainty}`;
|
|
18
|
+
const preview = site.preview.length > 0 ? ` ${site.preview}` : "";
|
|
19
|
+
return ` L${site.line} ${site.kind}${certainty}${preview}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatResolved(
|
|
23
|
+
target: RelationshipTarget,
|
|
24
|
+
sites: readonly RelationshipSite[],
|
|
25
|
+
cwd: string,
|
|
26
|
+
resultLimit: number,
|
|
27
|
+
resultLimitReached: boolean,
|
|
28
|
+
parseDegraded: boolean,
|
|
29
|
+
): string {
|
|
30
|
+
const path = formatPathForDisplay(target.path, cwd);
|
|
31
|
+
const header = `${target.qualifiedName || target.name} ${target.kind} ${path}:${target.startLine}`;
|
|
32
|
+
if (sites.length === 0) {
|
|
33
|
+
const lines = [header, "No relationship sites."];
|
|
34
|
+
if (parseDegraded) lines.push("parser: degraded on at least one file");
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const lines: string[] = [header, ""];
|
|
39
|
+
let currentPath = "";
|
|
40
|
+
for (const site of sites) {
|
|
41
|
+
if (site.path !== currentPath) {
|
|
42
|
+
currentPath = site.path;
|
|
43
|
+
lines.push(formatPathForDisplay(site.path, cwd));
|
|
44
|
+
}
|
|
45
|
+
lines.push(formatSiteRow(site));
|
|
46
|
+
if (site.competitors.length > 0) {
|
|
47
|
+
lines.push(" competitors:");
|
|
48
|
+
lines.push(...formatCompetitors(site.competitors, cwd));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (resultLimitReached) lines.push(`omitted (result limit ${resultLimit})`);
|
|
53
|
+
if (parseDegraded) lines.push("parser: degraded on at least one file");
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatCandidateList(candidates: readonly Candidate[], cwd: string): string {
|
|
58
|
+
const lines = ["Ambiguous target — disambiguate with targetPath or line:", ""];
|
|
59
|
+
for (const c of candidates) {
|
|
60
|
+
const path = formatPathForDisplay(c.path, cwd);
|
|
61
|
+
const sig = c.signature.replace(/\s+/gu, " ").trim();
|
|
62
|
+
const short = sig.length > 80 ? `${sig.slice(0, 77)}...` : sig;
|
|
63
|
+
lines.push(`${path}:${c.startLine} ${c.kind} ${c.qualifiedName || c.name}`);
|
|
64
|
+
if (short.length > 0) lines.push(` ${short}`);
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function formatRelationshipResult(result: RelationshipQueryResult, cwd: string): string {
|
|
70
|
+
if (result.kind === "error") return result.message;
|
|
71
|
+
if (result.kind === "notFound") return "No matching declaration.";
|
|
72
|
+
if (result.kind === "candidates") return formatCandidateList(result.candidates, cwd);
|
|
73
|
+
return formatResolved(
|
|
74
|
+
result.target,
|
|
75
|
+
result.sites,
|
|
76
|
+
cwd,
|
|
77
|
+
result.resultLimit,
|
|
78
|
+
result.resultLimitReached,
|
|
79
|
+
result.parseDegraded,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ShowBatch, ShowBlock } from "../queries/show.ts";
|
|
2
|
+
import { formatPathForDisplay } from "../../traverse.ts";
|
|
3
|
+
|
|
4
|
+
function lineRange(startLine: number, endLine: number): string {
|
|
5
|
+
return startLine === endLine ? `L${startLine}` : `L${startLine}-${endLine}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function formatBlock(block: ShowBlock, cwd: string, includePath: boolean): string {
|
|
9
|
+
const lines: string[] = [];
|
|
10
|
+
if (includePath) lines.push(formatPathForDisplay(block.path, cwd));
|
|
11
|
+
lines.push(`${lineRange(block.startLine, block.endLine)}: ${block.name}`);
|
|
12
|
+
for (const warning of block.warnings) {
|
|
13
|
+
lines.push(`warning: ${warning}`);
|
|
14
|
+
}
|
|
15
|
+
lines.push(block.text);
|
|
16
|
+
return lines.join("\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Exact source blocks.
|
|
21
|
+
* Single block: no path chrome. Multi-block: path header when the path changes.
|
|
22
|
+
*/
|
|
23
|
+
export function formatShowBatch(batch: ShowBatch, cwd: string): string {
|
|
24
|
+
if (batch.blocks.length === 0) return "";
|
|
25
|
+
if (batch.blocks.length === 1) {
|
|
26
|
+
const only = batch.blocks[0];
|
|
27
|
+
if (only === undefined) return "";
|
|
28
|
+
return formatBlock(only, cwd, false);
|
|
29
|
+
}
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
let lastPath: string | undefined;
|
|
32
|
+
for (const block of batch.blocks) {
|
|
33
|
+
const includePath = block.path !== lastPath;
|
|
34
|
+
lastPath = block.path;
|
|
35
|
+
parts.push(formatBlock(block, cwd, includePath));
|
|
36
|
+
}
|
|
37
|
+
return parts.join("\n\n");
|
|
38
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"webTreeSitter": "0.26.11",
|
|
3
|
+
"vscodeTreeSitterWasm": "0.3.1",
|
|
4
|
+
"treeSitterCli": "0.26.11",
|
|
5
|
+
"wasiSdk": "wasi-sdk-29",
|
|
6
|
+
"grammars": [
|
|
7
|
+
{ "id": "typescript", "source": "vscode", "file": "tree-sitter-typescript.wasm" },
|
|
8
|
+
{ "id": "tsx", "source": "vscode", "file": "tree-sitter-tsx.wasm" },
|
|
9
|
+
{ "id": "go", "source": "vscode", "file": "tree-sitter-go.wasm" },
|
|
10
|
+
{ "id": "rust", "source": "vscode", "file": "tree-sitter-rust.wasm" },
|
|
11
|
+
{ "id": "c_sharp", "source": "vscode", "file": "tree-sitter-c-sharp.wasm" },
|
|
12
|
+
{ "id": "java", "source": "vscode", "file": "tree-sitter-java.wasm" },
|
|
13
|
+
{
|
|
14
|
+
"id": "kotlin",
|
|
15
|
+
"source": "built",
|
|
16
|
+
"file": "kotlin.wasm",
|
|
17
|
+
"repo": "https://github.com/fwcd/tree-sitter-kotlin",
|
|
18
|
+
"tag": "0.3.8",
|
|
19
|
+
"rev": "9e7e62415a0546efb4549e730af1d9bf5460a37b",
|
|
20
|
+
"subdir": "."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "swift",
|
|
24
|
+
"source": "release",
|
|
25
|
+
"file": "swift.wasm",
|
|
26
|
+
"repo": "https://github.com/alex-pinkus/tree-sitter-swift",
|
|
27
|
+
"tag": "0.7.3",
|
|
28
|
+
"rev": "b8b22bffbb3441780e6471665bacfb263741c86a",
|
|
29
|
+
"url": "https://github.com/alex-pinkus/tree-sitter-swift/releases/download/0.7.3/tree-sitter-swift.wasm"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "odin",
|
|
33
|
+
"source": "built",
|
|
34
|
+
"file": "odin.wasm",
|
|
35
|
+
"repo": "https://github.com/tree-sitter-grammars/tree-sitter-odin",
|
|
36
|
+
"tag": "v1.3.0",
|
|
37
|
+
"rev": "e8adc739b78409a99f8c31313f0bb54cc538cf73",
|
|
38
|
+
"subdir": "."
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export type VscodeGrammarPin = {
|
|
7
|
+
id: string;
|
|
8
|
+
source: "vscode";
|
|
9
|
+
file: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type BuiltGrammarPin = {
|
|
13
|
+
id: string;
|
|
14
|
+
source: "built";
|
|
15
|
+
file: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
rev: string;
|
|
19
|
+
subdir: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ReleaseGrammarPin = {
|
|
23
|
+
id: string;
|
|
24
|
+
source: "release";
|
|
25
|
+
file: string;
|
|
26
|
+
repo: string;
|
|
27
|
+
tag: string;
|
|
28
|
+
rev: string;
|
|
29
|
+
url: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type GrammarPin = VscodeGrammarPin | BuiltGrammarPin | ReleaseGrammarPin;
|
|
33
|
+
|
|
34
|
+
export type GrammarManifest = {
|
|
35
|
+
webTreeSitter: string;
|
|
36
|
+
vscodeTreeSitterWasm: string;
|
|
37
|
+
treeSitterCli: string;
|
|
38
|
+
wasiSdk: string;
|
|
39
|
+
grammars: GrammarPin[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const grammarsDir = dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const resolveModule = createRequire(import.meta.url).resolve;
|
|
44
|
+
|
|
45
|
+
export function runtimeWasmPath(): string {
|
|
46
|
+
return join(dirname(resolveModule("web-tree-sitter")), "web-tree-sitter.wasm");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function grammarWasmPath(pin: GrammarPin): string {
|
|
50
|
+
if (pin.source === "vscode") {
|
|
51
|
+
return join(dirname(resolveModule("@vscode/tree-sitter-wasm")), pin.file);
|
|
52
|
+
}
|
|
53
|
+
return join(grammarsDir, pin.file);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireString(record: Record<string, unknown>, key: string, context: string): string {
|
|
57
|
+
const value = record[key];
|
|
58
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
59
|
+
throw new Error(`grammar manifest: ${context}.${key} must be a non-empty string`);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseGrammarPin(entry: unknown, index: number): GrammarPin {
|
|
65
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
66
|
+
throw new Error(`grammar manifest: grammars[${index}] must be an object`);
|
|
67
|
+
}
|
|
68
|
+
const record = entry as Record<string, unknown>;
|
|
69
|
+
const context = `grammars[${index}]`;
|
|
70
|
+
const id = requireString(record, "id", context);
|
|
71
|
+
const file = requireString(record, "file", context);
|
|
72
|
+
const source = requireString(record, "source", context);
|
|
73
|
+
if (source === "vscode") return { id, source, file };
|
|
74
|
+
const repo = requireString(record, "repo", context);
|
|
75
|
+
const tag = requireString(record, "tag", context);
|
|
76
|
+
const rev = requireString(record, "rev", context);
|
|
77
|
+
if (source === "built")
|
|
78
|
+
return { id, source, file, repo, tag, rev, subdir: requireString(record, "subdir", context) };
|
|
79
|
+
if (source === "release") return { id, source, file, repo, tag, rev, url: requireString(record, "url", context) };
|
|
80
|
+
throw new Error(`grammar manifest: ${context}.source must be vscode, built, or release`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function loadGrammarManifest(): GrammarManifest {
|
|
84
|
+
const parsed: unknown = JSON.parse(readFileSync(join(grammarsDir, "manifest.json"), "utf8"));
|
|
85
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
86
|
+
throw new Error("grammar manifest: root must be an object");
|
|
87
|
+
}
|
|
88
|
+
const root = parsed as Record<string, unknown>;
|
|
89
|
+
if (!Array.isArray(root.grammars) || root.grammars.length === 0) {
|
|
90
|
+
throw new Error("grammar manifest: grammars must be a non-empty array");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
webTreeSitter: requireString(root, "webTreeSitter", "root"),
|
|
94
|
+
vscodeTreeSitterWasm: requireString(root, "vscodeTreeSitterWasm", "root"),
|
|
95
|
+
treeSitterCli: requireString(root, "treeSitterCli", "root"),
|
|
96
|
+
wasiSdk: requireString(root, "wasiSdk", "root"),
|
|
97
|
+
grammars: root.grammars.map(parseGrammarPin),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
Binary file
|
|
Binary file
|