pi-readseek 0.7.7 → 0.7.9
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/README.md +28 -46
- package/dist/index.js +7333 -0
- package/package.json +8 -7
- package/index.ts +0 -147
- package/src/binary-detect.ts +0 -22
- package/src/check.ts +0 -92
- package/src/coerce-obvious-int.ts +0 -41
- package/src/confusable-hyphens.ts +0 -6
- package/src/def.ts +0 -143
- package/src/diff-data.ts +0 -302
- package/src/edit-classify.ts +0 -58
- package/src/edit-diff.ts +0 -359
- package/src/edit-output.ts +0 -110
- package/src/edit-render-helpers.ts +0 -141
- package/src/edit-syntax-validate.ts +0 -65
- package/src/edit.ts +0 -686
- package/src/file-map.ts +0 -17
- package/src/fs-error.ts +0 -33
- package/src/grep-budget.ts +0 -35
- package/src/grep-output.ts +0 -181
- package/src/grep-render-helpers.ts +0 -77
- package/src/grep-symbol-scope.ts +0 -184
- package/src/grep.ts +0 -608
- package/src/hashline.ts +0 -736
- package/src/hover.ts +0 -140
- package/src/path-utils.ts +0 -24
- package/src/pending-diff-preview.ts +0 -269
- package/src/read-local-bundle.ts +0 -75
- package/src/read-output.ts +0 -186
- package/src/read-render-helpers.ts +0 -104
- package/src/read.ts +0 -661
- package/src/readseek/constants.ts +0 -17
- package/src/readseek/enums.ts +0 -38
- package/src/readseek/formatter.ts +0 -350
- package/src/readseek/symbol-error-format.ts +0 -18
- package/src/readseek/symbol-lookup.ts +0 -240
- package/src/readseek/symbol-tree.ts +0 -21
- package/src/readseek/types.ts +0 -49
- package/src/readseek-client.ts +0 -1250
- package/src/readseek-params.ts +0 -39
- package/src/readseek-settings.ts +0 -263
- package/src/readseek-value.ts +0 -184
- package/src/refs-output.ts +0 -47
- package/src/refs.ts +0 -161
- package/src/register-tool.ts +0 -29
- package/src/rename.ts +0 -182
- package/src/replace-symbol.ts +0 -77
- package/src/runtime.ts +0 -3
- package/src/search-output.ts +0 -50
- package/src/search.ts +0 -214
- package/src/session-anchors.ts +0 -34
- package/src/stat-search-path.ts +0 -29
- package/src/syntax-validate-mode.ts +0 -15
- package/src/tool-prompt-metadata.ts +0 -107
- package/src/tool-types.ts +0 -12
- package/src/tui-diff-component.ts +0 -108
- package/src/tui-diff-renderer.ts +0 -92
- package/src/tui-render-utils.ts +0 -286
- package/src/view.ts +0 -158
- package/src/write.ts +0 -357
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
4
4
|
"description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./index.
|
|
7
|
+
".": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"pi-package",
|
|
@@ -21,25 +21,26 @@
|
|
|
21
21
|
"license": "Apache-2.0",
|
|
22
22
|
"pi": {
|
|
23
23
|
"extensions": [
|
|
24
|
-
"./index.
|
|
24
|
+
"./dist/index.js"
|
|
25
25
|
]
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"
|
|
29
|
-
"src/",
|
|
28
|
+
"dist/",
|
|
30
29
|
"prompts/",
|
|
31
30
|
"LICENSE",
|
|
32
31
|
"README.md"
|
|
33
32
|
],
|
|
34
33
|
"scripts": {
|
|
35
|
-
"
|
|
34
|
+
"build": "bun build index.ts --outfile dist/index.js --target=node --packages=external",
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"test": "npm run build && node scripts/vitest-run.mjs",
|
|
36
37
|
"typecheck": "tsc --noEmit"
|
|
37
38
|
},
|
|
38
39
|
"engines": {
|
|
39
40
|
"node": ">=20.0.0"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.7.
|
|
43
|
+
"@jarkkojs/readseek": "^0.7.9",
|
|
43
44
|
"diff": "^8.0.3",
|
|
44
45
|
"xxhash-wasm": "^1.1.0"
|
|
45
46
|
},
|
package/index.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { registerReadTool } from "./src/read.js";
|
|
3
|
-
import { registerEditTool } from "./src/edit.js";
|
|
4
|
-
import { registerGrepTool } from "./src/grep.js";
|
|
5
|
-
import { registerSearchTool } from "./src/search.js";
|
|
6
|
-
import { registerRefsTool } from "./src/refs.js";
|
|
7
|
-
import { registerRenameTool } from "./src/rename.js";
|
|
8
|
-
import { registerHoverTool } from "./src/hover.js";
|
|
9
|
-
import { registerWriteTool } from "./src/write.js";
|
|
10
|
-
import { registerDefTool } from "./src/def.js";
|
|
11
|
-
import { registerCheckTool } from "./src/check.js";
|
|
12
|
-
import { registerViewTool } from "./src/view.js";
|
|
13
|
-
import { SessionAnchors } from "./src/session-anchors.js";
|
|
14
|
-
import { readSeekBinaryAvailability } from "./src/readseek-client.js";
|
|
15
|
-
import { resolveReadSeekJsonSettings, type ReadSeekSettingsWarning } from "./src/readseek-settings.js";
|
|
16
|
-
|
|
17
|
-
/** Built-in tool names that pi-readseek can replace with a readSeek-backed implementation. */
|
|
18
|
-
const REPLACEABLE_TOOLS = {
|
|
19
|
-
read: "readSeek_read",
|
|
20
|
-
edit: "readSeek_edit",
|
|
21
|
-
write: "readSeek_write",
|
|
22
|
-
grep: "readSeek_grep",
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
type ReplacedBuiltIn = keyof typeof REPLACEABLE_TOOLS;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Canonical list of readSeek tools in registration/activation order. Each
|
|
29
|
-
* replaceable entry carries the built-in name it swaps in when replaced; null
|
|
30
|
-
* entries are readSeek-only and never replaced.
|
|
31
|
-
*/
|
|
32
|
-
const READSEEK_TOOL_ENTRIES: ReadonlyArray<{ builtIn: ReplacedBuiltIn | null; readSeekName: string }> = [
|
|
33
|
-
{ builtIn: "read", readSeekName: "readSeek_read" },
|
|
34
|
-
{ builtIn: "edit", readSeekName: "readSeek_edit" },
|
|
35
|
-
{ builtIn: "grep", readSeekName: "readSeek_grep" },
|
|
36
|
-
{ builtIn: null, readSeekName: "readSeek_search" },
|
|
37
|
-
{ builtIn: null, readSeekName: "readSeek_refs" },
|
|
38
|
-
{ builtIn: null, readSeekName: "readSeek_rename" },
|
|
39
|
-
{ builtIn: null, readSeekName: "readSeek_hover" },
|
|
40
|
-
{ builtIn: "write", readSeekName: "readSeek_write" },
|
|
41
|
-
{ builtIn: null, readSeekName: "readSeek_def" },
|
|
42
|
-
{ builtIn: null, readSeekName: "readSeek_check" },
|
|
43
|
-
{ builtIn: null, readSeekName: "readSeek_view" },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
function formatSettingsWarning(warning: ReadSeekSettingsWarning): string {
|
|
47
|
-
return `${warning.message} (${warning.source})`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function editingPolicy(readName: string, editName: string, writeName: string): string {
|
|
51
|
-
return [
|
|
52
|
-
"ReadSeek editing policy:",
|
|
53
|
-
`- Prefer ${readName} when preparing to edit existing text; its LINE:HASH anchors are required by ${editName}.`,
|
|
54
|
-
`- Prefer ${editName} for existing text files, ${writeName} for whole-file creation or replacement, and readSeek_rename for symbol renames.`,
|
|
55
|
-
"- Do not use Pi's built-in edit or write when the corresponding ReadSeek tool is available.",
|
|
56
|
-
"- Use readSeek_check after source edits for a quick syntax check.",
|
|
57
|
-
].join("\n");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default function piReadSeekExtension(pi: ExtensionAPI): void {
|
|
61
|
-
const sessionAnchors = new SessionAnchors();
|
|
62
|
-
const markAnchored = (absolutePath: string) => sessionAnchors.markAnchored(absolutePath);
|
|
63
|
-
const hasFreshAnchors = (absolutePath: string) => sessionAnchors.hasFreshAnchors(absolutePath);
|
|
64
|
-
const forgetAnchors = (absolutePath: string) => sessionAnchors.forget(absolutePath);
|
|
65
|
-
|
|
66
|
-
// Resolve replacedTools at load time so the readSeek implementation is
|
|
67
|
-
// registered under the built-in name (e.g. "edit") when replaced. Extension
|
|
68
|
-
// tool registrations override built-ins of the same name in pi's tool
|
|
69
|
-
// definition registry, so registering under "edit" makes calls to `edit`
|
|
70
|
-
// dispatch to the readSeek implementation.
|
|
71
|
-
const { settings } = resolveReadSeekJsonSettings();
|
|
72
|
-
const replacedBuiltIns = new Set<ReplacedBuiltIn>(
|
|
73
|
-
(settings.replacedTools ?? []) as ReplacedBuiltIn[],
|
|
74
|
-
);
|
|
75
|
-
// Only swap registration under the built-in name when the readSeek binary
|
|
76
|
-
// is available; otherwise keep readSeek_* names so pi's built-ins stay
|
|
77
|
-
// intact and usable.
|
|
78
|
-
const binaryAvailable = readSeekBinaryAvailability().available;
|
|
79
|
-
const swap = (builtIn: ReplacedBuiltIn, readSeekName: string) =>
|
|
80
|
-
binaryAvailable && replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
|
|
81
|
-
|
|
82
|
-
const readName = swap("read", "readSeek_read");
|
|
83
|
-
const editName = swap("edit", "readSeek_edit");
|
|
84
|
-
const writeName = swap("write", "readSeek_write");
|
|
85
|
-
const grepName = swap("grep", "readSeek_grep");
|
|
86
|
-
const toolAliases = {
|
|
87
|
-
readSeek_read: readName,
|
|
88
|
-
readSeek_edit: editName,
|
|
89
|
-
readSeek_grep: grepName,
|
|
90
|
-
readSeek_write: writeName,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
registerReadTool(pi, { onSuccessfulRead: markAnchored, name: readName });
|
|
94
|
-
registerEditTool(pi, { wasReadInSession: hasFreshAnchors, onFileMutated: forgetAnchors, name: editName, toolAliases });
|
|
95
|
-
registerGrepTool(pi, { onFileAnchored: markAnchored, name: grepName });
|
|
96
|
-
registerSearchTool(pi, { onFileAnchored: markAnchored });
|
|
97
|
-
registerRefsTool(pi, { onFileAnchored: markAnchored });
|
|
98
|
-
registerRenameTool(pi, { onFileMutated: forgetAnchors });
|
|
99
|
-
registerHoverTool(pi);
|
|
100
|
-
registerDefTool(pi, { onFileAnchored: markAnchored });
|
|
101
|
-
registerCheckTool(pi);
|
|
102
|
-
registerViewTool(pi);
|
|
103
|
-
registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
|
|
104
|
-
|
|
105
|
-
pi.on("before_agent_start", (event) => {
|
|
106
|
-
if (!binaryAvailable) return;
|
|
107
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${editingPolicy(readName, editName, writeName)}` };
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
pi.on("session_start", (_event, ctx) => {
|
|
111
|
-
sessionAnchors.clear();
|
|
112
|
-
const { warnings } = resolveReadSeekJsonSettings();
|
|
113
|
-
const problems = warnings.map(formatSettingsWarning);
|
|
114
|
-
|
|
115
|
-
const availability = readSeekBinaryAvailability();
|
|
116
|
-
if (!availability.available) {
|
|
117
|
-
problems.push(`readseek tools are inactive: ${availability.reason}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
for (const problem of problems) {
|
|
121
|
-
if (ctx.hasUI) ctx.ui.notify(problem, "warning");
|
|
122
|
-
else console.warn(problem);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!availability.available) return;
|
|
126
|
-
|
|
127
|
-
// readSeek tools were registered at load under names chosen from the
|
|
128
|
-
// load-time replacedTools. Replaced built-ins are registered under the
|
|
129
|
-
// built-in name, so the built-in stays active (now readSeek-backed);
|
|
130
|
-
// their readSeek_* variant was never registered and is excluded.
|
|
131
|
-
const activeReadSeekNames = READSEEK_TOOL_ENTRIES.map((entry) =>
|
|
132
|
-
entry.builtIn && replacedBuiltIns.has(entry.builtIn) ? entry.builtIn : entry.readSeekName,
|
|
133
|
-
);
|
|
134
|
-
const inactiveReadSeekNames = new Set(
|
|
135
|
-
READSEEK_TOOL_ENTRIES.filter(
|
|
136
|
-
(entry): entry is { builtIn: ReplacedBuiltIn; readSeekName: string } =>
|
|
137
|
-
entry.builtIn !== null && replacedBuiltIns.has(entry.builtIn),
|
|
138
|
-
).map((entry) => entry.readSeekName),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const activeTools = [...pi.getActiveTools(), ...activeReadSeekNames].filter(
|
|
142
|
-
(name) => !inactiveReadSeekNames.has(name),
|
|
143
|
-
);
|
|
144
|
-
pi.setActiveTools([...new Set(activeTools)]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
}
|
package/src/binary-detect.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns true if the buffer appears to contain binary (non-text) content.
|
|
3
|
-
*
|
|
4
|
-
* Detection strategy:
|
|
5
|
-
* 1. NUL byte (0x00) — universally indicates binary
|
|
6
|
-
* 2. Invalid UTF-8 sequences — Node.js decodes them as U+FFFD replacement
|
|
7
|
-
* characters. To avoid false-positives on valid UTF-8 text that
|
|
8
|
-
* intentionally contains U+FFFD (encoded as EF BF BD), we do a round-trip
|
|
9
|
-
* check: if re-encoding the decoded string produces a different byte
|
|
10
|
-
* sequence, then U+FFFD was introduced by the decoder — not present in
|
|
11
|
-
* the original bytes as valid UTF-8.
|
|
12
|
-
*/
|
|
13
|
-
export function looksLikeBinary(buf: Buffer): boolean {
|
|
14
|
-
if (buf.length === 0) return false;
|
|
15
|
-
// Fast path: NUL byte is a reliable binary marker
|
|
16
|
-
if (buf.includes(0)) return true;
|
|
17
|
-
const decoded = buf.toString("utf8");
|
|
18
|
-
if (!decoded.includes("\uFFFD")) return false;
|
|
19
|
-
|
|
20
|
-
// If U+FFFD came from invalid byte sequences, re-encoding will differ.
|
|
21
|
-
return !Buffer.from(decoded, "utf8").equals(buf);
|
|
22
|
-
}
|
package/src/check.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import { Type } from "@sinclair/typebox";
|
|
6
|
-
|
|
7
|
-
import { classifyReadSeekFailure, readSeekCheck, type ReadSeekCheckOutput } from "./readseek-client.js";
|
|
8
|
-
import { buildToolErrorResult } from "./readseek-value.js";
|
|
9
|
-
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
10
|
-
import { formatFsError } from "./fs-error.js";
|
|
11
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
12
|
-
import { filePathParam, registerReadSeekTool } from "./register-tool.js";
|
|
13
|
-
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderErrorResult, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
14
|
-
|
|
15
|
-
const CHECK_PROMPT_METADATA = defineToolPromptMetadata({
|
|
16
|
-
promptUrl: new URL("../prompts/check.md", import.meta.url),
|
|
17
|
-
promptSnippet: "Check a source file for parser errors and missing syntax",
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export async function executeCheck(opts: {
|
|
21
|
-
params: unknown;
|
|
22
|
-
signal: AbortSignal | undefined;
|
|
23
|
-
cwd: string;
|
|
24
|
-
}): Promise<any> {
|
|
25
|
-
const { params, signal, cwd } = opts;
|
|
26
|
-
const p = params as { path: string };
|
|
27
|
-
const filePath = resolveToCwd(p.path, cwd);
|
|
28
|
-
|
|
29
|
-
let content: string;
|
|
30
|
-
try {
|
|
31
|
-
content = await readFile(filePath, "utf-8");
|
|
32
|
-
} catch (err: any) {
|
|
33
|
-
const { code, message } = formatFsError(err, "check-error");
|
|
34
|
-
return buildToolErrorResult("check", code, message, { path: p.path });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const output = await readSeekCheck(filePath, content, { signal });
|
|
39
|
-
const lines = output.diagnostics.map((diagnostic) =>
|
|
40
|
-
`${diagnostic.kind}: lines ${diagnostic.start_line}-${diagnostic.end_line}`,
|
|
41
|
-
);
|
|
42
|
-
const summary = output.errorCount === 0 && output.missingCount === 0
|
|
43
|
-
? "No syntax errors found."
|
|
44
|
-
: `${output.errorCount} error(s), ${output.missingCount} missing syntax node(s)`;
|
|
45
|
-
return {
|
|
46
|
-
content: [{ type: "text", text: [summary, ...lines].join("\n") }],
|
|
47
|
-
details: {
|
|
48
|
-
readSeekValue: { tool: "check", ok: true, path: filePath, output },
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
} catch (err: any) {
|
|
52
|
-
const failure = classifyReadSeekFailure(err);
|
|
53
|
-
return buildToolErrorResult("check", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function registerCheckTool(pi: ExtensionAPI) {
|
|
58
|
-
return registerReadSeekTool(pi, {
|
|
59
|
-
name: "readSeek_check",
|
|
60
|
-
label: "Check",
|
|
61
|
-
description: CHECK_PROMPT_METADATA.description,
|
|
62
|
-
promptSnippet: CHECK_PROMPT_METADATA.promptSnippet,
|
|
63
|
-
promptGuidelines: CHECK_PROMPT_METADATA.promptGuidelines,
|
|
64
|
-
parameters: Type.Object({ path: filePathParam() }),
|
|
65
|
-
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
66
|
-
return executeCheck({ params, signal, cwd: ctx.cwd });
|
|
67
|
-
},
|
|
68
|
-
renderCall(args: any, theme: any, ...rest: any[]) {
|
|
69
|
-
const context = rest[0] ?? {};
|
|
70
|
-
const cwd = context.cwd ?? process.cwd();
|
|
71
|
-
const displayPath = typeof args?.path === "string" ? args.path : "?";
|
|
72
|
-
const text = `${theme.fg("toolTitle", theme.bold("check"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
|
|
73
|
-
return new Text(clampLineToWidth(text, context.width), 0, 0);
|
|
74
|
-
},
|
|
75
|
-
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
76
|
-
const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
|
|
77
|
-
if (isPartial) return renderPendingResult("pending check", width, theme);
|
|
78
|
-
const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
|
|
79
|
-
if (isError || result.isError) {
|
|
80
|
-
return renderErrorResult(textContent, { expanded, width, fallback: "check failed", theme });
|
|
81
|
-
}
|
|
82
|
-
const output = (result.details as any)?.readSeekValue?.output as ReadSeekCheckOutput | undefined;
|
|
83
|
-
const issueCount = (output?.errorCount ?? 0) + (output?.missingCount ?? 0);
|
|
84
|
-
let text = summaryLine(issueCount === 0 ? "syntax valid" : `${issueCount} syntax issue(s)`, {
|
|
85
|
-
theme,
|
|
86
|
-
style: issueCount === 0 ? "success" : "warning",
|
|
87
|
-
});
|
|
88
|
-
if (expanded && textContent) text += `\n${textContent}`;
|
|
89
|
-
return new Text(clampLinesToWidth(text.split("\n"), width).join("\n"), 0, 0);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const BASE10_INT_RE = /^-?\d+$/;
|
|
2
|
-
const MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
|
|
3
|
-
const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
|
|
4
|
-
|
|
5
|
-
type CoercedIntResult =
|
|
6
|
-
| { ok: true; value: number | undefined }
|
|
7
|
-
| { ok: false; message: string };
|
|
8
|
-
|
|
9
|
-
function formatReceived(value: unknown): string {
|
|
10
|
-
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
11
|
-
return JSON.stringify(value) ?? String(value);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function invalidInteger(value: unknown, name: string): CoercedIntResult {
|
|
15
|
-
return {
|
|
16
|
-
ok: false,
|
|
17
|
-
message: `Invalid ${name}: expected a safe base-10 integer, received ${formatReceived(value)}.`,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function coerceObviousBase10Int(value: unknown, name: string): CoercedIntResult {
|
|
22
|
-
if (value === undefined) {
|
|
23
|
-
return { ok: true, value: undefined };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (typeof value === "number") {
|
|
27
|
-
if (Number.isSafeInteger(value)) {
|
|
28
|
-
return { ok: true, value };
|
|
29
|
-
}
|
|
30
|
-
return invalidInteger(value, name);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (typeof value === "string" && BASE10_INT_RE.test(value)) {
|
|
34
|
-
const parsed = BigInt(value);
|
|
35
|
-
if (parsed >= MIN_SAFE_INTEGER && parsed <= MAX_SAFE_INTEGER) {
|
|
36
|
-
return { ok: true, value: Number(parsed) };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return invalidInteger(value, name);
|
|
41
|
-
}
|
package/src/def.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
6
|
-
import { buildReadSeekLineWithHash, buildToolErrorResult } from "./readseek-value.js";
|
|
7
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
8
|
-
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
9
|
-
import { classifyReadSeekFailure, readSeekDef } from "./readseek-client.js";
|
|
10
|
-
import { searchPathParam, langParam, readSeekGitSearchParams, validateIgnoredRequiresOthers } from "./readseek-params.js";
|
|
11
|
-
import { registerReadSeekTool } from "./register-tool.js";
|
|
12
|
-
|
|
13
|
-
import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
|
|
14
|
-
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
15
|
-
|
|
16
|
-
const DEF_PROMPT_METADATA = defineToolPromptMetadata({
|
|
17
|
-
promptUrl: new URL("../prompts/def.md", import.meta.url),
|
|
18
|
-
promptSnippet: "Find where a symbol is defined",
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
type DefParams = {
|
|
22
|
-
name?: string;
|
|
23
|
-
path?: string;
|
|
24
|
-
lang?: string;
|
|
25
|
-
cached?: boolean;
|
|
26
|
-
others?: boolean;
|
|
27
|
-
ignored?: boolean;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
interface DefToolOptions {
|
|
31
|
-
onFileAnchored?: FileAnchoredCallback;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ExecuteDefOptions {
|
|
35
|
-
params: unknown;
|
|
36
|
-
signal: AbortSignal | undefined;
|
|
37
|
-
cwd: string;
|
|
38
|
-
onFileAnchored?: FileAnchoredCallback;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function executeDef(opts: ExecuteDefOptions): Promise<any> {
|
|
42
|
-
const { params, signal, cwd, onFileAnchored } = opts;
|
|
43
|
-
const p = params as DefParams;
|
|
44
|
-
const ignoredError = validateIgnoredRequiresOthers("def", p);
|
|
45
|
-
if (ignoredError) return ignoredError;
|
|
46
|
-
|
|
47
|
-
if (!p.name || !p.name.trim()) {
|
|
48
|
-
return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
52
|
-
|
|
53
|
-
const statResult = await statSearchPathOrError("def", p.path, searchPath);
|
|
54
|
-
if (!statResult.ok) return statResult.error;
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const definitions = await readSeekDef(searchPath, {
|
|
58
|
-
name: p.name,
|
|
59
|
-
language: p.lang,
|
|
60
|
-
cached: p.cached,
|
|
61
|
-
others: p.others,
|
|
62
|
-
ignored: p.ignored,
|
|
63
|
-
signal,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (definitions.length === 0) {
|
|
67
|
-
return {
|
|
68
|
-
content: [{ type: "text", text: "no definitions found" }],
|
|
69
|
-
details: {
|
|
70
|
-
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions: [] },
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const files = new Map<string, { displayPath: string; path: string; lines: ReturnType<typeof buildReadSeekLineWithHash>[] }>();
|
|
76
|
-
for (const def of definitions) {
|
|
77
|
-
const abs = path.isAbsolute(def.file) ? def.file : path.resolve(cwd, def.file);
|
|
78
|
-
let file = files.get(abs);
|
|
79
|
-
if (!file) {
|
|
80
|
-
file = { displayPath: path.relative(cwd, abs) || abs, path: abs, lines: [] };
|
|
81
|
-
files.set(abs, file);
|
|
82
|
-
}
|
|
83
|
-
file.lines.push(buildReadSeekLineWithHash(def.line, def.line_hash, def.text));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const fileList = [...files.values()];
|
|
87
|
-
for (const file of fileList) {
|
|
88
|
-
onFileAnchored?.(file.path);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const textParts: string[] = [];
|
|
92
|
-
for (const file of fileList) {
|
|
93
|
-
textParts.push(file.displayPath);
|
|
94
|
-
for (const line of file.lines) {
|
|
95
|
-
textParts.push(` ${line.line}:${line.hash} ${line.display}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
content: [{ type: "text", text: textParts.join("\n") }],
|
|
101
|
-
details: {
|
|
102
|
-
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions },
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
} catch (err: any) {
|
|
106
|
-
const failure = classifyReadSeekFailure(err);
|
|
107
|
-
return buildToolErrorResult("def", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function registerDefTool(pi: ExtensionAPI, options: DefToolOptions = {}) {
|
|
112
|
-
registerReadSeekTool(pi, {
|
|
113
|
-
name: "readSeek_def",
|
|
114
|
-
label: "Definition",
|
|
115
|
-
description: DEF_PROMPT_METADATA.description,
|
|
116
|
-
promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
|
|
117
|
-
promptGuidelines: DEF_PROMPT_METADATA.promptGuidelines,
|
|
118
|
-
parameters: Type.Object({
|
|
119
|
-
name: Type.String({ description: "Qualified or unqualified symbol name" }),
|
|
120
|
-
path: searchPathParam(),
|
|
121
|
-
lang: langParam(),
|
|
122
|
-
...readSeekGitSearchParams(),
|
|
123
|
-
}),
|
|
124
|
-
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
125
|
-
return executeDef({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
|
|
126
|
-
},
|
|
127
|
-
renderCall(args: any, theme: any, ...rest: any[]) {
|
|
128
|
-
return renderReadSeekSearchCall(args, theme, rest, {
|
|
129
|
-
label: "def",
|
|
130
|
-
accent: args.name,
|
|
131
|
-
flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"],
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
135
|
-
return renderAnchoredFilesResult(result, options, theme, rest, {
|
|
136
|
-
pendingLabel: "pending def",
|
|
137
|
-
emptyLabel: "no definitions",
|
|
138
|
-
unitSingular: "definition",
|
|
139
|
-
unitPlural: "definitions",
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
}
|