@cortexkit/aft-pi 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.ts +3 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +940 -23
- package/dist/tools/_shared.d.ts +11 -0
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/ast.d.ts +27 -1
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/conflicts.d.ts +9 -0
- package/dist/tools/conflicts.d.ts.map +1 -1
- package/dist/tools/fs.d.ts +15 -1
- package/dist/tools/fs.d.ts.map +1 -1
- package/dist/tools/imports.d.ts +21 -1
- package/dist/tools/imports.d.ts.map +1 -1
- package/dist/tools/lsp.d.ts +16 -1
- package/dist/tools/lsp.d.ts.map +1 -1
- package/dist/tools/navigate.d.ts +17 -1
- package/dist/tools/navigate.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +27 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/refactor.d.ts +22 -1
- package/dist/tools/refactor.d.ts.map +1 -1
- package/dist/tools/render-helpers.d.ts +36 -0
- package/dist/tools/render-helpers.d.ts.map +1 -0
- package/dist/tools/safety.d.ts +16 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts +14 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tools/structure.d.ts +26 -1
- package/dist/tools/structure.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { createRequire as createRequire3 } from "node:module";
|
|
6
|
-
import { homedir as
|
|
6
|
+
import { homedir as homedir7 } from "node:os";
|
|
7
7
|
import { join as join8 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/shared/status.ts
|
|
@@ -143,11 +143,24 @@ function formatStatusDialogMessage(status) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/tools/_shared.ts
|
|
146
|
+
var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
147
|
+
callers: 60000,
|
|
148
|
+
trace_to: 60000,
|
|
149
|
+
trace_data: 60000,
|
|
150
|
+
impact: 60000,
|
|
151
|
+
grep: 60000,
|
|
152
|
+
glob: 60000,
|
|
153
|
+
semantic_search: 45000
|
|
154
|
+
};
|
|
155
|
+
function timeoutForCommand(command) {
|
|
156
|
+
return LONG_RUNNING_COMMAND_TIMEOUT_MS[command];
|
|
157
|
+
}
|
|
146
158
|
function bridgeFor(ctx, cwd) {
|
|
147
159
|
return ctx.pool.getBridge(cwd);
|
|
148
160
|
}
|
|
149
161
|
async function callBridge(bridge, command, params = {}) {
|
|
150
|
-
const
|
|
162
|
+
const timeoutMs = timeoutForCommand(command);
|
|
163
|
+
const response = await bridge.send(command, params, timeoutMs !== undefined ? { timeoutMs } : undefined);
|
|
151
164
|
if (response.success === false) {
|
|
152
165
|
const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
|
|
153
166
|
throw new Error(message);
|
|
@@ -647,7 +660,7 @@ class BinaryBridge {
|
|
|
647
660
|
isAlive() {
|
|
648
661
|
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
649
662
|
}
|
|
650
|
-
async send(command, params = {}) {
|
|
663
|
+
async send(command, params = {}, options) {
|
|
651
664
|
if (this._shuttingDown) {
|
|
652
665
|
throw new Error(`[aft-pi] Bridge is shutting down, cannot send "${command}"`);
|
|
653
666
|
}
|
|
@@ -681,13 +694,14 @@ class BinaryBridge {
|
|
|
681
694
|
const request = { id, command, ...params };
|
|
682
695
|
const line = `${JSON.stringify(request)}
|
|
683
696
|
`;
|
|
697
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? this.timeoutMs;
|
|
684
698
|
return new Promise((resolve, reject) => {
|
|
685
699
|
const timer = setTimeout(() => {
|
|
686
700
|
this.pending.delete(id);
|
|
687
|
-
warn(`Request "${command}" (id=${id}) timed out after ${
|
|
688
|
-
reject(new Error(`[aft-pi] Request "${command}" (id=${id}) timed out after ${
|
|
701
|
+
warn(`Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms — restarting bridge`);
|
|
702
|
+
reject(new Error(`[aft-pi] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
689
703
|
this.handleTimeout();
|
|
690
|
-
},
|
|
704
|
+
}, effectiveTimeoutMs);
|
|
691
705
|
this.pending.set(id, { resolve, reject, timer });
|
|
692
706
|
if (!this.process?.stdin?.writable) {
|
|
693
707
|
this.pending.delete(id);
|
|
@@ -1342,6 +1356,197 @@ function registerShutdownCleanup(fn) {
|
|
|
1342
1356
|
// src/tools/ast.ts
|
|
1343
1357
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
1344
1358
|
import { Type } from "@sinclair/typebox";
|
|
1359
|
+
|
|
1360
|
+
// src/tools/render-helpers.ts
|
|
1361
|
+
import { homedir as homedir5 } from "node:os";
|
|
1362
|
+
import { renderDiff } from "@mariozechner/pi-coding-agent";
|
|
1363
|
+
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
1364
|
+
function reuseText(last) {
|
|
1365
|
+
return last instanceof Text ? last : new Text("", 0, 0);
|
|
1366
|
+
}
|
|
1367
|
+
function reuseContainer(last) {
|
|
1368
|
+
return last instanceof Container ? last : new Container;
|
|
1369
|
+
}
|
|
1370
|
+
function shortenPath(path2) {
|
|
1371
|
+
const home = homedir5();
|
|
1372
|
+
if (path2.startsWith(home))
|
|
1373
|
+
return `~${path2.slice(home.length)}`;
|
|
1374
|
+
return path2;
|
|
1375
|
+
}
|
|
1376
|
+
function renderToolCall(toolName, summary, theme, context) {
|
|
1377
|
+
const text = reuseText(context.lastComponent);
|
|
1378
|
+
const suffix = summary ? ` ${summary}` : "";
|
|
1379
|
+
text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
|
|
1380
|
+
return text;
|
|
1381
|
+
}
|
|
1382
|
+
function accentPath(theme, path2) {
|
|
1383
|
+
if (!path2)
|
|
1384
|
+
return theme.fg("toolOutput", "...");
|
|
1385
|
+
return theme.fg("accent", shortenPath(path2));
|
|
1386
|
+
}
|
|
1387
|
+
function collectTextContent(result) {
|
|
1388
|
+
return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
|
|
1389
|
+
`).trim();
|
|
1390
|
+
}
|
|
1391
|
+
function extractStructuredPayload(result) {
|
|
1392
|
+
if (result.details !== undefined)
|
|
1393
|
+
return result.details;
|
|
1394
|
+
const text = collectTextContent(result);
|
|
1395
|
+
if (!text)
|
|
1396
|
+
return;
|
|
1397
|
+
try {
|
|
1398
|
+
return JSON.parse(text);
|
|
1399
|
+
} catch {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
function renderErrorResult(result, fallback, theme, context) {
|
|
1404
|
+
const text = reuseText(context.lastComponent);
|
|
1405
|
+
const message = collectTextContent(result) || fallback;
|
|
1406
|
+
text.setText(`
|
|
1407
|
+
${theme.fg("error", message)}`);
|
|
1408
|
+
return text;
|
|
1409
|
+
}
|
|
1410
|
+
function renderSections(sections, context) {
|
|
1411
|
+
const container = reuseContainer(context.lastComponent);
|
|
1412
|
+
container.clear();
|
|
1413
|
+
const visibleSections = sections.filter((section) => section.trim().length > 0);
|
|
1414
|
+
if (visibleSections.length === 0)
|
|
1415
|
+
return container;
|
|
1416
|
+
container.addChild(new Spacer(1));
|
|
1417
|
+
visibleSections.forEach((section, index) => {
|
|
1418
|
+
if (index > 0)
|
|
1419
|
+
container.addChild(new Spacer(1));
|
|
1420
|
+
container.addChild(new Text(section, 0, 0));
|
|
1421
|
+
});
|
|
1422
|
+
return container;
|
|
1423
|
+
}
|
|
1424
|
+
function asRecord2(value) {
|
|
1425
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1426
|
+
return;
|
|
1427
|
+
return value;
|
|
1428
|
+
}
|
|
1429
|
+
function asRecords(value) {
|
|
1430
|
+
return Array.isArray(value) ? value.map(asRecord2).filter(Boolean) : [];
|
|
1431
|
+
}
|
|
1432
|
+
function asString(value) {
|
|
1433
|
+
return typeof value === "string" ? value : undefined;
|
|
1434
|
+
}
|
|
1435
|
+
function asNumber(value) {
|
|
1436
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1437
|
+
}
|
|
1438
|
+
function asBoolean(value) {
|
|
1439
|
+
return typeof value === "boolean" ? value : undefined;
|
|
1440
|
+
}
|
|
1441
|
+
function formatValue(value) {
|
|
1442
|
+
if (Array.isArray(value))
|
|
1443
|
+
return value.map(formatValue).join(", ");
|
|
1444
|
+
if (typeof value === "string")
|
|
1445
|
+
return value;
|
|
1446
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
1447
|
+
return String(value);
|
|
1448
|
+
if (value === null || value === undefined)
|
|
1449
|
+
return "—";
|
|
1450
|
+
try {
|
|
1451
|
+
return JSON.stringify(value);
|
|
1452
|
+
} catch {
|
|
1453
|
+
return String(value);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function groupByFile(items, getFile) {
|
|
1457
|
+
const groups = new Map;
|
|
1458
|
+
items.forEach((item) => {
|
|
1459
|
+
const file = getFile(item) ?? "(unknown file)";
|
|
1460
|
+
const current = groups.get(file) ?? [];
|
|
1461
|
+
current.push(item);
|
|
1462
|
+
groups.set(file, current);
|
|
1463
|
+
});
|
|
1464
|
+
return groups;
|
|
1465
|
+
}
|
|
1466
|
+
function distinctCount(values) {
|
|
1467
|
+
return new Set(values.filter((value) => Boolean(value))).size;
|
|
1468
|
+
}
|
|
1469
|
+
function severityBadge(theme, severity) {
|
|
1470
|
+
const label = severity === "information" ? "info" : severity;
|
|
1471
|
+
switch (severity) {
|
|
1472
|
+
case "error":
|
|
1473
|
+
return theme.fg("error", `[${label}]`);
|
|
1474
|
+
case "warning":
|
|
1475
|
+
return theme.fg("warning", `[${label}]`);
|
|
1476
|
+
case "information":
|
|
1477
|
+
return theme.fg("accent", `[${label}]`);
|
|
1478
|
+
case "hint":
|
|
1479
|
+
return theme.fg("muted", `[${label}]`);
|
|
1480
|
+
default:
|
|
1481
|
+
return theme.fg("muted", `[${label}]`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function formatTimestamp(value) {
|
|
1485
|
+
if (typeof value === "string" && value.length > 0)
|
|
1486
|
+
return value;
|
|
1487
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
1488
|
+
return;
|
|
1489
|
+
const millis = value > 1000000000000 ? value : value * 1000;
|
|
1490
|
+
const date = new Date(millis);
|
|
1491
|
+
if (Number.isNaN(date.getTime()))
|
|
1492
|
+
return String(value);
|
|
1493
|
+
return date.toISOString().replace("T", " ").replace(".000Z", "Z");
|
|
1494
|
+
}
|
|
1495
|
+
function formatUnifiedDiffForPi(unifiedDiff) {
|
|
1496
|
+
if (!unifiedDiff.trim())
|
|
1497
|
+
return "";
|
|
1498
|
+
const entries = [];
|
|
1499
|
+
const hunkHeader = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
1500
|
+
let oldLine = 1;
|
|
1501
|
+
let newLine = 1;
|
|
1502
|
+
for (const line of unifiedDiff.split(`
|
|
1503
|
+
`)) {
|
|
1504
|
+
if (!line)
|
|
1505
|
+
continue;
|
|
1506
|
+
if (line.startsWith("--- ") || line.startsWith("+++ "))
|
|
1507
|
+
continue;
|
|
1508
|
+
if (line.startsWith("\"))
|
|
1509
|
+
continue;
|
|
1510
|
+
const headerMatch = line.match(hunkHeader);
|
|
1511
|
+
if (headerMatch) {
|
|
1512
|
+
oldLine = Number(headerMatch[1]);
|
|
1513
|
+
newLine = Number(headerMatch[2]);
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1517
|
+
entries.push({ prefix: "+", line: newLine, text: line.slice(1) });
|
|
1518
|
+
newLine += 1;
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1522
|
+
entries.push({ prefix: "-", line: oldLine, text: line.slice(1) });
|
|
1523
|
+
oldLine += 1;
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (line.startsWith(" ")) {
|
|
1527
|
+
entries.push({ prefix: " ", line: oldLine, text: line.slice(1) });
|
|
1528
|
+
oldLine += 1;
|
|
1529
|
+
newLine += 1;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (entries.length === 0)
|
|
1533
|
+
return "";
|
|
1534
|
+
const width = String(entries.reduce((max, entry) => Math.max(max, entry.line), 1)).length;
|
|
1535
|
+
return entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
|
|
1536
|
+
`);
|
|
1537
|
+
}
|
|
1538
|
+
function renderUnifiedDiff(unifiedDiff) {
|
|
1539
|
+
const piDiff = formatUnifiedDiffForPi(unifiedDiff);
|
|
1540
|
+
if (!piDiff)
|
|
1541
|
+
return "";
|
|
1542
|
+
try {
|
|
1543
|
+
return renderDiff(piDiff);
|
|
1544
|
+
} catch {
|
|
1545
|
+
return piDiff;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/tools/ast.ts
|
|
1345
1550
|
var AstLang = StringEnum(["typescript", "tsx", "javascript", "python", "rust", "go"], {
|
|
1346
1551
|
description: "Target language"
|
|
1347
1552
|
});
|
|
@@ -1362,6 +1567,107 @@ var ReplaceParams = Type.Object({
|
|
|
1362
1567
|
globs: Type.Optional(Type.Array(Type.String(), { description: "Include/exclude globs" })),
|
|
1363
1568
|
dryRun: Type.Optional(Type.Boolean({ description: "Preview without applying (default: false)" }))
|
|
1364
1569
|
});
|
|
1570
|
+
function buildAstSearchSections(payload, theme) {
|
|
1571
|
+
const response = asRecord2(payload);
|
|
1572
|
+
if (!response)
|
|
1573
|
+
return [theme.fg("muted", "No AST search results.")];
|
|
1574
|
+
const matches = asRecords(response.matches);
|
|
1575
|
+
const totalMatches = asNumber(response.total_matches) ?? matches.length;
|
|
1576
|
+
const filesWithMatches = asNumber(response.files_with_matches) ?? groupByFile(matches, (match) => asString(match.file)).size;
|
|
1577
|
+
const filesSearched = asNumber(response.files_searched);
|
|
1578
|
+
const header = [
|
|
1579
|
+
theme.fg("success", `${totalMatches} match${totalMatches === 1 ? "" : "es"}`),
|
|
1580
|
+
theme.fg("accent", `${filesWithMatches} file${filesWithMatches === 1 ? "" : "s"}`),
|
|
1581
|
+
filesSearched !== undefined ? theme.fg("muted", `${filesSearched} searched`) : undefined
|
|
1582
|
+
].filter(Boolean).join(" · ");
|
|
1583
|
+
if (matches.length === 0)
|
|
1584
|
+
return [header, theme.fg("muted", "No AST matches found.")];
|
|
1585
|
+
const grouped = groupByFile(matches, (match) => asString(match.file));
|
|
1586
|
+
const sections = [header];
|
|
1587
|
+
for (const [file, fileMatches] of grouped.entries()) {
|
|
1588
|
+
const lines = [theme.fg("accent", shortenPath(file))];
|
|
1589
|
+
fileMatches.forEach((match, index) => {
|
|
1590
|
+
const line = asNumber(match.line) ?? 0;
|
|
1591
|
+
const column = asNumber(match.column) ?? 0;
|
|
1592
|
+
const snippet = asString(match.text)?.trim() || "(empty match)";
|
|
1593
|
+
lines.push(` ${index + 1}. ${theme.fg("muted", `${line}:${column}`)} ${snippet}`);
|
|
1594
|
+
const metaVars = asRecord2(match.meta_variables);
|
|
1595
|
+
if (metaVars && Object.keys(metaVars).length > 0) {
|
|
1596
|
+
Object.entries(metaVars).forEach(([name, value]) => {
|
|
1597
|
+
lines.push(` ${theme.fg("muted", `${name} =`)} ${formatValue(value)}`);
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
const context = asRecords(match.context);
|
|
1601
|
+
context.forEach((ctxLine) => {
|
|
1602
|
+
const ctxNumber = asNumber(ctxLine.line) ?? 0;
|
|
1603
|
+
const prefix = ctxLine.is_match === true ? theme.fg("accent", ">") : theme.fg("muted", "|");
|
|
1604
|
+
lines.push(` ${prefix} ${ctxNumber}: ${asString(ctxLine.text) ?? ""}`);
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
sections.push(lines.join(`
|
|
1608
|
+
`));
|
|
1609
|
+
}
|
|
1610
|
+
return sections;
|
|
1611
|
+
}
|
|
1612
|
+
function buildAstReplaceSections(payload, theme) {
|
|
1613
|
+
const response = asRecord2(payload);
|
|
1614
|
+
if (!response)
|
|
1615
|
+
return [theme.fg("muted", "No AST replace results.")];
|
|
1616
|
+
const files = asRecords(response.files);
|
|
1617
|
+
const totalReplacements = asNumber(response.total_replacements) ?? 0;
|
|
1618
|
+
const totalFiles = asNumber(response.total_files) ?? files.length;
|
|
1619
|
+
const filesWithMatches = asNumber(response.files_with_matches);
|
|
1620
|
+
const dryRun = response.dry_run === true;
|
|
1621
|
+
const headerParts = [
|
|
1622
|
+
dryRun ? theme.fg("warning", "[dry run]") : theme.fg("success", "[applied]"),
|
|
1623
|
+
`${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}`,
|
|
1624
|
+
`${totalFiles} file${totalFiles === 1 ? "" : "s"}`,
|
|
1625
|
+
filesWithMatches !== undefined ? theme.fg("muted", `${filesWithMatches} matched`) : undefined
|
|
1626
|
+
];
|
|
1627
|
+
const sections = [headerParts.filter(Boolean).join(" ")];
|
|
1628
|
+
if (files.length === 0) {
|
|
1629
|
+
sections.push(theme.fg("muted", "No files changed."));
|
|
1630
|
+
return sections;
|
|
1631
|
+
}
|
|
1632
|
+
files.forEach((fileResult) => {
|
|
1633
|
+
const file = shortenPath(asString(fileResult.file) ?? "(unknown file)");
|
|
1634
|
+
const replacements = asNumber(fileResult.replacements) ?? 0;
|
|
1635
|
+
const error2 = asString(fileResult.error);
|
|
1636
|
+
const diff = asString(fileResult.diff);
|
|
1637
|
+
const lines = [
|
|
1638
|
+
`${theme.fg("accent", file)} ${theme.fg("muted", `(${replacements} replacement${replacements === 1 ? "" : "s"})`)}`
|
|
1639
|
+
];
|
|
1640
|
+
if (error2) {
|
|
1641
|
+
lines.push(theme.fg("error", error2));
|
|
1642
|
+
} else if (diff) {
|
|
1643
|
+
const rendered = renderUnifiedDiff(diff);
|
|
1644
|
+
lines.push(rendered || theme.fg("muted", "No diff available."));
|
|
1645
|
+
} else {
|
|
1646
|
+
const backupId = asString(fileResult.backup_id);
|
|
1647
|
+
lines.push(backupId ? `${theme.fg("success", "saved")} ${theme.fg("muted", backupId)}` : theme.fg("success", "saved"));
|
|
1648
|
+
}
|
|
1649
|
+
sections.push(lines.join(`
|
|
1650
|
+
`));
|
|
1651
|
+
});
|
|
1652
|
+
return sections;
|
|
1653
|
+
}
|
|
1654
|
+
function renderAstCall(toolName, args, theme, context) {
|
|
1655
|
+
const lang = theme.fg("accent", args.lang);
|
|
1656
|
+
const summary = toolName === "ast_grep_replace" ? `${lang} ${theme.fg("toolOutput", `${args.pattern} → ${args.rewrite}`)}` : `${lang} ${theme.fg("toolOutput", args.pattern)}`;
|
|
1657
|
+
return renderToolCall(toolName === "ast_grep_replace" ? "ast replace" : "ast search", summary, theme, context);
|
|
1658
|
+
}
|
|
1659
|
+
function renderAstResult(toolName, result, theme, context) {
|
|
1660
|
+
if (context.isError) {
|
|
1661
|
+
return renderErrorResult(result, `${toolName} failed`, theme, context);
|
|
1662
|
+
}
|
|
1663
|
+
const payload = extractStructuredPayload(result);
|
|
1664
|
+
if (!payload) {
|
|
1665
|
+
const text = collectTextContent(result);
|
|
1666
|
+
return renderSections([text || theme.fg("muted", "No result.")], context);
|
|
1667
|
+
}
|
|
1668
|
+
const sections = toolName === "ast_grep_replace" ? buildAstReplaceSections(payload, theme) : buildAstSearchSections(payload, theme);
|
|
1669
|
+
return renderSections(sections, context);
|
|
1670
|
+
}
|
|
1365
1671
|
function registerAstTools(pi, ctx, surface) {
|
|
1366
1672
|
if (surface.astSearch) {
|
|
1367
1673
|
pi.registerTool({
|
|
@@ -1383,6 +1689,12 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
1383
1689
|
req.context_lines = params.contextLines;
|
|
1384
1690
|
const response = await callBridge(bridge, "ast_search", req);
|
|
1385
1691
|
return textResult(response.text ?? JSON.stringify(response));
|
|
1692
|
+
},
|
|
1693
|
+
renderCall(args, theme, context) {
|
|
1694
|
+
return renderAstCall("ast_grep_search", args, theme, context);
|
|
1695
|
+
},
|
|
1696
|
+
renderResult(result, _options, theme, context) {
|
|
1697
|
+
return renderAstResult("ast_grep_search", result, theme, context);
|
|
1386
1698
|
}
|
|
1387
1699
|
});
|
|
1388
1700
|
}
|
|
@@ -1406,6 +1718,12 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
1406
1718
|
req.dry_run = params.dryRun === true;
|
|
1407
1719
|
const response = await callBridge(bridge, "ast_replace", req);
|
|
1408
1720
|
return textResult(response.text ?? JSON.stringify(response));
|
|
1721
|
+
},
|
|
1722
|
+
renderCall(args, theme, context) {
|
|
1723
|
+
return renderAstCall("ast_grep_replace", args, theme, context);
|
|
1724
|
+
},
|
|
1725
|
+
renderResult(result, _options, theme, context) {
|
|
1726
|
+
return renderAstResult("ast_grep_replace", result, theme, context);
|
|
1409
1727
|
}
|
|
1410
1728
|
});
|
|
1411
1729
|
}
|
|
@@ -1414,6 +1732,32 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
1414
1732
|
// src/tools/conflicts.ts
|
|
1415
1733
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
1416
1734
|
var ConflictsParams = Type2.Object({});
|
|
1735
|
+
function renderConflictCall(theme, context) {
|
|
1736
|
+
return renderToolCall("conflicts", undefined, theme, context);
|
|
1737
|
+
}
|
|
1738
|
+
function buildConflictSections(text) {
|
|
1739
|
+
const trimmed = text.trim();
|
|
1740
|
+
if (!trimmed)
|
|
1741
|
+
return ["No merge conflicts found."];
|
|
1742
|
+
const [header, ...rest] = trimmed.split(/\n\n+/);
|
|
1743
|
+
const match = header.match(/^(\d+)\s+files?,\s+(\d+)\s+conflicts?/i);
|
|
1744
|
+
const sections = [
|
|
1745
|
+
match ? `${match[1]} conflicted file${match[1] === "1" ? "" : "s"} · ${match[2]} region${match[2] === "1" ? "" : "s"}` : header
|
|
1746
|
+
];
|
|
1747
|
+
if (rest.length === 0)
|
|
1748
|
+
return sections;
|
|
1749
|
+
sections.push(...rest.map((section) => section.trim()).filter(Boolean));
|
|
1750
|
+
return sections;
|
|
1751
|
+
}
|
|
1752
|
+
function renderConflictResult(text, theme, context) {
|
|
1753
|
+
const sections = buildConflictSections(text).map((section, index) => index === 0 ? theme.fg("warning", section) : section);
|
|
1754
|
+
return renderSections(sections, context);
|
|
1755
|
+
}
|
|
1756
|
+
function renderConflictToolResult(result, theme, context) {
|
|
1757
|
+
if (context.isError)
|
|
1758
|
+
return renderErrorResult(result, "conflicts failed", theme, context);
|
|
1759
|
+
return renderConflictResult(collectTextContent(result), theme, context);
|
|
1760
|
+
}
|
|
1417
1761
|
function registerConflictsTool(pi, ctx) {
|
|
1418
1762
|
pi.registerTool({
|
|
1419
1763
|
name: "aft_conflicts",
|
|
@@ -1424,6 +1768,12 @@ function registerConflictsTool(pi, ctx) {
|
|
|
1424
1768
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
1425
1769
|
const response = await callBridge(bridge, "git_conflicts");
|
|
1426
1770
|
return textResult(response.text ?? JSON.stringify(response, null, 2));
|
|
1771
|
+
},
|
|
1772
|
+
renderCall(_args, theme, context) {
|
|
1773
|
+
return renderConflictCall(theme, context);
|
|
1774
|
+
},
|
|
1775
|
+
renderResult(result, _options, theme, context) {
|
|
1776
|
+
return renderConflictToolResult(result, theme, context);
|
|
1427
1777
|
}
|
|
1428
1778
|
});
|
|
1429
1779
|
}
|
|
@@ -1437,6 +1787,27 @@ var MoveParams = Type3.Object({
|
|
|
1437
1787
|
filePath: Type3.String({ description: "Source file path to move" }),
|
|
1438
1788
|
destination: Type3.String({ description: "Destination file path" })
|
|
1439
1789
|
});
|
|
1790
|
+
function renderFsCall(toolName, args, theme, context) {
|
|
1791
|
+
if (toolName === "aft_delete") {
|
|
1792
|
+
return renderToolCall("delete", accentPath(theme, args.filePath), theme, context);
|
|
1793
|
+
}
|
|
1794
|
+
const moveArgs = args;
|
|
1795
|
+
return renderToolCall("move", `${accentPath(theme, moveArgs.filePath)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
|
|
1796
|
+
}
|
|
1797
|
+
function renderFsResult(toolName, args, result, theme, context) {
|
|
1798
|
+
if (context.isError) {
|
|
1799
|
+
return renderErrorResult(result, `${toolName} failed`, theme, context);
|
|
1800
|
+
}
|
|
1801
|
+
if (toolName === "aft_delete") {
|
|
1802
|
+
const filePath = shortenPath(args.filePath);
|
|
1803
|
+
return renderSections([`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", filePath)}`], context);
|
|
1804
|
+
}
|
|
1805
|
+
const moveArgs = args;
|
|
1806
|
+
return renderSections([
|
|
1807
|
+
`${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath(moveArgs.filePath))}`,
|
|
1808
|
+
`${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath(moveArgs.destination))}`
|
|
1809
|
+
], context);
|
|
1810
|
+
}
|
|
1440
1811
|
function registerFsTools(pi, ctx, surface) {
|
|
1441
1812
|
if (surface.delete) {
|
|
1442
1813
|
pi.registerTool({
|
|
@@ -1448,6 +1819,12 @@ function registerFsTools(pi, ctx, surface) {
|
|
|
1448
1819
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
1449
1820
|
const response = await callBridge(bridge, "delete_file", { file: params.filePath });
|
|
1450
1821
|
return textResult(`Deleted ${params.filePath}`, response);
|
|
1822
|
+
},
|
|
1823
|
+
renderCall(args, theme, context) {
|
|
1824
|
+
return renderFsCall("aft_delete", args, theme, context);
|
|
1825
|
+
},
|
|
1826
|
+
renderResult(result, _options, theme, context) {
|
|
1827
|
+
return renderFsResult("aft_delete", context.args, result, theme, context);
|
|
1451
1828
|
}
|
|
1452
1829
|
});
|
|
1453
1830
|
}
|
|
@@ -1464,6 +1841,12 @@ function registerFsTools(pi, ctx, surface) {
|
|
|
1464
1841
|
destination: params.destination
|
|
1465
1842
|
});
|
|
1466
1843
|
return textResult(`Moved ${params.filePath} → ${params.destination}`, response);
|
|
1844
|
+
},
|
|
1845
|
+
renderCall(args, theme, context) {
|
|
1846
|
+
return renderFsCall("aft_move", args, theme, context);
|
|
1847
|
+
},
|
|
1848
|
+
renderResult(result, _options, theme, context) {
|
|
1849
|
+
return renderFsResult("aft_move", context.args, result, theme, context);
|
|
1467
1850
|
}
|
|
1468
1851
|
});
|
|
1469
1852
|
}
|
|
@@ -1471,12 +1854,12 @@ function registerFsTools(pi, ctx, surface) {
|
|
|
1471
1854
|
|
|
1472
1855
|
// src/tools/hoisted.ts
|
|
1473
1856
|
import { stat } from "node:fs/promises";
|
|
1474
|
-
import { homedir as
|
|
1857
|
+
import { homedir as homedir6 } from "node:os";
|
|
1475
1858
|
import { resolve } from "node:path";
|
|
1476
1859
|
import {
|
|
1477
|
-
renderDiff
|
|
1860
|
+
renderDiff as renderDiff2
|
|
1478
1861
|
} from "@mariozechner/pi-coding-agent";
|
|
1479
|
-
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
1862
|
+
import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@mariozechner/pi-tui";
|
|
1480
1863
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
1481
1864
|
|
|
1482
1865
|
// src/tools/diff-format.ts
|
|
@@ -1783,15 +2166,15 @@ function formatDiagnosticsText(diagnostics) {
|
|
|
1783
2166
|
return JSON.stringify(diagnostics, null, 2);
|
|
1784
2167
|
}
|
|
1785
2168
|
}
|
|
1786
|
-
function
|
|
1787
|
-
return last instanceof
|
|
2169
|
+
function reuseText2(last) {
|
|
2170
|
+
return last instanceof Text2 ? last : new Text2("", 0, 0);
|
|
1788
2171
|
}
|
|
1789
|
-
function
|
|
1790
|
-
return last instanceof
|
|
2172
|
+
function reuseContainer2(last) {
|
|
2173
|
+
return last instanceof Container2 ? last : new Container2;
|
|
1791
2174
|
}
|
|
1792
2175
|
function renderMutationCall(toolName, filePath, theme, context) {
|
|
1793
|
-
const text =
|
|
1794
|
-
const pathDisplay = filePath ? theme.fg("accent",
|
|
2176
|
+
const text = reuseText2(context.lastComponent);
|
|
2177
|
+
const pathDisplay = filePath ? theme.fg("accent", shortenPath2(filePath)) : theme.fg("toolOutput", "...");
|
|
1795
2178
|
text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
|
|
1796
2179
|
return text;
|
|
1797
2180
|
}
|
|
@@ -1799,7 +2182,7 @@ function renderMutationResult(result, theme, context) {
|
|
|
1799
2182
|
if (context.isError) {
|
|
1800
2183
|
const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
|
|
1801
2184
|
`).trim();
|
|
1802
|
-
const text =
|
|
2185
|
+
const text = reuseText2(context.lastComponent);
|
|
1803
2186
|
text.setText(`
|
|
1804
2187
|
${theme.fg("error", errorText || "edit failed")}`);
|
|
1805
2188
|
return text;
|
|
@@ -1809,21 +2192,21 @@ ${theme.fg("error", errorText || "edit failed")}`);
|
|
|
1809
2192
|
if (!diff) {
|
|
1810
2193
|
const additions = details?.additions ?? 0;
|
|
1811
2194
|
const deletions = details?.deletions ?? 0;
|
|
1812
|
-
const text =
|
|
2195
|
+
const text = reuseText2(context.lastComponent);
|
|
1813
2196
|
const summary = theme.fg("success", `+${additions}/-${deletions}`);
|
|
1814
2197
|
const suffix = details?.truncated ? ` ${theme.fg("muted", "(diff truncated)")}` : "";
|
|
1815
2198
|
text.setText(`
|
|
1816
2199
|
${summary}${suffix}`);
|
|
1817
2200
|
return text;
|
|
1818
2201
|
}
|
|
1819
|
-
const container =
|
|
2202
|
+
const container = reuseContainer2(context.lastComponent);
|
|
1820
2203
|
container.clear();
|
|
1821
|
-
container.addChild(new
|
|
1822
|
-
container.addChild(new
|
|
2204
|
+
container.addChild(new Spacer2(1));
|
|
2205
|
+
container.addChild(new Text2(renderDiff2(diff), 1, 0));
|
|
1823
2206
|
return container;
|
|
1824
2207
|
}
|
|
1825
|
-
function
|
|
1826
|
-
const home =
|
|
2208
|
+
function shortenPath2(path2) {
|
|
2209
|
+
const home = homedir6();
|
|
1827
2210
|
if (path2.startsWith(home))
|
|
1828
2211
|
return `~${path2.slice(home.length)}`;
|
|
1829
2212
|
return path2;
|
|
@@ -1857,6 +2240,54 @@ var ImportParams = Type5.Object({
|
|
|
1857
2240
|
description: "Post-edit validation level (default: syntax)"
|
|
1858
2241
|
}))
|
|
1859
2242
|
});
|
|
2243
|
+
function buildImportSections(args, payload, theme) {
|
|
2244
|
+
const response = asRecord2(payload);
|
|
2245
|
+
if (!response)
|
|
2246
|
+
return [theme.fg("muted", "No import result.")];
|
|
2247
|
+
if (response.dry_run === true) {
|
|
2248
|
+
return [
|
|
2249
|
+
theme.fg("warning", `[dry run] ${args.op}`),
|
|
2250
|
+
asString(response.diff) || theme.fg("muted", "No diff available.")
|
|
2251
|
+
];
|
|
2252
|
+
}
|
|
2253
|
+
if (args.op === "organize") {
|
|
2254
|
+
const groups = asRecords(response.groups);
|
|
2255
|
+
const groupText = groups.length > 0 ? groups.map((group) => `${asString(group.name) ?? "unknown"}: ${asNumber(group.count) ?? 0}`).join(" · ") : "No imports found";
|
|
2256
|
+
return [
|
|
2257
|
+
`${theme.fg("success", "organized")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
|
|
2258
|
+
`${theme.fg("muted", "groups")} ${groupText}`,
|
|
2259
|
+
`${theme.fg("muted", "duplicates removed")} ${asNumber(response.removed_duplicates) ?? 0}`
|
|
2260
|
+
];
|
|
2261
|
+
}
|
|
2262
|
+
if (args.op === "add") {
|
|
2263
|
+
const moduleName = asString(response.module) ?? args.module ?? "(module)";
|
|
2264
|
+
const status = response.already_present === true ? theme.fg("warning", "already present") : theme.fg("success", "added");
|
|
2265
|
+
return [
|
|
2266
|
+
`${status} ${theme.fg("accent", moduleName)}`,
|
|
2267
|
+
`${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
|
|
2268
|
+
`${theme.fg("muted", "group")} ${asString(response.group) ?? "—"}`
|
|
2269
|
+
];
|
|
2270
|
+
}
|
|
2271
|
+
return [
|
|
2272
|
+
`${theme.fg("success", "removed")} ${theme.fg("accent", asString(response.module) ?? args.module ?? "(module)")}`,
|
|
2273
|
+
`${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
|
|
2274
|
+
args.removeName ? `${theme.fg("muted", "name")} ${args.removeName}` : `${theme.fg("muted", "scope")} entire import`
|
|
2275
|
+
];
|
|
2276
|
+
}
|
|
2277
|
+
function renderImportCall(args, theme, context) {
|
|
2278
|
+
const summary = [
|
|
2279
|
+
theme.fg("accent", args.op),
|
|
2280
|
+
accentPath(theme, args.filePath),
|
|
2281
|
+
args.module ? theme.fg("toolOutput", args.module) : undefined
|
|
2282
|
+
].filter(Boolean).join(" ");
|
|
2283
|
+
return renderToolCall("import", summary, theme, context);
|
|
2284
|
+
}
|
|
2285
|
+
function renderImportResult(result, args, theme, context) {
|
|
2286
|
+
if (context.isError)
|
|
2287
|
+
return renderErrorResult(result, "import failed", theme, context);
|
|
2288
|
+
const payload = extractStructuredPayload(result);
|
|
2289
|
+
return renderSections(buildImportSections(args, payload, theme), context);
|
|
2290
|
+
}
|
|
1860
2291
|
function registerImportTools(pi, ctx) {
|
|
1861
2292
|
pi.registerTool({
|
|
1862
2293
|
name: "aft_import",
|
|
@@ -1890,6 +2321,12 @@ function registerImportTools(pi, ctx) {
|
|
|
1890
2321
|
req.validate = params.validate;
|
|
1891
2322
|
const response = await callBridge(bridge, commandMap[params.op], req);
|
|
1892
2323
|
return textResult(JSON.stringify(response, null, 2));
|
|
2324
|
+
},
|
|
2325
|
+
renderCall(args, theme, context) {
|
|
2326
|
+
return renderImportCall(args, theme, context);
|
|
2327
|
+
},
|
|
2328
|
+
renderResult(result, _options, theme, context) {
|
|
2329
|
+
return renderImportResult(result, context.args, theme, context);
|
|
1893
2330
|
}
|
|
1894
2331
|
});
|
|
1895
2332
|
}
|
|
@@ -1909,6 +2346,51 @@ var LspDiagnosticsParams = Type6.Object({
|
|
|
1909
2346
|
description: "Wait N ms for fresh diagnostics (max 10000, default: 0)"
|
|
1910
2347
|
}))
|
|
1911
2348
|
});
|
|
2349
|
+
function buildDiagnosticsSections(payload, theme) {
|
|
2350
|
+
const response = asRecord2(payload);
|
|
2351
|
+
if (!response)
|
|
2352
|
+
return [theme.fg("muted", "No diagnostics available.")];
|
|
2353
|
+
const diagnostics = asRecords(response.diagnostics);
|
|
2354
|
+
const total = asNumber(response.total) ?? diagnostics.length;
|
|
2355
|
+
const filesWithErrors = asNumber(response.files_with_errors) ?? distinctCount(diagnostics.filter((diag) => asString(diag.severity) === "error").map((diag) => asString(diag.file)));
|
|
2356
|
+
const filesCount = distinctCount(diagnostics.map((diag) => asString(diag.file)));
|
|
2357
|
+
const sections = [
|
|
2358
|
+
`${theme.fg(total > 0 ? "warning" : "success", `${total} diagnostic${total === 1 ? "" : "s"}`)} ${theme.fg("muted", `across ${filesCount} file${filesCount === 1 ? "" : "s"}, ${filesWithErrors} error file${filesWithErrors === 1 ? "" : "s"}`)}`
|
|
2359
|
+
];
|
|
2360
|
+
if (diagnostics.length === 0) {
|
|
2361
|
+
sections.push(theme.fg("muted", "No diagnostics found."));
|
|
2362
|
+
return sections;
|
|
2363
|
+
}
|
|
2364
|
+
const grouped = groupByFile(diagnostics, (diag) => asString(diag.file));
|
|
2365
|
+
for (const [file, fileDiagnostics] of grouped.entries()) {
|
|
2366
|
+
const lines = [theme.fg("accent", shortenPath(file))];
|
|
2367
|
+
fileDiagnostics.forEach((diagnostic) => {
|
|
2368
|
+
const severity = asString(diagnostic.severity) ?? "information";
|
|
2369
|
+
const line = asNumber(diagnostic.line) ?? 0;
|
|
2370
|
+
const column = asNumber(diagnostic.column) ?? 0;
|
|
2371
|
+
const code = asString(diagnostic.code);
|
|
2372
|
+
const message = asString(diagnostic.message) ?? "(no message)";
|
|
2373
|
+
const location = `${line}:${column}`;
|
|
2374
|
+
lines.push(` ${severityBadge(theme, severity)} ${location}${code ? ` ${theme.fg("muted", code)}` : ""} ${message}`);
|
|
2375
|
+
});
|
|
2376
|
+
sections.push(lines.join(`
|
|
2377
|
+
`));
|
|
2378
|
+
}
|
|
2379
|
+
return sections;
|
|
2380
|
+
}
|
|
2381
|
+
function renderDiagnosticsCall(args, theme, context) {
|
|
2382
|
+
const target = args.filePath ?? args.directory;
|
|
2383
|
+
const summary = [
|
|
2384
|
+
target ? accentPath(theme, target) : undefined,
|
|
2385
|
+
args.severity ? theme.fg("toolOutput", args.severity) : undefined
|
|
2386
|
+
].filter(Boolean).join(" ");
|
|
2387
|
+
return renderToolCall("lsp diagnostics", summary, theme, context);
|
|
2388
|
+
}
|
|
2389
|
+
function renderDiagnosticsResult(result, theme, context) {
|
|
2390
|
+
if (context.isError)
|
|
2391
|
+
return renderErrorResult(result, "lsp diagnostics failed", theme, context);
|
|
2392
|
+
return renderSections(buildDiagnosticsSections(extractStructuredPayload(result), theme), context);
|
|
2393
|
+
}
|
|
1912
2394
|
function registerLspTools(pi, ctx) {
|
|
1913
2395
|
pi.registerTool({
|
|
1914
2396
|
name: "lsp_diagnostics",
|
|
@@ -1933,6 +2415,12 @@ function registerLspTools(pi, ctx) {
|
|
|
1933
2415
|
req.wait_ms = params.waitMs;
|
|
1934
2416
|
const response = await callBridge(bridge, "lsp_diagnostics", req);
|
|
1935
2417
|
return textResult(JSON.stringify(response, null, 2));
|
|
2418
|
+
},
|
|
2419
|
+
renderCall(args, theme, context) {
|
|
2420
|
+
return renderDiagnosticsCall(args, theme, context);
|
|
2421
|
+
},
|
|
2422
|
+
renderResult(result, _options, theme, context) {
|
|
2423
|
+
return renderDiagnosticsResult(result, theme, context);
|
|
1936
2424
|
}
|
|
1937
2425
|
});
|
|
1938
2426
|
}
|
|
@@ -1949,6 +2437,121 @@ var NavigateParams = Type7.Object({
|
|
|
1949
2437
|
depth: Type7.Optional(Type7.Number({ description: "Max traversal depth" })),
|
|
1950
2438
|
expression: Type7.Optional(Type7.String({ description: "Expression to track (required for trace_data)" }))
|
|
1951
2439
|
});
|
|
2440
|
+
function treeLine(depth, text) {
|
|
2441
|
+
return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
|
|
2442
|
+
}
|
|
2443
|
+
function renderCallTreeNode(node, depth, lines) {
|
|
2444
|
+
const name = asString(node.name) ?? "(unknown)";
|
|
2445
|
+
const file = shortenPath(asString(node.file) ?? "(unknown file)");
|
|
2446
|
+
const line = asNumber(node.line);
|
|
2447
|
+
lines.push(treeLine(depth, `${name} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
|
|
2448
|
+
asRecords(node.children).forEach((child) => {
|
|
2449
|
+
renderCallTreeNode(child, depth + 1, lines);
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
function renderTracePath(path2, index, lines) {
|
|
2453
|
+
lines.push(`Path ${index + 1}`);
|
|
2454
|
+
asRecords(path2.hops).forEach((hop, hopIndex) => {
|
|
2455
|
+
const symbol = asString(hop.symbol) ?? "(unknown)";
|
|
2456
|
+
const file = shortenPath(asString(hop.file) ?? "(unknown file)");
|
|
2457
|
+
const line = asNumber(hop.line);
|
|
2458
|
+
const entry = hop.is_entry_point === true ? " [entry]" : "";
|
|
2459
|
+
lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
function buildNavigateSections(args, payload, theme) {
|
|
2463
|
+
const response = asRecord2(payload);
|
|
2464
|
+
if (!response)
|
|
2465
|
+
return [theme.fg("muted", "No navigation result.")];
|
|
2466
|
+
if (args.op === "call_tree") {
|
|
2467
|
+
const lines = [];
|
|
2468
|
+
renderCallTreeNode(response, 0, lines);
|
|
2469
|
+
return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
|
|
2470
|
+
}
|
|
2471
|
+
if (args.op === "callers") {
|
|
2472
|
+
const groups = asRecords(response.callers);
|
|
2473
|
+
const sections2 = [
|
|
2474
|
+
`${theme.fg("success", `${asNumber(response.total_callers) ?? 0} caller${(asNumber(response.total_callers) ?? 0) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${groups.length} file group${groups.length === 1 ? "" : "s"}`)}`
|
|
2475
|
+
];
|
|
2476
|
+
groups.forEach((group) => {
|
|
2477
|
+
const file = shortenPath(asString(group.file) ?? "(unknown file)");
|
|
2478
|
+
const lines = [theme.fg("accent", file)];
|
|
2479
|
+
asRecords(group.callers).forEach((caller) => {
|
|
2480
|
+
lines.push(` ↳ ${asString(caller.symbol) ?? "(unknown)"} ${theme.fg("muted", `line ${asNumber(caller.line) ?? "?"}`)}`);
|
|
2481
|
+
});
|
|
2482
|
+
sections2.push(lines.join(`
|
|
2483
|
+
`));
|
|
2484
|
+
});
|
|
2485
|
+
return sections2;
|
|
2486
|
+
}
|
|
2487
|
+
if (args.op === "trace_to") {
|
|
2488
|
+
const paths = asRecords(response.paths);
|
|
2489
|
+
const sections2 = [
|
|
2490
|
+
`${theme.fg("success", `${asNumber(response.total_paths) ?? paths.length} path${(asNumber(response.total_paths) ?? paths.length) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${asNumber(response.entry_points_found) ?? 0} entry point${(asNumber(response.entry_points_found) ?? 0) === 1 ? "" : "s"}`)}`
|
|
2491
|
+
];
|
|
2492
|
+
if (paths.length === 0)
|
|
2493
|
+
sections2.push(theme.fg("muted", "No entry paths found."));
|
|
2494
|
+
paths.forEach((path2, index) => {
|
|
2495
|
+
const lines = [];
|
|
2496
|
+
renderTracePath(path2, index, lines);
|
|
2497
|
+
sections2.push(lines.join(`
|
|
2498
|
+
`));
|
|
2499
|
+
});
|
|
2500
|
+
return sections2;
|
|
2501
|
+
}
|
|
2502
|
+
if (args.op === "impact") {
|
|
2503
|
+
const callers = asRecords(response.callers);
|
|
2504
|
+
const sections2 = [
|
|
2505
|
+
`${theme.fg("warning", `${asNumber(response.total_affected) ?? callers.length} affected call site${(asNumber(response.total_affected) ?? callers.length) === 1 ? "" : "s"}`)} ${theme.fg("muted", `${asNumber(response.affected_files) ?? 0} file${(asNumber(response.affected_files) ?? 0) === 1 ? "" : "s"}`)}`
|
|
2506
|
+
];
|
|
2507
|
+
if (callers.length === 0)
|
|
2508
|
+
sections2.push(theme.fg("muted", "No impacted callers found."));
|
|
2509
|
+
callers.forEach((caller) => {
|
|
2510
|
+
const file = shortenPath(asString(caller.caller_file) ?? "(unknown file)");
|
|
2511
|
+
const symbol = asString(caller.caller_symbol) ?? "(unknown)";
|
|
2512
|
+
const line = asNumber(caller.line) ?? 0;
|
|
2513
|
+
const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
|
|
2514
|
+
const expression = asString(caller.call_expression);
|
|
2515
|
+
const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
|
|
2516
|
+
sections2.push([
|
|
2517
|
+
`${theme.fg("accent", file)}:${line}`,
|
|
2518
|
+
` ↳ ${symbol}${entry}`,
|
|
2519
|
+
expression ? ` ${theme.fg("muted", expression)}` : undefined,
|
|
2520
|
+
params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
|
|
2521
|
+
].filter(Boolean).join(`
|
|
2522
|
+
`));
|
|
2523
|
+
});
|
|
2524
|
+
return sections2;
|
|
2525
|
+
}
|
|
2526
|
+
const hops = asRecords(response.hops);
|
|
2527
|
+
const sections = [
|
|
2528
|
+
`${theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`)} ${asBoolean(response.depth_limited) ? theme.fg("warning", "(depth limited)") : ""}`.trim()
|
|
2529
|
+
];
|
|
2530
|
+
if (hops.length === 0)
|
|
2531
|
+
sections.push(theme.fg("muted", "No data-flow hops found."));
|
|
2532
|
+
hops.forEach((hop, index) => {
|
|
2533
|
+
const file = shortenPath(asString(hop.file) ?? "(unknown file)");
|
|
2534
|
+
const symbol = asString(hop.symbol) ?? "(unknown)";
|
|
2535
|
+
const variable = asString(hop.variable) ?? "(unknown)";
|
|
2536
|
+
const line = asNumber(hop.line) ?? 0;
|
|
2537
|
+
const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
|
|
2538
|
+
sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
|
|
2539
|
+
});
|
|
2540
|
+
return sections;
|
|
2541
|
+
}
|
|
2542
|
+
function renderNavigateCall(args, theme, context) {
|
|
2543
|
+
const summary = [
|
|
2544
|
+
theme.fg("accent", args.op),
|
|
2545
|
+
accentPath(theme, args.filePath),
|
|
2546
|
+
theme.fg("toolOutput", args.symbol)
|
|
2547
|
+
].filter(Boolean).join(" ");
|
|
2548
|
+
return renderToolCall("navigate", summary, theme, context);
|
|
2549
|
+
}
|
|
2550
|
+
function renderNavigateResult(result, args, theme, context) {
|
|
2551
|
+
if (context.isError)
|
|
2552
|
+
return renderErrorResult(result, "navigate failed", theme, context);
|
|
2553
|
+
return renderSections(buildNavigateSections(args, extractStructuredPayload(result), theme), context);
|
|
2554
|
+
}
|
|
1952
2555
|
function registerNavigateTool(pi, ctx) {
|
|
1953
2556
|
pi.registerTool({
|
|
1954
2557
|
name: "aft_navigate",
|
|
@@ -1971,6 +2574,12 @@ function registerNavigateTool(pi, ctx) {
|
|
|
1971
2574
|
req.expression = params.expression;
|
|
1972
2575
|
const response = await callBridge(bridge, params.op, req);
|
|
1973
2576
|
return textResult(JSON.stringify(response, null, 2));
|
|
2577
|
+
},
|
|
2578
|
+
renderCall(args, theme, context) {
|
|
2579
|
+
return renderNavigateCall(args, theme, context);
|
|
2580
|
+
},
|
|
2581
|
+
renderResult(result, _options, theme, context) {
|
|
2582
|
+
return renderNavigateResult(result, context.args, theme, context);
|
|
1974
2583
|
}
|
|
1975
2584
|
});
|
|
1976
2585
|
}
|
|
@@ -2083,6 +2692,71 @@ var ZoomParams = Type8.Object({
|
|
|
2083
2692
|
symbols: Type8.Optional(Type8.Array(Type8.String(), { description: "Multiple symbols — returns array of matches" })),
|
|
2084
2693
|
contextLines: Type8.Optional(Type8.Number({ description: "Lines of context before/after (default: 3)" }))
|
|
2085
2694
|
});
|
|
2695
|
+
function buildOutlineSections(text, theme) {
|
|
2696
|
+
const trimmed = text.trim();
|
|
2697
|
+
if (!trimmed)
|
|
2698
|
+
return [theme.fg("muted", "No outline available.")];
|
|
2699
|
+
const lines = trimmed.split(`
|
|
2700
|
+
`);
|
|
2701
|
+
if (lines.length === 1)
|
|
2702
|
+
return [theme.fg("accent", lines[0])];
|
|
2703
|
+
return [theme.fg("accent", lines[0]), lines.slice(1).join(`
|
|
2704
|
+
`)];
|
|
2705
|
+
}
|
|
2706
|
+
function buildZoomSections(args, payload, theme) {
|
|
2707
|
+
const items = Array.isArray(payload) ? payload : payload ? [payload] : [];
|
|
2708
|
+
if (items.length === 0)
|
|
2709
|
+
return [theme.fg("muted", "No zoom result available.")];
|
|
2710
|
+
return items.map((item) => {
|
|
2711
|
+
const record = asRecord2(item);
|
|
2712
|
+
if (!record)
|
|
2713
|
+
return theme.fg("muted", "No zoom result available.");
|
|
2714
|
+
const name = asString(record.name) ?? "(unknown symbol)";
|
|
2715
|
+
const kind = asString(record.kind) ?? "symbol";
|
|
2716
|
+
const range = asRecord2(record.range);
|
|
2717
|
+
const startLine = range && typeof range.start_line === "number" ? range.start_line : undefined;
|
|
2718
|
+
const endLine = range && typeof range.end_line === "number" ? range.end_line : undefined;
|
|
2719
|
+
const location = startLine !== undefined ? `${shortenPath(args.filePath)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath(args.filePath);
|
|
2720
|
+
const lines = [`${theme.fg("accent", name)} ${theme.fg("muted", `[${kind}] ${location}`)}`];
|
|
2721
|
+
const content = asString(record.content);
|
|
2722
|
+
if (content) {
|
|
2723
|
+
lines.push(content.split(`
|
|
2724
|
+
`).map((line) => ` ${line}`).join(`
|
|
2725
|
+
`));
|
|
2726
|
+
}
|
|
2727
|
+
const annotations = asRecord2(record.annotations);
|
|
2728
|
+
const callsOut = annotations ? asRecords(annotations.calls_out) : [];
|
|
2729
|
+
const calledBy = annotations ? asRecords(annotations.called_by) : [];
|
|
2730
|
+
if (callsOut.length > 0) {
|
|
2731
|
+
lines.push(`${theme.fg("muted", "calls out")}`, callsOut.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
|
|
2732
|
+
`));
|
|
2733
|
+
}
|
|
2734
|
+
if (calledBy.length > 0) {
|
|
2735
|
+
lines.push(`${theme.fg("muted", "called by")}`, calledBy.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
|
|
2736
|
+
`));
|
|
2737
|
+
}
|
|
2738
|
+
return lines.join(`
|
|
2739
|
+
`);
|
|
2740
|
+
}).filter(Boolean);
|
|
2741
|
+
}
|
|
2742
|
+
function renderOutlineCall(args, theme, context) {
|
|
2743
|
+
const summary = args.filePath ? accentPath(theme, args.filePath) : args.directory ? `${theme.fg("muted", "dir")} ${accentPath(theme, args.directory)}` : args.files && args.files.length > 0 ? theme.fg("accent", `${args.files.length} files`) : undefined;
|
|
2744
|
+
return renderToolCall("outline", summary, theme, context);
|
|
2745
|
+
}
|
|
2746
|
+
function renderOutlineResult(result, theme, context) {
|
|
2747
|
+
if (context.isError)
|
|
2748
|
+
return renderErrorResult(result, "outline failed", theme, context);
|
|
2749
|
+
return renderSections(buildOutlineSections(collectTextContent(result), theme), context);
|
|
2750
|
+
}
|
|
2751
|
+
function renderZoomCall(args, theme, context) {
|
|
2752
|
+
const target = args.symbol ? theme.fg("toolOutput", args.symbol) : args.symbols && args.symbols.length > 0 ? theme.fg("toolOutput", `${args.symbols.length} symbols`) : theme.fg("toolOutput", "lines");
|
|
2753
|
+
return renderToolCall("zoom", `${accentPath(theme, args.filePath)} ${target}`, theme, context);
|
|
2754
|
+
}
|
|
2755
|
+
function renderZoomResult(result, args, theme, context) {
|
|
2756
|
+
if (context.isError)
|
|
2757
|
+
return renderErrorResult(result, "zoom failed", theme, context);
|
|
2758
|
+
return renderSections(buildZoomSections(args, extractStructuredPayload(result), theme), context);
|
|
2759
|
+
}
|
|
2086
2760
|
function registerReadingTools(pi, ctx, surface) {
|
|
2087
2761
|
if (surface.outline) {
|
|
2088
2762
|
pi.registerTool({
|
|
@@ -2126,6 +2800,12 @@ function registerReadingTools(pi, ctx, surface) {
|
|
|
2126
2800
|
}
|
|
2127
2801
|
const response = await callBridge(bridge, "outline", { file: params.filePath });
|
|
2128
2802
|
return textResult(response.text ?? "");
|
|
2803
|
+
},
|
|
2804
|
+
renderCall(args, theme, context) {
|
|
2805
|
+
return renderOutlineCall(args, theme, context);
|
|
2806
|
+
},
|
|
2807
|
+
renderResult(result, _options, theme, context) {
|
|
2808
|
+
return renderOutlineResult(result, theme, context);
|
|
2129
2809
|
}
|
|
2130
2810
|
});
|
|
2131
2811
|
}
|
|
@@ -2153,6 +2833,12 @@ function registerReadingTools(pi, ctx, surface) {
|
|
|
2153
2833
|
req.context_lines = params.contextLines;
|
|
2154
2834
|
const response = await callBridge(bridge, "zoom", req);
|
|
2155
2835
|
return textResult(JSON.stringify(response, null, 2));
|
|
2836
|
+
},
|
|
2837
|
+
renderCall(args, theme, context) {
|
|
2838
|
+
return renderZoomCall(args, theme, context);
|
|
2839
|
+
},
|
|
2840
|
+
renderResult(result, _options, theme, context) {
|
|
2841
|
+
return renderZoomResult(result, context.args, theme, context);
|
|
2156
2842
|
}
|
|
2157
2843
|
});
|
|
2158
2844
|
}
|
|
@@ -2173,6 +2859,63 @@ var RefactorParams = Type9.Object({
|
|
|
2173
2859
|
callSiteLine: Type9.Optional(Type9.Number({ description: "1-based call site line (for inline)" })),
|
|
2174
2860
|
dryRun: Type9.Optional(Type9.Boolean({ description: "Preview as diff" }))
|
|
2175
2861
|
});
|
|
2862
|
+
function buildRefactorSections(args, payload, theme) {
|
|
2863
|
+
const response = asRecord2(payload);
|
|
2864
|
+
if (!response)
|
|
2865
|
+
return [theme.fg("muted", "No refactor result.")];
|
|
2866
|
+
if (response.dry_run === true) {
|
|
2867
|
+
const diffs = asRecords(response.diffs);
|
|
2868
|
+
const sections = [theme.fg("warning", `[dry run] ${args.op}`)];
|
|
2869
|
+
if (diffs.length === 0) {
|
|
2870
|
+
sections.push(theme.fg("muted", "No diff available."));
|
|
2871
|
+
return sections;
|
|
2872
|
+
}
|
|
2873
|
+
diffs.forEach((diff) => {
|
|
2874
|
+
const file = shortenPath(asString(diff.file) ?? "(unknown file)");
|
|
2875
|
+
const rendered = renderUnifiedDiff(asString(diff.diff) ?? "") || theme.fg("muted", "No diff available.");
|
|
2876
|
+
sections.push(`${theme.fg("accent", file)}
|
|
2877
|
+
${rendered}`);
|
|
2878
|
+
});
|
|
2879
|
+
return sections;
|
|
2880
|
+
}
|
|
2881
|
+
if (args.op === "move") {
|
|
2882
|
+
const results = asRecords(response.results);
|
|
2883
|
+
return [
|
|
2884
|
+
`${theme.fg("success", "moved symbol")} ${theme.fg("toolOutput", args.symbol ?? "(symbol)")}`,
|
|
2885
|
+
`${theme.fg("muted", "files modified")} ${asNumber(response.files_modified) ?? results.length}`,
|
|
2886
|
+
`${theme.fg("muted", "consumers updated")} ${asNumber(response.consumers_updated) ?? 0}`,
|
|
2887
|
+
results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath(asString(entry.file) ?? "(unknown file)")}`).join(`
|
|
2888
|
+
`) : theme.fg("muted", "No files reported.")
|
|
2889
|
+
];
|
|
2890
|
+
}
|
|
2891
|
+
if (args.op === "extract") {
|
|
2892
|
+
return [
|
|
2893
|
+
`${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString(response.name) ?? args.name ?? "(function)")}`,
|
|
2894
|
+
`${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath))}`,
|
|
2895
|
+
`${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
|
|
2896
|
+
`${theme.fg("muted", "return type")} ${asString(response.return_type) ?? "unknown"}`
|
|
2897
|
+
];
|
|
2898
|
+
}
|
|
2899
|
+
return [
|
|
2900
|
+
`${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString(response.symbol) ?? args.symbol ?? "(symbol)")}`,
|
|
2901
|
+
`${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath))}`,
|
|
2902
|
+
`${theme.fg("muted", "context")} ${asString(response.call_context) ?? "unknown"}`,
|
|
2903
|
+
`${theme.fg("muted", "substitutions")} ${asNumber(response.substitutions) ?? 0}`
|
|
2904
|
+
];
|
|
2905
|
+
}
|
|
2906
|
+
function renderRefactorCall(args, theme, context) {
|
|
2907
|
+
const summary = [
|
|
2908
|
+
theme.fg("accent", args.op),
|
|
2909
|
+
accentPath(theme, args.filePath),
|
|
2910
|
+
args.symbol ? theme.fg("toolOutput", args.symbol) : undefined
|
|
2911
|
+
].filter(Boolean).join(" ");
|
|
2912
|
+
return renderToolCall("refactor", summary, theme, context);
|
|
2913
|
+
}
|
|
2914
|
+
function renderRefactorResult(result, args, theme, context) {
|
|
2915
|
+
if (context.isError)
|
|
2916
|
+
return renderErrorResult(result, "refactor failed", theme, context);
|
|
2917
|
+
return renderSections(buildRefactorSections(args, extractStructuredPayload(result), theme), context);
|
|
2918
|
+
}
|
|
2176
2919
|
function registerRefactorTool(pi, ctx) {
|
|
2177
2920
|
pi.registerTool({
|
|
2178
2921
|
name: "aft_refactor",
|
|
@@ -2206,6 +2949,12 @@ function registerRefactorTool(pi, ctx) {
|
|
|
2206
2949
|
req.dry_run = params.dryRun;
|
|
2207
2950
|
const response = await callBridge(bridge, commandMap[params.op], req);
|
|
2208
2951
|
return textResult(JSON.stringify(response, null, 2));
|
|
2952
|
+
},
|
|
2953
|
+
renderCall(args, theme, context) {
|
|
2954
|
+
return renderRefactorCall(args, theme, context);
|
|
2955
|
+
},
|
|
2956
|
+
renderResult(result, _options, theme, context) {
|
|
2957
|
+
return renderRefactorResult(result, context.args, theme, context);
|
|
2209
2958
|
}
|
|
2210
2959
|
});
|
|
2211
2960
|
}
|
|
@@ -2223,6 +2972,78 @@ var SafetyParams = Type10.Object({
|
|
|
2223
2972
|
description: "Specific files for checkpoint (optional, defaults to all tracked)"
|
|
2224
2973
|
}))
|
|
2225
2974
|
});
|
|
2975
|
+
function buildSafetySections(args, payload, theme) {
|
|
2976
|
+
const response = asRecord2(payload);
|
|
2977
|
+
if (!response)
|
|
2978
|
+
return [theme.fg("muted", "No safety result.")];
|
|
2979
|
+
if (args.op === "undo") {
|
|
2980
|
+
return [
|
|
2981
|
+
`${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath(asString(response.path) ?? args.filePath ?? "(file)"))}`,
|
|
2982
|
+
`${theme.fg("muted", "backup")} ${asString(response.backup_id) ?? "—"}`
|
|
2983
|
+
];
|
|
2984
|
+
}
|
|
2985
|
+
if (args.op === "history") {
|
|
2986
|
+
const entries = asRecords(response.entries);
|
|
2987
|
+
const sections2 = [
|
|
2988
|
+
theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath ?? "(file)"))
|
|
2989
|
+
];
|
|
2990
|
+
if (entries.length === 0) {
|
|
2991
|
+
sections2.push(theme.fg("muted", "No history entries."));
|
|
2992
|
+
return sections2;
|
|
2993
|
+
}
|
|
2994
|
+
sections2.push(entries.map((entry, index) => {
|
|
2995
|
+
const backupId = asString(entry.backup_id) ?? `entry-${index + 1}`;
|
|
2996
|
+
const timestamp = formatTimestamp(entry.timestamp) ?? "unknown time";
|
|
2997
|
+
const description = asString(entry.description) ?? "";
|
|
2998
|
+
return `${index + 1}. ${backupId} ${theme.fg("muted", timestamp)}${description ? `
|
|
2999
|
+
${description}` : ""}`;
|
|
3000
|
+
}).join(`
|
|
3001
|
+
`));
|
|
3002
|
+
return sections2;
|
|
3003
|
+
}
|
|
3004
|
+
if (args.op === "checkpoint") {
|
|
3005
|
+
const skipped = asRecords(response.skipped);
|
|
3006
|
+
return [
|
|
3007
|
+
`${theme.fg("success", "checkpoint created")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
|
|
3008
|
+
`${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`,
|
|
3009
|
+
skipped.length > 0 ? `${theme.fg("warning", "skipped")}
|
|
3010
|
+
${skipped.map((entry) => ` ↳ ${shortenPath(asString(entry.file) ?? "(file)")}: ${asString(entry.error) ?? "unknown error"}`).join(`
|
|
3011
|
+
`)}` : theme.fg("muted", "No skipped files.")
|
|
3012
|
+
];
|
|
3013
|
+
}
|
|
3014
|
+
if (args.op === "restore") {
|
|
3015
|
+
return [
|
|
3016
|
+
`${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
|
|
3017
|
+
`${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`
|
|
3018
|
+
];
|
|
3019
|
+
}
|
|
3020
|
+
const checkpoints = asRecords(response.checkpoints);
|
|
3021
|
+
const sections = [
|
|
3022
|
+
theme.fg("accent", `${checkpoints.length} checkpoint${checkpoints.length === 1 ? "" : "s"}`)
|
|
3023
|
+
];
|
|
3024
|
+
if (checkpoints.length === 0) {
|
|
3025
|
+
sections.push(theme.fg("muted", "No checkpoints saved."));
|
|
3026
|
+
return sections;
|
|
3027
|
+
}
|
|
3028
|
+
sections.push(checkpoints.map((checkpoint, index) => {
|
|
3029
|
+
const name = asString(checkpoint.name) ?? `checkpoint-${index + 1}`;
|
|
3030
|
+
const count = asNumber(checkpoint.file_count) ?? 0;
|
|
3031
|
+
const created = formatTimestamp(checkpoint.created_at) ?? "unknown time";
|
|
3032
|
+
return `${index + 1}. ${name} ${theme.fg("muted", `${count} file${count === 1 ? "" : "s"} · ${created}`)}`;
|
|
3033
|
+
}).join(`
|
|
3034
|
+
`));
|
|
3035
|
+
return sections;
|
|
3036
|
+
}
|
|
3037
|
+
function renderSafetyCall(args, theme, context) {
|
|
3038
|
+
const target = args.filePath ?? args.name;
|
|
3039
|
+
const summary = [theme.fg("accent", args.op), target ? accentPath(theme, target) : undefined].filter(Boolean).join(" ");
|
|
3040
|
+
return renderToolCall("safety", summary, theme, context);
|
|
3041
|
+
}
|
|
3042
|
+
function renderSafetyResult(result, args, theme, context) {
|
|
3043
|
+
if (context.isError)
|
|
3044
|
+
return renderErrorResult(result, "safety failed", theme, context);
|
|
3045
|
+
return renderSections(buildSafetySections(args, extractStructuredPayload(result), theme), context);
|
|
3046
|
+
}
|
|
2226
3047
|
function registerSafetyTool(pi, ctx) {
|
|
2227
3048
|
pi.registerTool({
|
|
2228
3049
|
name: "aft_safety",
|
|
@@ -2261,6 +3082,12 @@ function registerSafetyTool(pi, ctx) {
|
|
|
2261
3082
|
}
|
|
2262
3083
|
const response = await callBridge(bridge, commandMap[params.op], req);
|
|
2263
3084
|
return textResult(JSON.stringify(response, null, 2));
|
|
3085
|
+
},
|
|
3086
|
+
renderCall(args, theme, context) {
|
|
3087
|
+
return renderSafetyCall(args, theme, context);
|
|
3088
|
+
},
|
|
3089
|
+
renderResult(result, _options, theme, context) {
|
|
3090
|
+
return renderSafetyResult(result, context.args, theme, context);
|
|
2264
3091
|
}
|
|
2265
3092
|
});
|
|
2266
3093
|
}
|
|
@@ -2271,6 +3098,53 @@ var SearchParams2 = Type11.Object({
|
|
|
2271
3098
|
query: Type11.String({ description: "Natural-language description of the code to find" }),
|
|
2272
3099
|
topK: Type11.Optional(Type11.Number({ description: "Maximum number of results (default: 10, max: 100)" }))
|
|
2273
3100
|
});
|
|
3101
|
+
function buildSemanticSections(args, payload, theme) {
|
|
3102
|
+
const response = asRecord2(payload);
|
|
3103
|
+
if (!response)
|
|
3104
|
+
return [theme.fg("muted", "No semantic search result.")];
|
|
3105
|
+
const status = asString(response.status) ?? "unknown";
|
|
3106
|
+
const sections = [
|
|
3107
|
+
`${theme.fg(status === "ready" ? "success" : "warning", `index: ${status}`)} ${theme.fg("muted", `query=${JSON.stringify(args.query)} topK=${args.topK ?? 10}`)}`
|
|
3108
|
+
];
|
|
3109
|
+
if (status !== "ready") {
|
|
3110
|
+
sections.push(asString(response.text) ?? theme.fg("muted", "Semantic index is not ready."));
|
|
3111
|
+
return sections;
|
|
3112
|
+
}
|
|
3113
|
+
const results = asRecords(response.results);
|
|
3114
|
+
if (results.length === 0) {
|
|
3115
|
+
sections.push(theme.fg("muted", "No semantic matches found."));
|
|
3116
|
+
return sections;
|
|
3117
|
+
}
|
|
3118
|
+
const grouped = groupByFile(results, (result) => asString(result.file));
|
|
3119
|
+
for (const [file, fileResults] of grouped.entries()) {
|
|
3120
|
+
const lines = [theme.fg("accent", shortenPath(file))];
|
|
3121
|
+
fileResults.forEach((result) => {
|
|
3122
|
+
const score = asNumber(result.score);
|
|
3123
|
+
const startLine = asNumber(result.start_line);
|
|
3124
|
+
const endLine = asNumber(result.end_line);
|
|
3125
|
+
const range = startLine !== undefined ? `${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : "?";
|
|
3126
|
+
const kind = asString(result.kind) ?? "symbol";
|
|
3127
|
+
const name = asString(result.name) ?? "(unknown)";
|
|
3128
|
+
lines.push(` ↳ ${name} ${theme.fg("muted", `[${kind}] lines ${range}${score !== undefined ? ` score ${score.toFixed(3)}` : ""}`)}`);
|
|
3129
|
+
const snippet = asString(result.snippet);
|
|
3130
|
+
if (snippet) {
|
|
3131
|
+
lines.push(...snippet.split(`
|
|
3132
|
+
`).map((line) => ` ${line}`));
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
sections.push(lines.join(`
|
|
3136
|
+
`));
|
|
3137
|
+
}
|
|
3138
|
+
return sections;
|
|
3139
|
+
}
|
|
3140
|
+
function renderSemanticCall(args, theme, context) {
|
|
3141
|
+
return renderToolCall("semantic search", theme.fg("toolOutput", args.query), theme, context);
|
|
3142
|
+
}
|
|
3143
|
+
function renderSemanticResult(result, args, theme, context) {
|
|
3144
|
+
if (context.isError)
|
|
3145
|
+
return renderErrorResult(result, "semantic search failed", theme, context);
|
|
3146
|
+
return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
|
|
3147
|
+
}
|
|
2274
3148
|
function registerSemanticTool(pi, ctx) {
|
|
2275
3149
|
pi.registerTool({
|
|
2276
3150
|
name: "aft_search",
|
|
@@ -2284,6 +3158,12 @@ function registerSemanticTool(pi, ctx) {
|
|
|
2284
3158
|
req.top_k = params.topK;
|
|
2285
3159
|
const response = await callBridge(bridge, "semantic_search", req);
|
|
2286
3160
|
return textResult(response.text ?? JSON.stringify(response, null, 2));
|
|
3161
|
+
},
|
|
3162
|
+
renderCall(args, theme, context) {
|
|
3163
|
+
return renderSemanticCall(args, theme, context);
|
|
3164
|
+
},
|
|
3165
|
+
renderResult(result, _options, theme, context) {
|
|
3166
|
+
return renderSemanticResult(result, context.args, theme, context);
|
|
2287
3167
|
}
|
|
2288
3168
|
});
|
|
2289
3169
|
}
|
|
@@ -2311,6 +3191,37 @@ var TransformParams = Type12.Object({
|
|
|
2311
3191
|
description: "Validation level (default: syntax)"
|
|
2312
3192
|
}))
|
|
2313
3193
|
});
|
|
3194
|
+
function buildTransformSections(args, payload, theme) {
|
|
3195
|
+
const response = asRecord2(payload);
|
|
3196
|
+
if (!response)
|
|
3197
|
+
return [theme.fg("muted", "No transform result.")];
|
|
3198
|
+
if (response.dry_run === true) {
|
|
3199
|
+
return [
|
|
3200
|
+
theme.fg("warning", `[dry run] ${args.op}`),
|
|
3201
|
+
asString(response.diff) ?? theme.fg("muted", "No diff available.")
|
|
3202
|
+
];
|
|
3203
|
+
}
|
|
3204
|
+
const target = asString(response.target) ?? asString(response.scope) ?? args.target ?? args.container ?? args.field ?? args.filePath;
|
|
3205
|
+
return [
|
|
3206
|
+
`${theme.fg("success", "transformed")} ${theme.fg("accent", args.op)}`,
|
|
3207
|
+
`${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
|
|
3208
|
+
target ? `${theme.fg("muted", "target")} ${target}` : theme.fg("muted", "No target metadata.")
|
|
3209
|
+
];
|
|
3210
|
+
}
|
|
3211
|
+
function renderTransformCall(args, theme, context) {
|
|
3212
|
+
const target = args.target ?? args.container ?? args.field;
|
|
3213
|
+
const summary = [
|
|
3214
|
+
theme.fg("accent", args.op),
|
|
3215
|
+
accentPath(theme, args.filePath),
|
|
3216
|
+
target ? theme.fg("toolOutput", target) : undefined
|
|
3217
|
+
].filter(Boolean).join(" ");
|
|
3218
|
+
return renderToolCall("transform", summary, theme, context);
|
|
3219
|
+
}
|
|
3220
|
+
function renderTransformResult(result, args, theme, context) {
|
|
3221
|
+
if (context.isError)
|
|
3222
|
+
return renderErrorResult(result, "transform failed", theme, context);
|
|
3223
|
+
return renderSections(buildTransformSections(args, extractStructuredPayload(result), theme), context);
|
|
3224
|
+
}
|
|
2314
3225
|
function registerStructureTool(pi, ctx) {
|
|
2315
3226
|
pi.registerTool({
|
|
2316
3227
|
name: "aft_transform",
|
|
@@ -2346,6 +3257,12 @@ function registerStructureTool(pi, ctx) {
|
|
|
2346
3257
|
req.validate = params.validate;
|
|
2347
3258
|
const response = await callBridge(bridge, params.op, req);
|
|
2348
3259
|
return textResult(JSON.stringify(response, null, 2));
|
|
3260
|
+
},
|
|
3261
|
+
renderCall(args, theme, context) {
|
|
3262
|
+
return renderTransformCall(args, theme, context);
|
|
3263
|
+
},
|
|
3264
|
+
renderResult(result, _options, theme, context) {
|
|
3265
|
+
return renderTransformResult(result, context.args, theme, context);
|
|
2349
3266
|
}
|
|
2350
3267
|
});
|
|
2351
3268
|
}
|
|
@@ -2360,7 +3277,7 @@ var PLUGIN_VERSION = (() => {
|
|
|
2360
3277
|
}
|
|
2361
3278
|
})();
|
|
2362
3279
|
function resolveStorageDir() {
|
|
2363
|
-
return join8(
|
|
3280
|
+
return join8(homedir7(), ".pi", "agent", "aft");
|
|
2364
3281
|
}
|
|
2365
3282
|
function resolveToolSurface(config) {
|
|
2366
3283
|
const surface = config.tool_surface ?? "recommended";
|