@cortexkit/aft-pi 0.14.1 → 0.15.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/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 homedir6 } from "node:os";
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
@@ -1342,6 +1342,197 @@ function registerShutdownCleanup(fn) {
1342
1342
  // src/tools/ast.ts
1343
1343
  import { StringEnum } from "@mariozechner/pi-ai";
1344
1344
  import { Type } from "@sinclair/typebox";
1345
+
1346
+ // src/tools/render-helpers.ts
1347
+ import { homedir as homedir5 } from "node:os";
1348
+ import { renderDiff } from "@mariozechner/pi-coding-agent";
1349
+ import { Container, Spacer, Text } from "@mariozechner/pi-tui";
1350
+ function reuseText(last) {
1351
+ return last instanceof Text ? last : new Text("", 0, 0);
1352
+ }
1353
+ function reuseContainer(last) {
1354
+ return last instanceof Container ? last : new Container;
1355
+ }
1356
+ function shortenPath(path2) {
1357
+ const home = homedir5();
1358
+ if (path2.startsWith(home))
1359
+ return `~${path2.slice(home.length)}`;
1360
+ return path2;
1361
+ }
1362
+ function renderToolCall(toolName, summary, theme, context) {
1363
+ const text = reuseText(context.lastComponent);
1364
+ const suffix = summary ? ` ${summary}` : "";
1365
+ text.setText(`${theme.fg("toolTitle", theme.bold(toolName))}${suffix}`);
1366
+ return text;
1367
+ }
1368
+ function accentPath(theme, path2) {
1369
+ if (!path2)
1370
+ return theme.fg("toolOutput", "...");
1371
+ return theme.fg("accent", shortenPath(path2));
1372
+ }
1373
+ function collectTextContent(result) {
1374
+ return result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
1375
+ `).trim();
1376
+ }
1377
+ function extractStructuredPayload(result) {
1378
+ if (result.details !== undefined)
1379
+ return result.details;
1380
+ const text = collectTextContent(result);
1381
+ if (!text)
1382
+ return;
1383
+ try {
1384
+ return JSON.parse(text);
1385
+ } catch {
1386
+ return;
1387
+ }
1388
+ }
1389
+ function renderErrorResult(result, fallback, theme, context) {
1390
+ const text = reuseText(context.lastComponent);
1391
+ const message = collectTextContent(result) || fallback;
1392
+ text.setText(`
1393
+ ${theme.fg("error", message)}`);
1394
+ return text;
1395
+ }
1396
+ function renderSections(sections, context) {
1397
+ const container = reuseContainer(context.lastComponent);
1398
+ container.clear();
1399
+ const visibleSections = sections.filter((section) => section.trim().length > 0);
1400
+ if (visibleSections.length === 0)
1401
+ return container;
1402
+ container.addChild(new Spacer(1));
1403
+ visibleSections.forEach((section, index) => {
1404
+ if (index > 0)
1405
+ container.addChild(new Spacer(1));
1406
+ container.addChild(new Text(section, 0, 0));
1407
+ });
1408
+ return container;
1409
+ }
1410
+ function asRecord2(value) {
1411
+ if (!value || typeof value !== "object" || Array.isArray(value))
1412
+ return;
1413
+ return value;
1414
+ }
1415
+ function asRecords(value) {
1416
+ return Array.isArray(value) ? value.map(asRecord2).filter(Boolean) : [];
1417
+ }
1418
+ function asString(value) {
1419
+ return typeof value === "string" ? value : undefined;
1420
+ }
1421
+ function asNumber(value) {
1422
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1423
+ }
1424
+ function asBoolean(value) {
1425
+ return typeof value === "boolean" ? value : undefined;
1426
+ }
1427
+ function formatValue(value) {
1428
+ if (Array.isArray(value))
1429
+ return value.map(formatValue).join(", ");
1430
+ if (typeof value === "string")
1431
+ return value;
1432
+ if (typeof value === "number" || typeof value === "boolean")
1433
+ return String(value);
1434
+ if (value === null || value === undefined)
1435
+ return "—";
1436
+ try {
1437
+ return JSON.stringify(value);
1438
+ } catch {
1439
+ return String(value);
1440
+ }
1441
+ }
1442
+ function groupByFile(items, getFile) {
1443
+ const groups = new Map;
1444
+ items.forEach((item) => {
1445
+ const file = getFile(item) ?? "(unknown file)";
1446
+ const current = groups.get(file) ?? [];
1447
+ current.push(item);
1448
+ groups.set(file, current);
1449
+ });
1450
+ return groups;
1451
+ }
1452
+ function distinctCount(values) {
1453
+ return new Set(values.filter((value) => Boolean(value))).size;
1454
+ }
1455
+ function severityBadge(theme, severity) {
1456
+ const label = severity === "information" ? "info" : severity;
1457
+ switch (severity) {
1458
+ case "error":
1459
+ return theme.fg("error", `[${label}]`);
1460
+ case "warning":
1461
+ return theme.fg("warning", `[${label}]`);
1462
+ case "information":
1463
+ return theme.fg("accent", `[${label}]`);
1464
+ case "hint":
1465
+ return theme.fg("muted", `[${label}]`);
1466
+ default:
1467
+ return theme.fg("muted", `[${label}]`);
1468
+ }
1469
+ }
1470
+ function formatTimestamp(value) {
1471
+ if (typeof value === "string" && value.length > 0)
1472
+ return value;
1473
+ if (typeof value !== "number" || !Number.isFinite(value))
1474
+ return;
1475
+ const millis = value > 1000000000000 ? value : value * 1000;
1476
+ const date = new Date(millis);
1477
+ if (Number.isNaN(date.getTime()))
1478
+ return String(value);
1479
+ return date.toISOString().replace("T", " ").replace(".000Z", "Z");
1480
+ }
1481
+ function formatUnifiedDiffForPi(unifiedDiff) {
1482
+ if (!unifiedDiff.trim())
1483
+ return "";
1484
+ const entries = [];
1485
+ const hunkHeader = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
1486
+ let oldLine = 1;
1487
+ let newLine = 1;
1488
+ for (const line of unifiedDiff.split(`
1489
+ `)) {
1490
+ if (!line)
1491
+ continue;
1492
+ if (line.startsWith("--- ") || line.startsWith("+++ "))
1493
+ continue;
1494
+ if (line.startsWith("\"))
1495
+ continue;
1496
+ const headerMatch = line.match(hunkHeader);
1497
+ if (headerMatch) {
1498
+ oldLine = Number(headerMatch[1]);
1499
+ newLine = Number(headerMatch[2]);
1500
+ continue;
1501
+ }
1502
+ if (line.startsWith("+") && !line.startsWith("+++")) {
1503
+ entries.push({ prefix: "+", line: newLine, text: line.slice(1) });
1504
+ newLine += 1;
1505
+ continue;
1506
+ }
1507
+ if (line.startsWith("-") && !line.startsWith("---")) {
1508
+ entries.push({ prefix: "-", line: oldLine, text: line.slice(1) });
1509
+ oldLine += 1;
1510
+ continue;
1511
+ }
1512
+ if (line.startsWith(" ")) {
1513
+ entries.push({ prefix: " ", line: oldLine, text: line.slice(1) });
1514
+ oldLine += 1;
1515
+ newLine += 1;
1516
+ }
1517
+ }
1518
+ if (entries.length === 0)
1519
+ return "";
1520
+ const width = String(entries.reduce((max, entry) => Math.max(max, entry.line), 1)).length;
1521
+ return entries.map((entry) => `${entry.prefix}${String(entry.line).padStart(width, " ")} ${entry.text}`).join(`
1522
+ `);
1523
+ }
1524
+ function renderUnifiedDiff(unifiedDiff) {
1525
+ const piDiff = formatUnifiedDiffForPi(unifiedDiff);
1526
+ if (!piDiff)
1527
+ return "";
1528
+ try {
1529
+ return renderDiff(piDiff);
1530
+ } catch {
1531
+ return piDiff;
1532
+ }
1533
+ }
1534
+
1535
+ // src/tools/ast.ts
1345
1536
  var AstLang = StringEnum(["typescript", "tsx", "javascript", "python", "rust", "go"], {
1346
1537
  description: "Target language"
1347
1538
  });
@@ -1362,6 +1553,107 @@ var ReplaceParams = Type.Object({
1362
1553
  globs: Type.Optional(Type.Array(Type.String(), { description: "Include/exclude globs" })),
1363
1554
  dryRun: Type.Optional(Type.Boolean({ description: "Preview without applying (default: false)" }))
1364
1555
  });
1556
+ function buildAstSearchSections(payload, theme) {
1557
+ const response = asRecord2(payload);
1558
+ if (!response)
1559
+ return [theme.fg("muted", "No AST search results.")];
1560
+ const matches = asRecords(response.matches);
1561
+ const totalMatches = asNumber(response.total_matches) ?? matches.length;
1562
+ const filesWithMatches = asNumber(response.files_with_matches) ?? groupByFile(matches, (match) => asString(match.file)).size;
1563
+ const filesSearched = asNumber(response.files_searched);
1564
+ const header = [
1565
+ theme.fg("success", `${totalMatches} match${totalMatches === 1 ? "" : "es"}`),
1566
+ theme.fg("accent", `${filesWithMatches} file${filesWithMatches === 1 ? "" : "s"}`),
1567
+ filesSearched !== undefined ? theme.fg("muted", `${filesSearched} searched`) : undefined
1568
+ ].filter(Boolean).join(" · ");
1569
+ if (matches.length === 0)
1570
+ return [header, theme.fg("muted", "No AST matches found.")];
1571
+ const grouped = groupByFile(matches, (match) => asString(match.file));
1572
+ const sections = [header];
1573
+ for (const [file, fileMatches] of grouped.entries()) {
1574
+ const lines = [theme.fg("accent", shortenPath(file))];
1575
+ fileMatches.forEach((match, index) => {
1576
+ const line = asNumber(match.line) ?? 0;
1577
+ const column = asNumber(match.column) ?? 0;
1578
+ const snippet = asString(match.text)?.trim() || "(empty match)";
1579
+ lines.push(` ${index + 1}. ${theme.fg("muted", `${line}:${column}`)} ${snippet}`);
1580
+ const metaVars = asRecord2(match.meta_variables);
1581
+ if (metaVars && Object.keys(metaVars).length > 0) {
1582
+ Object.entries(metaVars).forEach(([name, value]) => {
1583
+ lines.push(` ${theme.fg("muted", `${name} =`)} ${formatValue(value)}`);
1584
+ });
1585
+ }
1586
+ const context = asRecords(match.context);
1587
+ context.forEach((ctxLine) => {
1588
+ const ctxNumber = asNumber(ctxLine.line) ?? 0;
1589
+ const prefix = ctxLine.is_match === true ? theme.fg("accent", ">") : theme.fg("muted", "|");
1590
+ lines.push(` ${prefix} ${ctxNumber}: ${asString(ctxLine.text) ?? ""}`);
1591
+ });
1592
+ });
1593
+ sections.push(lines.join(`
1594
+ `));
1595
+ }
1596
+ return sections;
1597
+ }
1598
+ function buildAstReplaceSections(payload, theme) {
1599
+ const response = asRecord2(payload);
1600
+ if (!response)
1601
+ return [theme.fg("muted", "No AST replace results.")];
1602
+ const files = asRecords(response.files);
1603
+ const totalReplacements = asNumber(response.total_replacements) ?? 0;
1604
+ const totalFiles = asNumber(response.total_files) ?? files.length;
1605
+ const filesWithMatches = asNumber(response.files_with_matches);
1606
+ const dryRun = response.dry_run === true;
1607
+ const headerParts = [
1608
+ dryRun ? theme.fg("warning", "[dry run]") : theme.fg("success", "[applied]"),
1609
+ `${totalReplacements} replacement${totalReplacements === 1 ? "" : "s"}`,
1610
+ `${totalFiles} file${totalFiles === 1 ? "" : "s"}`,
1611
+ filesWithMatches !== undefined ? theme.fg("muted", `${filesWithMatches} matched`) : undefined
1612
+ ];
1613
+ const sections = [headerParts.filter(Boolean).join(" ")];
1614
+ if (files.length === 0) {
1615
+ sections.push(theme.fg("muted", "No files changed."));
1616
+ return sections;
1617
+ }
1618
+ files.forEach((fileResult) => {
1619
+ const file = shortenPath(asString(fileResult.file) ?? "(unknown file)");
1620
+ const replacements = asNumber(fileResult.replacements) ?? 0;
1621
+ const error2 = asString(fileResult.error);
1622
+ const diff = asString(fileResult.diff);
1623
+ const lines = [
1624
+ `${theme.fg("accent", file)} ${theme.fg("muted", `(${replacements} replacement${replacements === 1 ? "" : "s"})`)}`
1625
+ ];
1626
+ if (error2) {
1627
+ lines.push(theme.fg("error", error2));
1628
+ } else if (diff) {
1629
+ const rendered = renderUnifiedDiff(diff);
1630
+ lines.push(rendered || theme.fg("muted", "No diff available."));
1631
+ } else {
1632
+ const backupId = asString(fileResult.backup_id);
1633
+ lines.push(backupId ? `${theme.fg("success", "saved")} ${theme.fg("muted", backupId)}` : theme.fg("success", "saved"));
1634
+ }
1635
+ sections.push(lines.join(`
1636
+ `));
1637
+ });
1638
+ return sections;
1639
+ }
1640
+ function renderAstCall(toolName, args, theme, context) {
1641
+ const lang = theme.fg("accent", args.lang);
1642
+ const summary = toolName === "ast_grep_replace" ? `${lang} ${theme.fg("toolOutput", `${args.pattern} → ${args.rewrite}`)}` : `${lang} ${theme.fg("toolOutput", args.pattern)}`;
1643
+ return renderToolCall(toolName === "ast_grep_replace" ? "ast replace" : "ast search", summary, theme, context);
1644
+ }
1645
+ function renderAstResult(toolName, result, theme, context) {
1646
+ if (context.isError) {
1647
+ return renderErrorResult(result, `${toolName} failed`, theme, context);
1648
+ }
1649
+ const payload = extractStructuredPayload(result);
1650
+ if (!payload) {
1651
+ const text = collectTextContent(result);
1652
+ return renderSections([text || theme.fg("muted", "No result.")], context);
1653
+ }
1654
+ const sections = toolName === "ast_grep_replace" ? buildAstReplaceSections(payload, theme) : buildAstSearchSections(payload, theme);
1655
+ return renderSections(sections, context);
1656
+ }
1365
1657
  function registerAstTools(pi, ctx, surface) {
1366
1658
  if (surface.astSearch) {
1367
1659
  pi.registerTool({
@@ -1383,6 +1675,12 @@ function registerAstTools(pi, ctx, surface) {
1383
1675
  req.context_lines = params.contextLines;
1384
1676
  const response = await callBridge(bridge, "ast_search", req);
1385
1677
  return textResult(response.text ?? JSON.stringify(response));
1678
+ },
1679
+ renderCall(args, theme, context) {
1680
+ return renderAstCall("ast_grep_search", args, theme, context);
1681
+ },
1682
+ renderResult(result, _options, theme, context) {
1683
+ return renderAstResult("ast_grep_search", result, theme, context);
1386
1684
  }
1387
1685
  });
1388
1686
  }
@@ -1406,6 +1704,12 @@ function registerAstTools(pi, ctx, surface) {
1406
1704
  req.dry_run = params.dryRun === true;
1407
1705
  const response = await callBridge(bridge, "ast_replace", req);
1408
1706
  return textResult(response.text ?? JSON.stringify(response));
1707
+ },
1708
+ renderCall(args, theme, context) {
1709
+ return renderAstCall("ast_grep_replace", args, theme, context);
1710
+ },
1711
+ renderResult(result, _options, theme, context) {
1712
+ return renderAstResult("ast_grep_replace", result, theme, context);
1409
1713
  }
1410
1714
  });
1411
1715
  }
@@ -1414,6 +1718,32 @@ function registerAstTools(pi, ctx, surface) {
1414
1718
  // src/tools/conflicts.ts
1415
1719
  import { Type as Type2 } from "@sinclair/typebox";
1416
1720
  var ConflictsParams = Type2.Object({});
1721
+ function renderConflictCall(theme, context) {
1722
+ return renderToolCall("conflicts", undefined, theme, context);
1723
+ }
1724
+ function buildConflictSections(text) {
1725
+ const trimmed = text.trim();
1726
+ if (!trimmed)
1727
+ return ["No merge conflicts found."];
1728
+ const [header, ...rest] = trimmed.split(/\n\n+/);
1729
+ const match = header.match(/^(\d+)\s+files?,\s+(\d+)\s+conflicts?/i);
1730
+ const sections = [
1731
+ match ? `${match[1]} conflicted file${match[1] === "1" ? "" : "s"} · ${match[2]} region${match[2] === "1" ? "" : "s"}` : header
1732
+ ];
1733
+ if (rest.length === 0)
1734
+ return sections;
1735
+ sections.push(...rest.map((section) => section.trim()).filter(Boolean));
1736
+ return sections;
1737
+ }
1738
+ function renderConflictResult(text, theme, context) {
1739
+ const sections = buildConflictSections(text).map((section, index) => index === 0 ? theme.fg("warning", section) : section);
1740
+ return renderSections(sections, context);
1741
+ }
1742
+ function renderConflictToolResult(result, theme, context) {
1743
+ if (context.isError)
1744
+ return renderErrorResult(result, "conflicts failed", theme, context);
1745
+ return renderConflictResult(collectTextContent(result), theme, context);
1746
+ }
1417
1747
  function registerConflictsTool(pi, ctx) {
1418
1748
  pi.registerTool({
1419
1749
  name: "aft_conflicts",
@@ -1424,6 +1754,12 @@ function registerConflictsTool(pi, ctx) {
1424
1754
  const bridge = bridgeFor(ctx, extCtx.cwd);
1425
1755
  const response = await callBridge(bridge, "git_conflicts");
1426
1756
  return textResult(response.text ?? JSON.stringify(response, null, 2));
1757
+ },
1758
+ renderCall(_args, theme, context) {
1759
+ return renderConflictCall(theme, context);
1760
+ },
1761
+ renderResult(result, _options, theme, context) {
1762
+ return renderConflictToolResult(result, theme, context);
1427
1763
  }
1428
1764
  });
1429
1765
  }
@@ -1437,6 +1773,27 @@ var MoveParams = Type3.Object({
1437
1773
  filePath: Type3.String({ description: "Source file path to move" }),
1438
1774
  destination: Type3.String({ description: "Destination file path" })
1439
1775
  });
1776
+ function renderFsCall(toolName, args, theme, context) {
1777
+ if (toolName === "aft_delete") {
1778
+ return renderToolCall("delete", accentPath(theme, args.filePath), theme, context);
1779
+ }
1780
+ const moveArgs = args;
1781
+ return renderToolCall("move", `${accentPath(theme, moveArgs.filePath)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
1782
+ }
1783
+ function renderFsResult(toolName, args, result, theme, context) {
1784
+ if (context.isError) {
1785
+ return renderErrorResult(result, `${toolName} failed`, theme, context);
1786
+ }
1787
+ if (toolName === "aft_delete") {
1788
+ const filePath = shortenPath(args.filePath);
1789
+ return renderSections([`${theme.fg("success", "✓ deleted")} ${theme.fg("accent", filePath)}`], context);
1790
+ }
1791
+ const moveArgs = args;
1792
+ return renderSections([
1793
+ `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath(moveArgs.filePath))}`,
1794
+ `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath(moveArgs.destination))}`
1795
+ ], context);
1796
+ }
1440
1797
  function registerFsTools(pi, ctx, surface) {
1441
1798
  if (surface.delete) {
1442
1799
  pi.registerTool({
@@ -1448,6 +1805,12 @@ function registerFsTools(pi, ctx, surface) {
1448
1805
  const bridge = bridgeFor(ctx, extCtx.cwd);
1449
1806
  const response = await callBridge(bridge, "delete_file", { file: params.filePath });
1450
1807
  return textResult(`Deleted ${params.filePath}`, response);
1808
+ },
1809
+ renderCall(args, theme, context) {
1810
+ return renderFsCall("aft_delete", args, theme, context);
1811
+ },
1812
+ renderResult(result, _options, theme, context) {
1813
+ return renderFsResult("aft_delete", context.args, result, theme, context);
1451
1814
  }
1452
1815
  });
1453
1816
  }
@@ -1464,6 +1827,12 @@ function registerFsTools(pi, ctx, surface) {
1464
1827
  destination: params.destination
1465
1828
  });
1466
1829
  return textResult(`Moved ${params.filePath} → ${params.destination}`, response);
1830
+ },
1831
+ renderCall(args, theme, context) {
1832
+ return renderFsCall("aft_move", args, theme, context);
1833
+ },
1834
+ renderResult(result, _options, theme, context) {
1835
+ return renderFsResult("aft_move", context.args, result, theme, context);
1467
1836
  }
1468
1837
  });
1469
1838
  }
@@ -1471,12 +1840,12 @@ function registerFsTools(pi, ctx, surface) {
1471
1840
 
1472
1841
  // src/tools/hoisted.ts
1473
1842
  import { stat } from "node:fs/promises";
1474
- import { homedir as homedir5 } from "node:os";
1843
+ import { homedir as homedir6 } from "node:os";
1475
1844
  import { resolve } from "node:path";
1476
1845
  import {
1477
- renderDiff
1846
+ renderDiff as renderDiff2
1478
1847
  } from "@mariozechner/pi-coding-agent";
1479
- import { Container, Spacer, Text } from "@mariozechner/pi-tui";
1848
+ import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@mariozechner/pi-tui";
1480
1849
  import { Type as Type4 } from "@sinclair/typebox";
1481
1850
 
1482
1851
  // src/tools/diff-format.ts
@@ -1783,15 +2152,15 @@ function formatDiagnosticsText(diagnostics) {
1783
2152
  return JSON.stringify(diagnostics, null, 2);
1784
2153
  }
1785
2154
  }
1786
- function reuseText(last) {
1787
- return last instanceof Text ? last : new Text("", 0, 0);
2155
+ function reuseText2(last) {
2156
+ return last instanceof Text2 ? last : new Text2("", 0, 0);
1788
2157
  }
1789
- function reuseContainer(last) {
1790
- return last instanceof Container ? last : new Container;
2158
+ function reuseContainer2(last) {
2159
+ return last instanceof Container2 ? last : new Container2;
1791
2160
  }
1792
2161
  function renderMutationCall(toolName, filePath, theme, context) {
1793
- const text = reuseText(context.lastComponent);
1794
- const pathDisplay = filePath ? theme.fg("accent", shortenPath(filePath)) : theme.fg("toolOutput", "...");
2162
+ const text = reuseText2(context.lastComponent);
2163
+ const pathDisplay = filePath ? theme.fg("accent", shortenPath2(filePath)) : theme.fg("toolOutput", "...");
1795
2164
  text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
1796
2165
  return text;
1797
2166
  }
@@ -1799,7 +2168,7 @@ function renderMutationResult(result, theme, context) {
1799
2168
  if (context.isError) {
1800
2169
  const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
1801
2170
  `).trim();
1802
- const text = reuseText(context.lastComponent);
2171
+ const text = reuseText2(context.lastComponent);
1803
2172
  text.setText(`
1804
2173
  ${theme.fg("error", errorText || "edit failed")}`);
1805
2174
  return text;
@@ -1809,21 +2178,21 @@ ${theme.fg("error", errorText || "edit failed")}`);
1809
2178
  if (!diff) {
1810
2179
  const additions = details?.additions ?? 0;
1811
2180
  const deletions = details?.deletions ?? 0;
1812
- const text = reuseText(context.lastComponent);
2181
+ const text = reuseText2(context.lastComponent);
1813
2182
  const summary = theme.fg("success", `+${additions}/-${deletions}`);
1814
2183
  const suffix = details?.truncated ? ` ${theme.fg("muted", "(diff truncated)")}` : "";
1815
2184
  text.setText(`
1816
2185
  ${summary}${suffix}`);
1817
2186
  return text;
1818
2187
  }
1819
- const container = reuseContainer(context.lastComponent);
2188
+ const container = reuseContainer2(context.lastComponent);
1820
2189
  container.clear();
1821
- container.addChild(new Spacer(1));
1822
- container.addChild(new Text(renderDiff(diff), 1, 0));
2190
+ container.addChild(new Spacer2(1));
2191
+ container.addChild(new Text2(renderDiff2(diff), 1, 0));
1823
2192
  return container;
1824
2193
  }
1825
- function shortenPath(path2) {
1826
- const home = homedir5();
2194
+ function shortenPath2(path2) {
2195
+ const home = homedir6();
1827
2196
  if (path2.startsWith(home))
1828
2197
  return `~${path2.slice(home.length)}`;
1829
2198
  return path2;
@@ -1857,6 +2226,54 @@ var ImportParams = Type5.Object({
1857
2226
  description: "Post-edit validation level (default: syntax)"
1858
2227
  }))
1859
2228
  });
2229
+ function buildImportSections(args, payload, theme) {
2230
+ const response = asRecord2(payload);
2231
+ if (!response)
2232
+ return [theme.fg("muted", "No import result.")];
2233
+ if (response.dry_run === true) {
2234
+ return [
2235
+ theme.fg("warning", `[dry run] ${args.op}`),
2236
+ asString(response.diff) || theme.fg("muted", "No diff available.")
2237
+ ];
2238
+ }
2239
+ if (args.op === "organize") {
2240
+ const groups = asRecords(response.groups);
2241
+ const groupText = groups.length > 0 ? groups.map((group) => `${asString(group.name) ?? "unknown"}: ${asNumber(group.count) ?? 0}`).join(" · ") : "No imports found";
2242
+ return [
2243
+ `${theme.fg("success", "organized")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
2244
+ `${theme.fg("muted", "groups")} ${groupText}`,
2245
+ `${theme.fg("muted", "duplicates removed")} ${asNumber(response.removed_duplicates) ?? 0}`
2246
+ ];
2247
+ }
2248
+ if (args.op === "add") {
2249
+ const moduleName = asString(response.module) ?? args.module ?? "(module)";
2250
+ const status = response.already_present === true ? theme.fg("warning", "already present") : theme.fg("success", "added");
2251
+ return [
2252
+ `${status} ${theme.fg("accent", moduleName)}`,
2253
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
2254
+ `${theme.fg("muted", "group")} ${asString(response.group) ?? "—"}`
2255
+ ];
2256
+ }
2257
+ return [
2258
+ `${theme.fg("success", "removed")} ${theme.fg("accent", asString(response.module) ?? args.module ?? "(module)")}`,
2259
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
2260
+ args.removeName ? `${theme.fg("muted", "name")} ${args.removeName}` : `${theme.fg("muted", "scope")} entire import`
2261
+ ];
2262
+ }
2263
+ function renderImportCall(args, theme, context) {
2264
+ const summary = [
2265
+ theme.fg("accent", args.op),
2266
+ accentPath(theme, args.filePath),
2267
+ args.module ? theme.fg("toolOutput", args.module) : undefined
2268
+ ].filter(Boolean).join(" ");
2269
+ return renderToolCall("import", summary, theme, context);
2270
+ }
2271
+ function renderImportResult(result, args, theme, context) {
2272
+ if (context.isError)
2273
+ return renderErrorResult(result, "import failed", theme, context);
2274
+ const payload = extractStructuredPayload(result);
2275
+ return renderSections(buildImportSections(args, payload, theme), context);
2276
+ }
1860
2277
  function registerImportTools(pi, ctx) {
1861
2278
  pi.registerTool({
1862
2279
  name: "aft_import",
@@ -1890,6 +2307,12 @@ function registerImportTools(pi, ctx) {
1890
2307
  req.validate = params.validate;
1891
2308
  const response = await callBridge(bridge, commandMap[params.op], req);
1892
2309
  return textResult(JSON.stringify(response, null, 2));
2310
+ },
2311
+ renderCall(args, theme, context) {
2312
+ return renderImportCall(args, theme, context);
2313
+ },
2314
+ renderResult(result, _options, theme, context) {
2315
+ return renderImportResult(result, context.args, theme, context);
1893
2316
  }
1894
2317
  });
1895
2318
  }
@@ -1909,6 +2332,51 @@ var LspDiagnosticsParams = Type6.Object({
1909
2332
  description: "Wait N ms for fresh diagnostics (max 10000, default: 0)"
1910
2333
  }))
1911
2334
  });
2335
+ function buildDiagnosticsSections(payload, theme) {
2336
+ const response = asRecord2(payload);
2337
+ if (!response)
2338
+ return [theme.fg("muted", "No diagnostics available.")];
2339
+ const diagnostics = asRecords(response.diagnostics);
2340
+ const total = asNumber(response.total) ?? diagnostics.length;
2341
+ const filesWithErrors = asNumber(response.files_with_errors) ?? distinctCount(diagnostics.filter((diag) => asString(diag.severity) === "error").map((diag) => asString(diag.file)));
2342
+ const filesCount = distinctCount(diagnostics.map((diag) => asString(diag.file)));
2343
+ const sections = [
2344
+ `${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"}`)}`
2345
+ ];
2346
+ if (diagnostics.length === 0) {
2347
+ sections.push(theme.fg("muted", "No diagnostics found."));
2348
+ return sections;
2349
+ }
2350
+ const grouped = groupByFile(diagnostics, (diag) => asString(diag.file));
2351
+ for (const [file, fileDiagnostics] of grouped.entries()) {
2352
+ const lines = [theme.fg("accent", shortenPath(file))];
2353
+ fileDiagnostics.forEach((diagnostic) => {
2354
+ const severity = asString(diagnostic.severity) ?? "information";
2355
+ const line = asNumber(diagnostic.line) ?? 0;
2356
+ const column = asNumber(diagnostic.column) ?? 0;
2357
+ const code = asString(diagnostic.code);
2358
+ const message = asString(diagnostic.message) ?? "(no message)";
2359
+ const location = `${line}:${column}`;
2360
+ lines.push(` ${severityBadge(theme, severity)} ${location}${code ? ` ${theme.fg("muted", code)}` : ""} ${message}`);
2361
+ });
2362
+ sections.push(lines.join(`
2363
+ `));
2364
+ }
2365
+ return sections;
2366
+ }
2367
+ function renderDiagnosticsCall(args, theme, context) {
2368
+ const target = args.filePath ?? args.directory;
2369
+ const summary = [
2370
+ target ? accentPath(theme, target) : undefined,
2371
+ args.severity ? theme.fg("toolOutput", args.severity) : undefined
2372
+ ].filter(Boolean).join(" ");
2373
+ return renderToolCall("lsp diagnostics", summary, theme, context);
2374
+ }
2375
+ function renderDiagnosticsResult(result, theme, context) {
2376
+ if (context.isError)
2377
+ return renderErrorResult(result, "lsp diagnostics failed", theme, context);
2378
+ return renderSections(buildDiagnosticsSections(extractStructuredPayload(result), theme), context);
2379
+ }
1912
2380
  function registerLspTools(pi, ctx) {
1913
2381
  pi.registerTool({
1914
2382
  name: "lsp_diagnostics",
@@ -1933,6 +2401,12 @@ function registerLspTools(pi, ctx) {
1933
2401
  req.wait_ms = params.waitMs;
1934
2402
  const response = await callBridge(bridge, "lsp_diagnostics", req);
1935
2403
  return textResult(JSON.stringify(response, null, 2));
2404
+ },
2405
+ renderCall(args, theme, context) {
2406
+ return renderDiagnosticsCall(args, theme, context);
2407
+ },
2408
+ renderResult(result, _options, theme, context) {
2409
+ return renderDiagnosticsResult(result, theme, context);
1936
2410
  }
1937
2411
  });
1938
2412
  }
@@ -1949,6 +2423,121 @@ var NavigateParams = Type7.Object({
1949
2423
  depth: Type7.Optional(Type7.Number({ description: "Max traversal depth" })),
1950
2424
  expression: Type7.Optional(Type7.String({ description: "Expression to track (required for trace_data)" }))
1951
2425
  });
2426
+ function treeLine(depth, text) {
2427
+ return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
2428
+ }
2429
+ function renderCallTreeNode(node, depth, lines) {
2430
+ const name = asString(node.name) ?? "(unknown)";
2431
+ const file = shortenPath(asString(node.file) ?? "(unknown file)");
2432
+ const line = asNumber(node.line);
2433
+ lines.push(treeLine(depth, `${name} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
2434
+ asRecords(node.children).forEach((child) => {
2435
+ renderCallTreeNode(child, depth + 1, lines);
2436
+ });
2437
+ }
2438
+ function renderTracePath(path2, index, lines) {
2439
+ lines.push(`Path ${index + 1}`);
2440
+ asRecords(path2.hops).forEach((hop, hopIndex) => {
2441
+ const symbol = asString(hop.symbol) ?? "(unknown)";
2442
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
2443
+ const line = asNumber(hop.line);
2444
+ const entry = hop.is_entry_point === true ? " [entry]" : "";
2445
+ lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
2446
+ });
2447
+ }
2448
+ function buildNavigateSections(args, payload, theme) {
2449
+ const response = asRecord2(payload);
2450
+ if (!response)
2451
+ return [theme.fg("muted", "No navigation result.")];
2452
+ if (args.op === "call_tree") {
2453
+ const lines = [];
2454
+ renderCallTreeNode(response, 0, lines);
2455
+ return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
2456
+ }
2457
+ if (args.op === "callers") {
2458
+ const groups = asRecords(response.callers);
2459
+ const sections2 = [
2460
+ `${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"}`)}`
2461
+ ];
2462
+ groups.forEach((group) => {
2463
+ const file = shortenPath(asString(group.file) ?? "(unknown file)");
2464
+ const lines = [theme.fg("accent", file)];
2465
+ asRecords(group.callers).forEach((caller) => {
2466
+ lines.push(` ↳ ${asString(caller.symbol) ?? "(unknown)"} ${theme.fg("muted", `line ${asNumber(caller.line) ?? "?"}`)}`);
2467
+ });
2468
+ sections2.push(lines.join(`
2469
+ `));
2470
+ });
2471
+ return sections2;
2472
+ }
2473
+ if (args.op === "trace_to") {
2474
+ const paths = asRecords(response.paths);
2475
+ const sections2 = [
2476
+ `${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"}`)}`
2477
+ ];
2478
+ if (paths.length === 0)
2479
+ sections2.push(theme.fg("muted", "No entry paths found."));
2480
+ paths.forEach((path2, index) => {
2481
+ const lines = [];
2482
+ renderTracePath(path2, index, lines);
2483
+ sections2.push(lines.join(`
2484
+ `));
2485
+ });
2486
+ return sections2;
2487
+ }
2488
+ if (args.op === "impact") {
2489
+ const callers = asRecords(response.callers);
2490
+ const sections2 = [
2491
+ `${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"}`)}`
2492
+ ];
2493
+ if (callers.length === 0)
2494
+ sections2.push(theme.fg("muted", "No impacted callers found."));
2495
+ callers.forEach((caller) => {
2496
+ const file = shortenPath(asString(caller.caller_file) ?? "(unknown file)");
2497
+ const symbol = asString(caller.caller_symbol) ?? "(unknown)";
2498
+ const line = asNumber(caller.line) ?? 0;
2499
+ const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
2500
+ const expression = asString(caller.call_expression);
2501
+ const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
2502
+ sections2.push([
2503
+ `${theme.fg("accent", file)}:${line}`,
2504
+ ` ↳ ${symbol}${entry}`,
2505
+ expression ? ` ${theme.fg("muted", expression)}` : undefined,
2506
+ params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
2507
+ ].filter(Boolean).join(`
2508
+ `));
2509
+ });
2510
+ return sections2;
2511
+ }
2512
+ const hops = asRecords(response.hops);
2513
+ const sections = [
2514
+ `${theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`)} ${asBoolean(response.depth_limited) ? theme.fg("warning", "(depth limited)") : ""}`.trim()
2515
+ ];
2516
+ if (hops.length === 0)
2517
+ sections.push(theme.fg("muted", "No data-flow hops found."));
2518
+ hops.forEach((hop, index) => {
2519
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
2520
+ const symbol = asString(hop.symbol) ?? "(unknown)";
2521
+ const variable = asString(hop.variable) ?? "(unknown)";
2522
+ const line = asNumber(hop.line) ?? 0;
2523
+ const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
2524
+ sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
2525
+ });
2526
+ return sections;
2527
+ }
2528
+ function renderNavigateCall(args, theme, context) {
2529
+ const summary = [
2530
+ theme.fg("accent", args.op),
2531
+ accentPath(theme, args.filePath),
2532
+ theme.fg("toolOutput", args.symbol)
2533
+ ].filter(Boolean).join(" ");
2534
+ return renderToolCall("navigate", summary, theme, context);
2535
+ }
2536
+ function renderNavigateResult(result, args, theme, context) {
2537
+ if (context.isError)
2538
+ return renderErrorResult(result, "navigate failed", theme, context);
2539
+ return renderSections(buildNavigateSections(args, extractStructuredPayload(result), theme), context);
2540
+ }
1952
2541
  function registerNavigateTool(pi, ctx) {
1953
2542
  pi.registerTool({
1954
2543
  name: "aft_navigate",
@@ -1971,6 +2560,12 @@ function registerNavigateTool(pi, ctx) {
1971
2560
  req.expression = params.expression;
1972
2561
  const response = await callBridge(bridge, params.op, req);
1973
2562
  return textResult(JSON.stringify(response, null, 2));
2563
+ },
2564
+ renderCall(args, theme, context) {
2565
+ return renderNavigateCall(args, theme, context);
2566
+ },
2567
+ renderResult(result, _options, theme, context) {
2568
+ return renderNavigateResult(result, context.args, theme, context);
1974
2569
  }
1975
2570
  });
1976
2571
  }
@@ -2083,6 +2678,71 @@ var ZoomParams = Type8.Object({
2083
2678
  symbols: Type8.Optional(Type8.Array(Type8.String(), { description: "Multiple symbols — returns array of matches" })),
2084
2679
  contextLines: Type8.Optional(Type8.Number({ description: "Lines of context before/after (default: 3)" }))
2085
2680
  });
2681
+ function buildOutlineSections(text, theme) {
2682
+ const trimmed = text.trim();
2683
+ if (!trimmed)
2684
+ return [theme.fg("muted", "No outline available.")];
2685
+ const lines = trimmed.split(`
2686
+ `);
2687
+ if (lines.length === 1)
2688
+ return [theme.fg("accent", lines[0])];
2689
+ return [theme.fg("accent", lines[0]), lines.slice(1).join(`
2690
+ `)];
2691
+ }
2692
+ function buildZoomSections(args, payload, theme) {
2693
+ const items = Array.isArray(payload) ? payload : payload ? [payload] : [];
2694
+ if (items.length === 0)
2695
+ return [theme.fg("muted", "No zoom result available.")];
2696
+ return items.map((item) => {
2697
+ const record = asRecord2(item);
2698
+ if (!record)
2699
+ return theme.fg("muted", "No zoom result available.");
2700
+ const name = asString(record.name) ?? "(unknown symbol)";
2701
+ const kind = asString(record.kind) ?? "symbol";
2702
+ const range = asRecord2(record.range);
2703
+ const startLine = range && typeof range.start_line === "number" ? range.start_line : undefined;
2704
+ const endLine = range && typeof range.end_line === "number" ? range.end_line : undefined;
2705
+ const location = startLine !== undefined ? `${shortenPath(args.filePath)}:${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : shortenPath(args.filePath);
2706
+ const lines = [`${theme.fg("accent", name)} ${theme.fg("muted", `[${kind}] ${location}`)}`];
2707
+ const content = asString(record.content);
2708
+ if (content) {
2709
+ lines.push(content.split(`
2710
+ `).map((line) => ` ${line}`).join(`
2711
+ `));
2712
+ }
2713
+ const annotations = asRecord2(record.annotations);
2714
+ const callsOut = annotations ? asRecords(annotations.calls_out) : [];
2715
+ const calledBy = annotations ? asRecords(annotations.called_by) : [];
2716
+ if (callsOut.length > 0) {
2717
+ lines.push(`${theme.fg("muted", "calls out")}`, callsOut.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
2718
+ `));
2719
+ }
2720
+ if (calledBy.length > 0) {
2721
+ lines.push(`${theme.fg("muted", "called by")}`, calledBy.map((call) => ` ↳ ${asString(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}`).join(`
2722
+ `));
2723
+ }
2724
+ return lines.join(`
2725
+ `);
2726
+ }).filter(Boolean);
2727
+ }
2728
+ function renderOutlineCall(args, theme, context) {
2729
+ 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;
2730
+ return renderToolCall("outline", summary, theme, context);
2731
+ }
2732
+ function renderOutlineResult(result, theme, context) {
2733
+ if (context.isError)
2734
+ return renderErrorResult(result, "outline failed", theme, context);
2735
+ return renderSections(buildOutlineSections(collectTextContent(result), theme), context);
2736
+ }
2737
+ function renderZoomCall(args, theme, context) {
2738
+ 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");
2739
+ return renderToolCall("zoom", `${accentPath(theme, args.filePath)} ${target}`, theme, context);
2740
+ }
2741
+ function renderZoomResult(result, args, theme, context) {
2742
+ if (context.isError)
2743
+ return renderErrorResult(result, "zoom failed", theme, context);
2744
+ return renderSections(buildZoomSections(args, extractStructuredPayload(result), theme), context);
2745
+ }
2086
2746
  function registerReadingTools(pi, ctx, surface) {
2087
2747
  if (surface.outline) {
2088
2748
  pi.registerTool({
@@ -2126,6 +2786,12 @@ function registerReadingTools(pi, ctx, surface) {
2126
2786
  }
2127
2787
  const response = await callBridge(bridge, "outline", { file: params.filePath });
2128
2788
  return textResult(response.text ?? "");
2789
+ },
2790
+ renderCall(args, theme, context) {
2791
+ return renderOutlineCall(args, theme, context);
2792
+ },
2793
+ renderResult(result, _options, theme, context) {
2794
+ return renderOutlineResult(result, theme, context);
2129
2795
  }
2130
2796
  });
2131
2797
  }
@@ -2153,6 +2819,12 @@ function registerReadingTools(pi, ctx, surface) {
2153
2819
  req.context_lines = params.contextLines;
2154
2820
  const response = await callBridge(bridge, "zoom", req);
2155
2821
  return textResult(JSON.stringify(response, null, 2));
2822
+ },
2823
+ renderCall(args, theme, context) {
2824
+ return renderZoomCall(args, theme, context);
2825
+ },
2826
+ renderResult(result, _options, theme, context) {
2827
+ return renderZoomResult(result, context.args, theme, context);
2156
2828
  }
2157
2829
  });
2158
2830
  }
@@ -2173,6 +2845,63 @@ var RefactorParams = Type9.Object({
2173
2845
  callSiteLine: Type9.Optional(Type9.Number({ description: "1-based call site line (for inline)" })),
2174
2846
  dryRun: Type9.Optional(Type9.Boolean({ description: "Preview as diff" }))
2175
2847
  });
2848
+ function buildRefactorSections(args, payload, theme) {
2849
+ const response = asRecord2(payload);
2850
+ if (!response)
2851
+ return [theme.fg("muted", "No refactor result.")];
2852
+ if (response.dry_run === true) {
2853
+ const diffs = asRecords(response.diffs);
2854
+ const sections = [theme.fg("warning", `[dry run] ${args.op}`)];
2855
+ if (diffs.length === 0) {
2856
+ sections.push(theme.fg("muted", "No diff available."));
2857
+ return sections;
2858
+ }
2859
+ diffs.forEach((diff) => {
2860
+ const file = shortenPath(asString(diff.file) ?? "(unknown file)");
2861
+ const rendered = renderUnifiedDiff(asString(diff.diff) ?? "") || theme.fg("muted", "No diff available.");
2862
+ sections.push(`${theme.fg("accent", file)}
2863
+ ${rendered}`);
2864
+ });
2865
+ return sections;
2866
+ }
2867
+ if (args.op === "move") {
2868
+ const results = asRecords(response.results);
2869
+ return [
2870
+ `${theme.fg("success", "moved symbol")} ${theme.fg("toolOutput", args.symbol ?? "(symbol)")}`,
2871
+ `${theme.fg("muted", "files modified")} ${asNumber(response.files_modified) ?? results.length}`,
2872
+ `${theme.fg("muted", "consumers updated")} ${asNumber(response.consumers_updated) ?? 0}`,
2873
+ results.length > 0 ? results.map((entry) => ` ↳ ${shortenPath(asString(entry.file) ?? "(unknown file)")}`).join(`
2874
+ `) : theme.fg("muted", "No files reported.")
2875
+ ];
2876
+ }
2877
+ if (args.op === "extract") {
2878
+ return [
2879
+ `${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString(response.name) ?? args.name ?? "(function)")}`,
2880
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath))}`,
2881
+ `${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
2882
+ `${theme.fg("muted", "return type")} ${asString(response.return_type) ?? "unknown"}`
2883
+ ];
2884
+ }
2885
+ return [
2886
+ `${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString(response.symbol) ?? args.symbol ?? "(symbol)")}`,
2887
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath))}`,
2888
+ `${theme.fg("muted", "context")} ${asString(response.call_context) ?? "unknown"}`,
2889
+ `${theme.fg("muted", "substitutions")} ${asNumber(response.substitutions) ?? 0}`
2890
+ ];
2891
+ }
2892
+ function renderRefactorCall(args, theme, context) {
2893
+ const summary = [
2894
+ theme.fg("accent", args.op),
2895
+ accentPath(theme, args.filePath),
2896
+ args.symbol ? theme.fg("toolOutput", args.symbol) : undefined
2897
+ ].filter(Boolean).join(" ");
2898
+ return renderToolCall("refactor", summary, theme, context);
2899
+ }
2900
+ function renderRefactorResult(result, args, theme, context) {
2901
+ if (context.isError)
2902
+ return renderErrorResult(result, "refactor failed", theme, context);
2903
+ return renderSections(buildRefactorSections(args, extractStructuredPayload(result), theme), context);
2904
+ }
2176
2905
  function registerRefactorTool(pi, ctx) {
2177
2906
  pi.registerTool({
2178
2907
  name: "aft_refactor",
@@ -2206,6 +2935,12 @@ function registerRefactorTool(pi, ctx) {
2206
2935
  req.dry_run = params.dryRun;
2207
2936
  const response = await callBridge(bridge, commandMap[params.op], req);
2208
2937
  return textResult(JSON.stringify(response, null, 2));
2938
+ },
2939
+ renderCall(args, theme, context) {
2940
+ return renderRefactorCall(args, theme, context);
2941
+ },
2942
+ renderResult(result, _options, theme, context) {
2943
+ return renderRefactorResult(result, context.args, theme, context);
2209
2944
  }
2210
2945
  });
2211
2946
  }
@@ -2223,6 +2958,78 @@ var SafetyParams = Type10.Object({
2223
2958
  description: "Specific files for checkpoint (optional, defaults to all tracked)"
2224
2959
  }))
2225
2960
  });
2961
+ function buildSafetySections(args, payload, theme) {
2962
+ const response = asRecord2(payload);
2963
+ if (!response)
2964
+ return [theme.fg("muted", "No safety result.")];
2965
+ if (args.op === "undo") {
2966
+ return [
2967
+ `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath(asString(response.path) ?? args.filePath ?? "(file)"))}`,
2968
+ `${theme.fg("muted", "backup")} ${asString(response.backup_id) ?? "—"}`
2969
+ ];
2970
+ }
2971
+ if (args.op === "history") {
2972
+ const entries = asRecords(response.entries);
2973
+ const sections2 = [
2974
+ theme.fg("accent", shortenPath(asString(response.file) ?? args.filePath ?? "(file)"))
2975
+ ];
2976
+ if (entries.length === 0) {
2977
+ sections2.push(theme.fg("muted", "No history entries."));
2978
+ return sections2;
2979
+ }
2980
+ sections2.push(entries.map((entry, index) => {
2981
+ const backupId = asString(entry.backup_id) ?? `entry-${index + 1}`;
2982
+ const timestamp = formatTimestamp(entry.timestamp) ?? "unknown time";
2983
+ const description = asString(entry.description) ?? "";
2984
+ return `${index + 1}. ${backupId} ${theme.fg("muted", timestamp)}${description ? `
2985
+ ${description}` : ""}`;
2986
+ }).join(`
2987
+ `));
2988
+ return sections2;
2989
+ }
2990
+ if (args.op === "checkpoint") {
2991
+ const skipped = asRecords(response.skipped);
2992
+ return [
2993
+ `${theme.fg("success", "checkpoint created")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
2994
+ `${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`,
2995
+ skipped.length > 0 ? `${theme.fg("warning", "skipped")}
2996
+ ${skipped.map((entry) => ` ↳ ${shortenPath(asString(entry.file) ?? "(file)")}: ${asString(entry.error) ?? "unknown error"}`).join(`
2997
+ `)}` : theme.fg("muted", "No skipped files.")
2998
+ ];
2999
+ }
3000
+ if (args.op === "restore") {
3001
+ return [
3002
+ `${theme.fg("success", "checkpoint restored")} ${theme.fg("accent", asString(response.name) ?? args.name ?? "(checkpoint)")}`,
3003
+ `${theme.fg("muted", "files")} ${asNumber(response.file_count) ?? 0}`
3004
+ ];
3005
+ }
3006
+ const checkpoints = asRecords(response.checkpoints);
3007
+ const sections = [
3008
+ theme.fg("accent", `${checkpoints.length} checkpoint${checkpoints.length === 1 ? "" : "s"}`)
3009
+ ];
3010
+ if (checkpoints.length === 0) {
3011
+ sections.push(theme.fg("muted", "No checkpoints saved."));
3012
+ return sections;
3013
+ }
3014
+ sections.push(checkpoints.map((checkpoint, index) => {
3015
+ const name = asString(checkpoint.name) ?? `checkpoint-${index + 1}`;
3016
+ const count = asNumber(checkpoint.file_count) ?? 0;
3017
+ const created = formatTimestamp(checkpoint.created_at) ?? "unknown time";
3018
+ return `${index + 1}. ${name} ${theme.fg("muted", `${count} file${count === 1 ? "" : "s"} · ${created}`)}`;
3019
+ }).join(`
3020
+ `));
3021
+ return sections;
3022
+ }
3023
+ function renderSafetyCall(args, theme, context) {
3024
+ const target = args.filePath ?? args.name;
3025
+ const summary = [theme.fg("accent", args.op), target ? accentPath(theme, target) : undefined].filter(Boolean).join(" ");
3026
+ return renderToolCall("safety", summary, theme, context);
3027
+ }
3028
+ function renderSafetyResult(result, args, theme, context) {
3029
+ if (context.isError)
3030
+ return renderErrorResult(result, "safety failed", theme, context);
3031
+ return renderSections(buildSafetySections(args, extractStructuredPayload(result), theme), context);
3032
+ }
2226
3033
  function registerSafetyTool(pi, ctx) {
2227
3034
  pi.registerTool({
2228
3035
  name: "aft_safety",
@@ -2261,6 +3068,12 @@ function registerSafetyTool(pi, ctx) {
2261
3068
  }
2262
3069
  const response = await callBridge(bridge, commandMap[params.op], req);
2263
3070
  return textResult(JSON.stringify(response, null, 2));
3071
+ },
3072
+ renderCall(args, theme, context) {
3073
+ return renderSafetyCall(args, theme, context);
3074
+ },
3075
+ renderResult(result, _options, theme, context) {
3076
+ return renderSafetyResult(result, context.args, theme, context);
2264
3077
  }
2265
3078
  });
2266
3079
  }
@@ -2271,6 +3084,53 @@ var SearchParams2 = Type11.Object({
2271
3084
  query: Type11.String({ description: "Natural-language description of the code to find" }),
2272
3085
  topK: Type11.Optional(Type11.Number({ description: "Maximum number of results (default: 10, max: 100)" }))
2273
3086
  });
3087
+ function buildSemanticSections(args, payload, theme) {
3088
+ const response = asRecord2(payload);
3089
+ if (!response)
3090
+ return [theme.fg("muted", "No semantic search result.")];
3091
+ const status = asString(response.status) ?? "unknown";
3092
+ const sections = [
3093
+ `${theme.fg(status === "ready" ? "success" : "warning", `index: ${status}`)} ${theme.fg("muted", `query=${JSON.stringify(args.query)} topK=${args.topK ?? 10}`)}`
3094
+ ];
3095
+ if (status !== "ready") {
3096
+ sections.push(asString(response.text) ?? theme.fg("muted", "Semantic index is not ready."));
3097
+ return sections;
3098
+ }
3099
+ const results = asRecords(response.results);
3100
+ if (results.length === 0) {
3101
+ sections.push(theme.fg("muted", "No semantic matches found."));
3102
+ return sections;
3103
+ }
3104
+ const grouped = groupByFile(results, (result) => asString(result.file));
3105
+ for (const [file, fileResults] of grouped.entries()) {
3106
+ const lines = [theme.fg("accent", shortenPath(file))];
3107
+ fileResults.forEach((result) => {
3108
+ const score = asNumber(result.score);
3109
+ const startLine = asNumber(result.start_line);
3110
+ const endLine = asNumber(result.end_line);
3111
+ const range = startLine !== undefined ? `${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : "?";
3112
+ const kind = asString(result.kind) ?? "symbol";
3113
+ const name = asString(result.name) ?? "(unknown)";
3114
+ lines.push(` ↳ ${name} ${theme.fg("muted", `[${kind}] lines ${range}${score !== undefined ? ` score ${score.toFixed(3)}` : ""}`)}`);
3115
+ const snippet = asString(result.snippet);
3116
+ if (snippet) {
3117
+ lines.push(...snippet.split(`
3118
+ `).map((line) => ` ${line}`));
3119
+ }
3120
+ });
3121
+ sections.push(lines.join(`
3122
+ `));
3123
+ }
3124
+ return sections;
3125
+ }
3126
+ function renderSemanticCall(args, theme, context) {
3127
+ return renderToolCall("semantic search", theme.fg("toolOutput", args.query), theme, context);
3128
+ }
3129
+ function renderSemanticResult(result, args, theme, context) {
3130
+ if (context.isError)
3131
+ return renderErrorResult(result, "semantic search failed", theme, context);
3132
+ return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
3133
+ }
2274
3134
  function registerSemanticTool(pi, ctx) {
2275
3135
  pi.registerTool({
2276
3136
  name: "aft_search",
@@ -2284,6 +3144,12 @@ function registerSemanticTool(pi, ctx) {
2284
3144
  req.top_k = params.topK;
2285
3145
  const response = await callBridge(bridge, "semantic_search", req);
2286
3146
  return textResult(response.text ?? JSON.stringify(response, null, 2));
3147
+ },
3148
+ renderCall(args, theme, context) {
3149
+ return renderSemanticCall(args, theme, context);
3150
+ },
3151
+ renderResult(result, _options, theme, context) {
3152
+ return renderSemanticResult(result, context.args, theme, context);
2287
3153
  }
2288
3154
  });
2289
3155
  }
@@ -2311,6 +3177,37 @@ var TransformParams = Type12.Object({
2311
3177
  description: "Validation level (default: syntax)"
2312
3178
  }))
2313
3179
  });
3180
+ function buildTransformSections(args, payload, theme) {
3181
+ const response = asRecord2(payload);
3182
+ if (!response)
3183
+ return [theme.fg("muted", "No transform result.")];
3184
+ if (response.dry_run === true) {
3185
+ return [
3186
+ theme.fg("warning", `[dry run] ${args.op}`),
3187
+ asString(response.diff) ?? theme.fg("muted", "No diff available.")
3188
+ ];
3189
+ }
3190
+ const target = asString(response.target) ?? asString(response.scope) ?? args.target ?? args.container ?? args.field ?? args.filePath;
3191
+ return [
3192
+ `${theme.fg("success", "transformed")} ${theme.fg("accent", args.op)}`,
3193
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString(response.file) ?? args.filePath)}`,
3194
+ target ? `${theme.fg("muted", "target")} ${target}` : theme.fg("muted", "No target metadata.")
3195
+ ];
3196
+ }
3197
+ function renderTransformCall(args, theme, context) {
3198
+ const target = args.target ?? args.container ?? args.field;
3199
+ const summary = [
3200
+ theme.fg("accent", args.op),
3201
+ accentPath(theme, args.filePath),
3202
+ target ? theme.fg("toolOutput", target) : undefined
3203
+ ].filter(Boolean).join(" ");
3204
+ return renderToolCall("transform", summary, theme, context);
3205
+ }
3206
+ function renderTransformResult(result, args, theme, context) {
3207
+ if (context.isError)
3208
+ return renderErrorResult(result, "transform failed", theme, context);
3209
+ return renderSections(buildTransformSections(args, extractStructuredPayload(result), theme), context);
3210
+ }
2314
3211
  function registerStructureTool(pi, ctx) {
2315
3212
  pi.registerTool({
2316
3213
  name: "aft_transform",
@@ -2346,6 +3243,12 @@ function registerStructureTool(pi, ctx) {
2346
3243
  req.validate = params.validate;
2347
3244
  const response = await callBridge(bridge, params.op, req);
2348
3245
  return textResult(JSON.stringify(response, null, 2));
3246
+ },
3247
+ renderCall(args, theme, context) {
3248
+ return renderTransformCall(args, theme, context);
3249
+ },
3250
+ renderResult(result, _options, theme, context) {
3251
+ return renderTransformResult(result, context.args, theme, context);
2349
3252
  }
2350
3253
  });
2351
3254
  }
@@ -2360,7 +3263,7 @@ var PLUGIN_VERSION = (() => {
2360
3263
  }
2361
3264
  })();
2362
3265
  function resolveStorageDir() {
2363
- return join8(homedir6(), ".pi", "agent", "aft");
3266
+ return join8(homedir7(), ".pi", "agent", "aft");
2364
3267
  }
2365
3268
  function resolveToolSurface(config) {
2366
3269
  const surface = config.tool_surface ?? "recommended";