@cortexkit/aft-pi 0.14.0 → 0.14.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/index.js +260 -45
- package/dist/tools/diff-format.d.ts +24 -0
- package/dist/tools/diff-format.d.ts.map +1 -0
- package/dist/tools/hoisted.d.ts +37 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/package.json +12 -9
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 homedir6 } from "node:os";
|
|
7
7
|
import { join as join8 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/shared/status.ts
|
|
@@ -1471,8 +1471,117 @@ function registerFsTools(pi, ctx, surface) {
|
|
|
1471
1471
|
|
|
1472
1472
|
// src/tools/hoisted.ts
|
|
1473
1473
|
import { stat } from "node:fs/promises";
|
|
1474
|
+
import { homedir as homedir5 } from "node:os";
|
|
1474
1475
|
import { resolve } from "node:path";
|
|
1476
|
+
import {
|
|
1477
|
+
renderDiff
|
|
1478
|
+
} from "@mariozechner/pi-coding-agent";
|
|
1479
|
+
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
1475
1480
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
1481
|
+
|
|
1482
|
+
// src/tools/diff-format.ts
|
|
1483
|
+
import { diffLines } from "diff";
|
|
1484
|
+
var DEFAULT_CONTEXT_LINES = 4;
|
|
1485
|
+
function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_LINES) {
|
|
1486
|
+
const parts = diffLines(oldContent, newContent);
|
|
1487
|
+
const output = [];
|
|
1488
|
+
const oldLines = oldContent.split(`
|
|
1489
|
+
`);
|
|
1490
|
+
const newLines = newContent.split(`
|
|
1491
|
+
`);
|
|
1492
|
+
const maxLineNum = Math.max(oldLines.length, newLines.length);
|
|
1493
|
+
const lineNumWidth = String(maxLineNum).length;
|
|
1494
|
+
const pad = (n) => String(n).padStart(lineNumWidth, " ");
|
|
1495
|
+
const blank = " ".repeat(lineNumWidth);
|
|
1496
|
+
let oldLineNum = 1;
|
|
1497
|
+
let newLineNum = 1;
|
|
1498
|
+
let lastWasChange = false;
|
|
1499
|
+
let firstChangedLine;
|
|
1500
|
+
for (let i = 0;i < parts.length; i++) {
|
|
1501
|
+
const part = parts[i];
|
|
1502
|
+
const raw = part.value.split(`
|
|
1503
|
+
`);
|
|
1504
|
+
if (raw[raw.length - 1] === "")
|
|
1505
|
+
raw.pop();
|
|
1506
|
+
if (part.added || part.removed) {
|
|
1507
|
+
if (firstChangedLine === undefined)
|
|
1508
|
+
firstChangedLine = newLineNum;
|
|
1509
|
+
for (const line of raw) {
|
|
1510
|
+
if (part.added) {
|
|
1511
|
+
output.push(`+${pad(newLineNum)} ${line}`);
|
|
1512
|
+
newLineNum++;
|
|
1513
|
+
} else {
|
|
1514
|
+
output.push(`-${pad(oldLineNum)} ${line}`);
|
|
1515
|
+
oldLineNum++;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
lastWasChange = true;
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
const nextIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
|
|
1522
|
+
const hasLeading = lastWasChange;
|
|
1523
|
+
const hasTrailing = nextIsChange;
|
|
1524
|
+
if (hasLeading && hasTrailing) {
|
|
1525
|
+
if (raw.length <= contextLines * 2) {
|
|
1526
|
+
for (const line of raw) {
|
|
1527
|
+
output.push(` ${pad(oldLineNum)} ${line}`);
|
|
1528
|
+
oldLineNum++;
|
|
1529
|
+
newLineNum++;
|
|
1530
|
+
}
|
|
1531
|
+
} else {
|
|
1532
|
+
for (const line of raw.slice(0, contextLines)) {
|
|
1533
|
+
output.push(` ${pad(oldLineNum)} ${line}`);
|
|
1534
|
+
oldLineNum++;
|
|
1535
|
+
newLineNum++;
|
|
1536
|
+
}
|
|
1537
|
+
const skipped = raw.length - contextLines * 2;
|
|
1538
|
+
output.push(` ${blank} ...`);
|
|
1539
|
+
oldLineNum += skipped;
|
|
1540
|
+
newLineNum += skipped;
|
|
1541
|
+
for (const line of raw.slice(raw.length - contextLines)) {
|
|
1542
|
+
output.push(` ${pad(oldLineNum)} ${line}`);
|
|
1543
|
+
oldLineNum++;
|
|
1544
|
+
newLineNum++;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
} else if (hasLeading) {
|
|
1548
|
+
const shown = raw.slice(0, contextLines);
|
|
1549
|
+
for (const line of shown) {
|
|
1550
|
+
output.push(` ${pad(oldLineNum)} ${line}`);
|
|
1551
|
+
oldLineNum++;
|
|
1552
|
+
newLineNum++;
|
|
1553
|
+
}
|
|
1554
|
+
const skipped = raw.length - shown.length;
|
|
1555
|
+
if (skipped > 0) {
|
|
1556
|
+
output.push(` ${blank} ...`);
|
|
1557
|
+
oldLineNum += skipped;
|
|
1558
|
+
newLineNum += skipped;
|
|
1559
|
+
}
|
|
1560
|
+
} else if (hasTrailing) {
|
|
1561
|
+
const shownCount = Math.min(contextLines, raw.length);
|
|
1562
|
+
const shown = raw.slice(raw.length - shownCount);
|
|
1563
|
+
const skipped = raw.length - shown.length;
|
|
1564
|
+
if (skipped > 0) {
|
|
1565
|
+
output.push(` ${blank} ...`);
|
|
1566
|
+
oldLineNum += skipped;
|
|
1567
|
+
newLineNum += skipped;
|
|
1568
|
+
}
|
|
1569
|
+
for (const line of shown) {
|
|
1570
|
+
output.push(` ${pad(oldLineNum)} ${line}`);
|
|
1571
|
+
oldLineNum++;
|
|
1572
|
+
newLineNum++;
|
|
1573
|
+
}
|
|
1574
|
+
} else {
|
|
1575
|
+
oldLineNum += raw.length;
|
|
1576
|
+
newLineNum += raw.length;
|
|
1577
|
+
}
|
|
1578
|
+
lastWasChange = false;
|
|
1579
|
+
}
|
|
1580
|
+
return { diff: output.join(`
|
|
1581
|
+
`), firstChangedLine };
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// src/tools/hoisted.ts
|
|
1476
1585
|
var ReadParams = Type4.Object({
|
|
1477
1586
|
path: Type4.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
1478
1587
|
offset: Type4.Optional(Type4.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
@@ -1504,6 +1613,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
1504
1613
|
name: "read",
|
|
1505
1614
|
label: "read",
|
|
1506
1615
|
description: "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos and correctly handles images/PDFs as attachments.",
|
|
1616
|
+
promptSnippet: "Read file contents (supports offset/limit for large files)",
|
|
1617
|
+
promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
|
1507
1618
|
parameters: ReadParams,
|
|
1508
1619
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
1509
1620
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
@@ -1530,30 +1641,24 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
1530
1641
|
pi.registerTool({
|
|
1531
1642
|
name: "write",
|
|
1532
1643
|
label: "write",
|
|
1533
|
-
description: "Write a file atomically with per-file backup, optional auto-format, and inline LSP diagnostics. Parent directories are created automatically. Overwrites existing files.",
|
|
1644
|
+
description: "Write a file atomically with per-file backup, optional auto-format, and inline LSP diagnostics. Parent directories are created automatically. Overwrites existing files. Uses `filePath` (not `path`).",
|
|
1645
|
+
promptSnippet: "Create or overwrite files (uses filePath; auto-formats; returns LSP diagnostics inline)",
|
|
1646
|
+
promptGuidelines: ["Use write only for new files or complete rewrites."],
|
|
1534
1647
|
parameters: WriteParams,
|
|
1535
1648
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
1536
1649
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
1537
1650
|
const response = await callBridge(bridge, "write", {
|
|
1538
1651
|
file: params.filePath,
|
|
1539
|
-
content: params.content
|
|
1540
|
-
|
|
1541
|
-
const diffAdd = response.diff?.additions ?? 0;
|
|
1542
|
-
const diffDel = response.diff?.deletions ?? 0;
|
|
1543
|
-
const diagnostics = response.lsp_diagnostics;
|
|
1544
|
-
let summary = `Wrote ${params.filePath} (+${diffAdd}/-${diffDel})`;
|
|
1545
|
-
if (diagnostics && diagnostics.length > 0) {
|
|
1546
|
-
summary += `
|
|
1547
|
-
|
|
1548
|
-
LSP diagnostics:
|
|
1549
|
-
${JSON.stringify(diagnostics, null, 2)}`;
|
|
1550
|
-
}
|
|
1551
|
-
return textResult(summary, {
|
|
1552
|
-
filePath: params.filePath,
|
|
1553
|
-
additions: diffAdd,
|
|
1554
|
-
deletions: diffDel,
|
|
1555
|
-
diagnostics
|
|
1652
|
+
content: params.content,
|
|
1653
|
+
include_diff: true
|
|
1556
1654
|
});
|
|
1655
|
+
return buildMutationResult(params.filePath, response);
|
|
1656
|
+
},
|
|
1657
|
+
renderCall(args, theme, context) {
|
|
1658
|
+
return renderMutationCall("write", args?.filePath, theme, context);
|
|
1659
|
+
},
|
|
1660
|
+
renderResult(result, _options, theme, context) {
|
|
1661
|
+
return renderMutationResult(result, theme, context);
|
|
1557
1662
|
}
|
|
1558
1663
|
});
|
|
1559
1664
|
}
|
|
@@ -1561,39 +1666,33 @@ ${JSON.stringify(diagnostics, null, 2)}`;
|
|
|
1561
1666
|
pi.registerTool({
|
|
1562
1667
|
name: "edit",
|
|
1563
1668
|
label: "edit",
|
|
1564
|
-
description: "Find-and-replace edit with progressive fuzzy matching (handles whitespace and Unicode drift).
|
|
1669
|
+
description: "Find-and-replace edit with progressive fuzzy matching (handles whitespace and Unicode drift). Uses `filePath`, `oldString`, `newString`. Errors on multiple matches — use `occurrence` to pick one, or `replaceAll: true`. Always returns LSP diagnostics inline.",
|
|
1670
|
+
promptSnippet: "Targeted find-and-replace (uses filePath/oldString/newString; occurrence or replaceAll for disambiguation; fuzzy whitespace matching)",
|
|
1671
|
+
promptGuidelines: [
|
|
1672
|
+
"Prefer edit over write when changing part of an existing file.",
|
|
1673
|
+
"Include enough surrounding context in oldString to make the match unique, or set replaceAll/occurrence explicitly."
|
|
1674
|
+
],
|
|
1565
1675
|
parameters: EditParams,
|
|
1566
1676
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
1567
1677
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
1568
1678
|
const req = {
|
|
1569
1679
|
file: params.filePath,
|
|
1570
1680
|
match: params.oldString ?? "",
|
|
1571
|
-
replacement: params.newString ?? ""
|
|
1681
|
+
replacement: params.newString ?? "",
|
|
1682
|
+
include_diff: true
|
|
1572
1683
|
};
|
|
1573
1684
|
if (params.replaceAll === true)
|
|
1574
1685
|
req.replace_all = true;
|
|
1575
1686
|
if (params.occurrence !== undefined)
|
|
1576
1687
|
req.occurrence = params.occurrence;
|
|
1577
1688
|
const response = await callBridge(bridge, "edit_match", req);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
summary += `
|
|
1586
|
-
|
|
1587
|
-
LSP diagnostics:
|
|
1588
|
-
${JSON.stringify(diagnostics, null, 2)}`;
|
|
1589
|
-
}
|
|
1590
|
-
return textResult(summary, {
|
|
1591
|
-
filePath: params.filePath,
|
|
1592
|
-
additions: diffAdd,
|
|
1593
|
-
deletions: diffDel,
|
|
1594
|
-
replacements,
|
|
1595
|
-
diagnostics
|
|
1596
|
-
});
|
|
1689
|
+
return buildMutationResult(params.filePath, response);
|
|
1690
|
+
},
|
|
1691
|
+
renderCall(args, theme, context) {
|
|
1692
|
+
return renderMutationCall("edit", args?.filePath, theme, context);
|
|
1693
|
+
},
|
|
1694
|
+
renderResult(result, _options, theme, context) {
|
|
1695
|
+
return renderMutationResult(result, theme, context);
|
|
1597
1696
|
}
|
|
1598
1697
|
});
|
|
1599
1698
|
}
|
|
@@ -1602,6 +1701,8 @@ ${JSON.stringify(diagnostics, null, 2)}`;
|
|
|
1602
1701
|
name: "grep",
|
|
1603
1702
|
label: "grep",
|
|
1604
1703
|
description: "Search for a regex pattern across files. Uses AFT's trigram index inside the project root for fast repeated queries, and falls back to ripgrep for paths outside the project root.",
|
|
1704
|
+
promptSnippet: "Fast regex search across files (trigram-indexed inside the project root)",
|
|
1705
|
+
promptGuidelines: ["Prefer grep over bash-invoked find/rg for in-project searches."],
|
|
1605
1706
|
parameters: GrepParams,
|
|
1606
1707
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
1607
1708
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
@@ -1621,6 +1722,112 @@ ${JSON.stringify(diagnostics, null, 2)}`;
|
|
|
1621
1722
|
});
|
|
1622
1723
|
}
|
|
1623
1724
|
}
|
|
1725
|
+
function buildMutationResult(filePath, response) {
|
|
1726
|
+
const diffObj = response.diff;
|
|
1727
|
+
const additions = diffObj?.additions ?? 0;
|
|
1728
|
+
const deletions = diffObj?.deletions ?? 0;
|
|
1729
|
+
const replacements = response.replacements;
|
|
1730
|
+
const diagnostics = response.lsp_diagnostics;
|
|
1731
|
+
const truncated = diffObj?.truncated === true;
|
|
1732
|
+
let diffText;
|
|
1733
|
+
let firstChangedLine;
|
|
1734
|
+
if (diffObj && !truncated && typeof diffObj.before === "string" && typeof diffObj.after === "string") {
|
|
1735
|
+
const formatted = formatDiffForPi(diffObj.before, diffObj.after);
|
|
1736
|
+
diffText = formatted.diff;
|
|
1737
|
+
firstChangedLine = formatted.firstChangedLine;
|
|
1738
|
+
}
|
|
1739
|
+
const summaryHeader = replacements !== undefined ? `Edited ${filePath} (+${additions}/-${deletions}, ${replacements} replacement${replacements === 1 ? "" : "s"})` : `Wrote ${filePath} (+${additions}/-${deletions})`;
|
|
1740
|
+
let text = summaryHeader;
|
|
1741
|
+
if (diffText)
|
|
1742
|
+
text += `
|
|
1743
|
+
|
|
1744
|
+
${diffText}`;
|
|
1745
|
+
if (truncated) {
|
|
1746
|
+
text += `
|
|
1747
|
+
|
|
1748
|
+
(diff truncated — file too large to include before/after content)`;
|
|
1749
|
+
}
|
|
1750
|
+
if (diagnostics && diagnostics.length > 0) {
|
|
1751
|
+
text += `
|
|
1752
|
+
|
|
1753
|
+
LSP diagnostics:
|
|
1754
|
+
${formatDiagnosticsText(diagnostics)}`;
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
content: [{ type: "text", text }],
|
|
1758
|
+
details: {
|
|
1759
|
+
diff: diffText,
|
|
1760
|
+
firstChangedLine,
|
|
1761
|
+
additions,
|
|
1762
|
+
deletions,
|
|
1763
|
+
replacements,
|
|
1764
|
+
diagnostics,
|
|
1765
|
+
truncated: truncated || undefined
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
function formatDiagnosticsText(diagnostics) {
|
|
1770
|
+
try {
|
|
1771
|
+
return diagnostics.map((d) => {
|
|
1772
|
+
if (d && typeof d === "object") {
|
|
1773
|
+
const obj = d;
|
|
1774
|
+
const line = obj.line ?? obj.startLine ?? "?";
|
|
1775
|
+
const severity = obj.severity ?? "info";
|
|
1776
|
+
const msg = obj.message ?? JSON.stringify(obj);
|
|
1777
|
+
return ` [${severity}] line ${line}: ${msg}`;
|
|
1778
|
+
}
|
|
1779
|
+
return ` ${String(d)}`;
|
|
1780
|
+
}).join(`
|
|
1781
|
+
`);
|
|
1782
|
+
} catch {
|
|
1783
|
+
return JSON.stringify(diagnostics, null, 2);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
function reuseText(last) {
|
|
1787
|
+
return last instanceof Text ? last : new Text("", 0, 0);
|
|
1788
|
+
}
|
|
1789
|
+
function reuseContainer(last) {
|
|
1790
|
+
return last instanceof Container ? last : new Container;
|
|
1791
|
+
}
|
|
1792
|
+
function renderMutationCall(toolName, filePath, theme, context) {
|
|
1793
|
+
const text = reuseText(context.lastComponent);
|
|
1794
|
+
const pathDisplay = filePath ? theme.fg("accent", shortenPath(filePath)) : theme.fg("toolOutput", "...");
|
|
1795
|
+
text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
|
|
1796
|
+
return text;
|
|
1797
|
+
}
|
|
1798
|
+
function renderMutationResult(result, theme, context) {
|
|
1799
|
+
if (context.isError) {
|
|
1800
|
+
const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
|
|
1801
|
+
`).trim();
|
|
1802
|
+
const text = reuseText(context.lastComponent);
|
|
1803
|
+
text.setText(`
|
|
1804
|
+
${theme.fg("error", errorText || "edit failed")}`);
|
|
1805
|
+
return text;
|
|
1806
|
+
}
|
|
1807
|
+
const details = result.details;
|
|
1808
|
+
const diff = typeof details?.diff === "string" ? details.diff : undefined;
|
|
1809
|
+
if (!diff) {
|
|
1810
|
+
const additions = details?.additions ?? 0;
|
|
1811
|
+
const deletions = details?.deletions ?? 0;
|
|
1812
|
+
const text = reuseText(context.lastComponent);
|
|
1813
|
+
const summary = theme.fg("success", `+${additions}/-${deletions}`);
|
|
1814
|
+
const suffix = details?.truncated ? ` ${theme.fg("muted", "(diff truncated)")}` : "";
|
|
1815
|
+
text.setText(`
|
|
1816
|
+
${summary}${suffix}`);
|
|
1817
|
+
return text;
|
|
1818
|
+
}
|
|
1819
|
+
const container = reuseContainer(context.lastComponent);
|
|
1820
|
+
container.clear();
|
|
1821
|
+
container.addChild(new Spacer(1));
|
|
1822
|
+
container.addChild(new Text(renderDiff(diff), 1, 0));
|
|
1823
|
+
return container;
|
|
1824
|
+
}
|
|
1825
|
+
function shortenPath(path2) {
|
|
1826
|
+
const home = homedir5();
|
|
1827
|
+
if (path2.startsWith(home))
|
|
1828
|
+
return `~${path2.slice(home.length)}`;
|
|
1829
|
+
return path2;
|
|
1830
|
+
}
|
|
1624
1831
|
async function resolvePathArg(cwd, path2) {
|
|
1625
1832
|
const abs = resolve(cwd, path2);
|
|
1626
1833
|
try {
|
|
@@ -2038,12 +2245,20 @@ function registerSafetyTool(pi, ctx) {
|
|
|
2038
2245
|
list: "list_checkpoints"
|
|
2039
2246
|
};
|
|
2040
2247
|
const req = {};
|
|
2041
|
-
if (params.filePath)
|
|
2042
|
-
req.file = params.filePath;
|
|
2043
2248
|
if (params.name)
|
|
2044
2249
|
req.name = params.name;
|
|
2045
|
-
if (params.
|
|
2046
|
-
|
|
2250
|
+
if (params.op === "checkpoint") {
|
|
2251
|
+
if (params.files) {
|
|
2252
|
+
req.files = params.files;
|
|
2253
|
+
} else if (params.filePath) {
|
|
2254
|
+
req.files = [params.filePath];
|
|
2255
|
+
}
|
|
2256
|
+
} else {
|
|
2257
|
+
if (params.filePath)
|
|
2258
|
+
req.file = params.filePath;
|
|
2259
|
+
if (params.files)
|
|
2260
|
+
req.files = params.files;
|
|
2261
|
+
}
|
|
2047
2262
|
const response = await callBridge(bridge, commandMap[params.op], req);
|
|
2048
2263
|
return textResult(JSON.stringify(response, null, 2));
|
|
2049
2264
|
}
|
|
@@ -2145,7 +2360,7 @@ var PLUGIN_VERSION = (() => {
|
|
|
2145
2360
|
}
|
|
2146
2361
|
})();
|
|
2147
2362
|
function resolveStorageDir() {
|
|
2148
|
-
return join8(
|
|
2363
|
+
return join8(homedir6(), ".pi", "agent", "aft");
|
|
2149
2364
|
}
|
|
2150
2365
|
function resolveToolSurface(config) {
|
|
2151
2366
|
const surface = config.tool_surface ?? "recommended";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff formatting helpers for Pi hoisted tool renderers.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the output shape Pi's built-in `renderDiff` expects:
|
|
5
|
+
* "+NN content" for added lines
|
|
6
|
+
* "-NN content" for removed lines
|
|
7
|
+
* " NN content" for context lines
|
|
8
|
+
* " NN ..." for context truncation markers
|
|
9
|
+
*
|
|
10
|
+
* We don't re-export Pi's internal `generateDiffString` because it isn't
|
|
11
|
+
* public. Reimplementing it as a thin wrapper around the `diff` npm package
|
|
12
|
+
* (also used by Pi itself) is ~40 lines and keeps us decoupled from Pi's
|
|
13
|
+
* private internals.
|
|
14
|
+
*/
|
|
15
|
+
export interface FormattedDiff {
|
|
16
|
+
diff: string;
|
|
17
|
+
firstChangedLine: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate a line-numbered diff string suitable for Pi's `renderDiff`.
|
|
21
|
+
* Matches the format of Pi's built-in edit tool result.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatDiffForPi(oldContent: string, newContent: string, contextLines?: number): FormattedDiff;
|
|
24
|
+
//# sourceMappingURL=diff-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-format.d.ts","sourceRoot":"","sources":["../../src/tools/diff-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAID;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAwB,GACnC,aAAa,CAsGf"}
|
package/dist/tools/hoisted.d.ts
CHANGED
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
* Hoisted tool overrides — replace Pi's built-in read/write/edit/grep with
|
|
3
3
|
* AFT-backed Rust implementations. Registering a tool with the same name as
|
|
4
4
|
* a built-in replaces the built-in entirely.
|
|
5
|
+
*
|
|
6
|
+
* Each tool provides:
|
|
7
|
+
* - `promptSnippet` / `promptGuidelines`: teach the model our argument shape
|
|
8
|
+
* in Pi's system prompt (Pi's built-ins use generic one-liners otherwise).
|
|
9
|
+
* - `renderCall` / `renderResult` for `write` and `edit`: without these,
|
|
10
|
+
* Pi's ToolExecutionComponent falls back to the *built-in* renderer for
|
|
11
|
+
* same-named tools, which reads `path` and `edits[]` and garbles our
|
|
12
|
+
* `filePath` / `oldString` / `newString` output (issue #15).
|
|
13
|
+
* - Structured `details: { diff, firstChangedLine }` so the rendered diff
|
|
14
|
+
* also ends up in the agent's message stream, matching Pi's convention.
|
|
15
|
+
*
|
|
16
|
+
* `read` and `grep` keep the default text-only result rendering because our
|
|
17
|
+
* payload (`path`, `pattern`) already aligns with Pi's built-in arg shape.
|
|
5
18
|
*/
|
|
6
|
-
import type
|
|
19
|
+
import { type AgentToolResult, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
20
|
import type { PluginContext } from "../types.js";
|
|
8
21
|
export interface ToolSurfaceFlags {
|
|
9
22
|
hoistRead: boolean;
|
|
@@ -11,5 +24,28 @@ export interface ToolSurfaceFlags {
|
|
|
11
24
|
hoistEdit: boolean;
|
|
12
25
|
hoistGrep: boolean;
|
|
13
26
|
}
|
|
27
|
+
/** Details surfaced to both renderer and agent message stream. */
|
|
28
|
+
interface FileMutationDetails {
|
|
29
|
+
diff?: string;
|
|
30
|
+
firstChangedLine?: number;
|
|
31
|
+
additions: number;
|
|
32
|
+
deletions: number;
|
|
33
|
+
replacements?: number;
|
|
34
|
+
diagnostics?: unknown[];
|
|
35
|
+
/**
|
|
36
|
+
* True when Rust returned `diff.truncated = true` — the before/after strings
|
|
37
|
+
* were omitted because the file exceeded the diff size cap, so we have no
|
|
38
|
+
* line-level diff to render. Both the agent-facing text and the TUI renderer
|
|
39
|
+
* surface this explicitly rather than silently showing a summary.
|
|
40
|
+
*/
|
|
41
|
+
truncated?: boolean;
|
|
42
|
+
}
|
|
14
43
|
export declare function registerHoistedTools(pi: ExtensionAPI, ctx: PluginContext, surface: ToolSurfaceFlags): void;
|
|
44
|
+
/**
|
|
45
|
+
* Shape the bridge `edit_match` / `write` response into an `AgentToolResult`
|
|
46
|
+
* Pi can render. Exported for unit tests covering truncation and diagnostics
|
|
47
|
+
* behavior without spinning up a real bridge.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildMutationResult(filePath: string, response: Record<string, unknown>): AgentToolResult<FileMutationDetails>;
|
|
50
|
+
export {};
|
|
15
51
|
//# sourceMappingURL=hoisted.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,YAAY,EAGlB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqDjD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,kEAAkE;AAClE,UAAU,mBAAmB;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,gBAAgB,GACxB,IAAI,CA8IN;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,eAAe,CAAC,mBAAmB,CAAC,CA6DtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkBjD,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkBjD,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAkD7E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-pi",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,29 +15,32 @@
|
|
|
15
15
|
"README.md"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "bun build src/index.ts --outdir dist --target node --format esm --external @mariozechner/pi-coding-agent --external @mariozechner/pi-ai --external @mariozechner/pi-tui --external @sinclair/typebox && tsc --emitDeclarationOnly",
|
|
18
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm --external @mariozechner/pi-coding-agent --external @mariozechner/pi-ai --external @mariozechner/pi-tui --external @sinclair/typebox --external diff && tsc --emitDeclarationOnly",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
20
20
|
"test": "bun test src/__tests__/",
|
|
21
21
|
"prepublishOnly": "bun run build"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@sinclair/typebox": "^0.34.33"
|
|
24
|
+
"@sinclair/typebox": "^0.34.33",
|
|
25
|
+
"diff": "^8.0.4"
|
|
25
26
|
},
|
|
26
27
|
"optionalDependencies": {
|
|
27
|
-
"@cortexkit/aft-darwin-arm64": "0.14.
|
|
28
|
-
"@cortexkit/aft-darwin-x64": "0.14.
|
|
29
|
-
"@cortexkit/aft-linux-arm64": "0.14.
|
|
30
|
-
"@cortexkit/aft-linux-x64": "0.14.
|
|
31
|
-
"@cortexkit/aft-win32-x64": "0.14.
|
|
28
|
+
"@cortexkit/aft-darwin-arm64": "0.14.1",
|
|
29
|
+
"@cortexkit/aft-darwin-x64": "0.14.1",
|
|
30
|
+
"@cortexkit/aft-linux-arm64": "0.14.1",
|
|
31
|
+
"@cortexkit/aft-linux-x64": "0.14.1",
|
|
32
|
+
"@cortexkit/aft-win32-x64": "0.14.1"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@mariozechner/pi-coding-agent": "*",
|
|
35
36
|
"@mariozechner/pi-ai": "*",
|
|
37
|
+
"@mariozechner/pi-tui": "*",
|
|
36
38
|
"@types/node": "^22.0.0",
|
|
37
39
|
"typescript": "^5.8.0"
|
|
38
40
|
},
|
|
39
41
|
"peerDependencies": {
|
|
40
|
-
"@mariozechner/pi-coding-agent": "*"
|
|
42
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
43
|
+
"@mariozechner/pi-tui": "*"
|
|
41
44
|
},
|
|
42
45
|
"exports": {
|
|
43
46
|
".": {
|