pi-readseek 0.4.29 → 0.5.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/README.md +38 -44
- package/index.ts +45 -7
- package/package.json +2 -2
- package/prompts/def.md +2 -9
- package/prompts/edit.md +13 -13
- package/prompts/grep.md +3 -3
- package/prompts/hover.md +2 -6
- package/prompts/read.md +3 -3
- package/prompts/refs.md +1 -1
- package/prompts/rename.md +3 -7
- package/prompts/sg.md +1 -1
- package/prompts/write.md +5 -5
- package/src/def.ts +12 -19
- package/src/edit-classify.ts +0 -127
- package/src/edit-output.ts +5 -11
- package/src/edit-syntax-validate.ts +4 -3
- package/src/edit.ts +21 -42
- package/src/file-map.ts +2 -2
- package/src/grep-budget.ts +7 -51
- package/src/grep-output.ts +4 -4
- package/src/grep.ts +19 -26
- package/src/hover.ts +4 -8
- package/src/read-output.ts +4 -4
- package/src/read.ts +28 -37
- package/src/readseek-client.ts +171 -145
- package/src/readseek-params.ts +1 -1
- package/src/readseek-settings.ts +138 -55
- package/src/readseek-value.ts +4 -6
- package/src/refs-output.ts +3 -3
- package/src/refs.ts +6 -10
- package/src/register-tool.ts +4 -42
- package/src/rename.ts +6 -10
- package/src/replace-symbol.ts +2 -2
- package/src/sg-output.ts +3 -3
- package/src/sg.ts +21 -25
- package/src/syntax-validate-mode.ts +3 -13
- package/src/tool-prompt-metadata.ts +12 -12
- package/src/tui-render-utils.ts +2 -2
- package/src/write.ts +50 -80
package/src/edit-output.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface BuildEditOutputInput {
|
|
|
17
17
|
export interface EditOutputResult {
|
|
18
18
|
text: string;
|
|
19
19
|
patch: string;
|
|
20
|
-
|
|
20
|
+
readSeekValue: ReturnType<typeof buildReadSeekEditResult>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const EDIT_OPERATION_NAMES = ["set_line", "replace_lines", "insert_after", "replace"] as const;
|
|
@@ -76,21 +76,15 @@ function formatWhitespaceOnlyWarning(semanticSummary: SemanticSummary | undefine
|
|
|
76
76
|
if (!extractNewTextValues(edits).some((text) => /\S/.test(text))) return undefined;
|
|
77
77
|
return "⚠ Edit classified as whitespace-only — if you intended a behavior change, re-read to verify.";
|
|
78
78
|
}
|
|
79
|
-
function formatSemanticSuffix(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const movedBlocks = semanticSummary.movedBlocks ?? 0;
|
|
83
|
-
if (movedBlocks <= 0) return "";
|
|
84
|
-
|
|
85
|
-
const blockWord = movedBlocks === 1 ? "block" : "blocks";
|
|
86
|
-
return ` [semantic: ${semanticSummary.classification}, ${movedBlocks} ${blockWord} moved]`;
|
|
79
|
+
function formatSemanticSuffix(_semanticSummary: SemanticSummary | undefined): string {
|
|
80
|
+
return "";
|
|
87
81
|
}
|
|
88
82
|
function formatReplaceHint(edits: unknown[] | undefined, noopEdits: unknown[]): string | undefined {
|
|
89
83
|
if ((noopEdits ?? []).length > 0) return undefined;
|
|
90
84
|
const counts = countEditTypes(edits);
|
|
91
85
|
if (counts.replace === 0) return undefined;
|
|
92
86
|
if (counts.replace !== counts.total) return undefined;
|
|
93
|
-
return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from
|
|
87
|
+
return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_read/readSeek_grep/readSeek_search.]";
|
|
94
88
|
}
|
|
95
89
|
export function buildEditOutput(input: BuildEditOutputInput): EditOutputResult {
|
|
96
90
|
const summary = `Updated ${input.displayPath}`;
|
|
@@ -105,7 +99,7 @@ export function buildEditOutput(input: BuildEditOutputInput): EditOutputResult {
|
|
|
105
99
|
return {
|
|
106
100
|
text,
|
|
107
101
|
patch: input.patch ?? "",
|
|
108
|
-
|
|
102
|
+
readSeekValue: buildReadSeekEditResult({
|
|
109
103
|
path: input.path,
|
|
110
104
|
summary,
|
|
111
105
|
diff: input.diff,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readSeekCheck, type ReadSeekCheckOutput, type ReadSeekDiagnostic } from "./readseek-client.js";
|
|
2
2
|
|
|
3
3
|
export interface ValidateInput {
|
|
4
4
|
filePath: string;
|
|
@@ -41,12 +41,13 @@ const EMPTY: ReadSeekCheckOutput = { errorCount: 0, missingCount: 0, diagnostics
|
|
|
41
41
|
*/
|
|
42
42
|
export async function validateSyntaxRegression(
|
|
43
43
|
input: ValidateInput,
|
|
44
|
+
options: { signal?: AbortSignal } = {},
|
|
44
45
|
): Promise<ValidateResult | null> {
|
|
45
46
|
let before: ReadSeekCheckOutput;
|
|
46
47
|
let after: ReadSeekCheckOutput;
|
|
47
48
|
try {
|
|
48
|
-
before = input.before === undefined ? EMPTY : await
|
|
49
|
-
after = await
|
|
49
|
+
before = input.before === undefined ? EMPTY : await readSeekCheck(input.filePath, input.before, { signal: options.signal });
|
|
50
|
+
after = await readSeekCheck(input.filePath, input.after, { signal: options.signal });
|
|
50
51
|
} catch {
|
|
51
52
|
return null;
|
|
52
53
|
}
|
package/src/edit.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { looksLikeBinary } from "./binary-detect.js";
|
|
|
14
14
|
import { throwIfAborted } from "./runtime.js";
|
|
15
15
|
import { formatFsError } from "./fs-error.js";
|
|
16
16
|
import { buildEditOutput } from "./edit-output.js";
|
|
17
|
-
import { classifyEdit
|
|
17
|
+
import { classifyEdit } from "./edit-classify.js";
|
|
18
18
|
import type { SemanticSummary } from "./readseek-value.js";
|
|
19
19
|
import { buildReadSeekError } from "./readseek-value.js";
|
|
20
20
|
import { classifyReadSeekFailure } from "./readseek-client.js";
|
|
@@ -29,8 +29,6 @@ import { upsertDiffComponent, upsertTextComponent } from "./tui-diff-component.j
|
|
|
29
29
|
import type { FreshAnchorsPredicate } from "./tool-types.js";
|
|
30
30
|
import { filePathParam, registerReadSeekTool } from "./register-tool.js";
|
|
31
31
|
|
|
32
|
-
import { resolveEditDiffDisplay } from "./readseek-settings.js";
|
|
33
|
-
|
|
34
32
|
const EDIT_PENDING_PREVIEW_STATE_KEY = "hashline-edit-pending-preview";
|
|
35
33
|
|
|
36
34
|
function pendingPreviewLines(summary: string, preview: PendingDiffPreviewResult | undefined, expanded: boolean): { lines: string[]; diffData?: ReturnType<typeof buildDiffData>; headerLabel?: string } {
|
|
@@ -73,7 +71,7 @@ type HashlineParams = Static<typeof hashlineEditSchema>;
|
|
|
73
71
|
|
|
74
72
|
const EDIT_PROMPT_METADATA = defineToolPromptMetadata({
|
|
75
73
|
promptUrl: new URL("../prompts/edit.md", import.meta.url),
|
|
76
|
-
promptSnippet: "Edit files using hash-verified anchors from
|
|
74
|
+
promptSnippet: "Edit files using hash-verified anchors from readSeek_read/readSeek_grep/readSeek_search/readSeek_write",
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
function buildEditError(
|
|
@@ -85,7 +83,7 @@ function buildEditError(
|
|
|
85
83
|
): {
|
|
86
84
|
content: [{ type: "text"; text: string }];
|
|
87
85
|
isError: true;
|
|
88
|
-
details: EditToolDetails & {
|
|
86
|
+
details: EditToolDetails & { readSeekValue: any };
|
|
89
87
|
} {
|
|
90
88
|
return {
|
|
91
89
|
content: [{ type: "text", text: message }],
|
|
@@ -94,13 +92,13 @@ function buildEditError(
|
|
|
94
92
|
diff: "",
|
|
95
93
|
patch: "",
|
|
96
94
|
firstChangedLine: undefined,
|
|
97
|
-
|
|
95
|
+
readSeekValue: {
|
|
98
96
|
tool: "edit",
|
|
99
97
|
ok: false,
|
|
100
98
|
path,
|
|
101
99
|
error: buildReadSeekError(code, message, hint, errorDetails),
|
|
102
100
|
},
|
|
103
|
-
} as EditToolDetails & {
|
|
101
|
+
} as EditToolDetails & { readSeekValue: any },
|
|
104
102
|
};
|
|
105
103
|
}
|
|
106
104
|
|
|
@@ -137,14 +135,14 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
137
135
|
if (wasReadInSession && !wasReadInSession(absolutePath)) {
|
|
138
136
|
const message = [
|
|
139
137
|
`You must get fresh anchors for ${absolutePath} before editing it.`,
|
|
140
|
-
`Call
|
|
141
|
-
"
|
|
138
|
+
`Call readSeek_read(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`,
|
|
139
|
+
"readSeek_edit requires fresh LINE:HASH anchors from readSeek_read, readSeek_grep, readSeek_search, or readSeek_write so the hashes match the current file contents.",
|
|
142
140
|
].join(" ");
|
|
143
141
|
return buildEditError(
|
|
144
142
|
absolutePath,
|
|
145
143
|
"file-not-read",
|
|
146
144
|
message,
|
|
147
|
-
`Call
|
|
145
|
+
`Call readSeek_read(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`,
|
|
148
146
|
);
|
|
149
147
|
}
|
|
150
148
|
const hasTopLevelReplaceInput =
|
|
@@ -253,7 +251,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
253
251
|
// Error-precedence order: replace_symbol resolution > anchor-overlap > anchored-edit.
|
|
254
252
|
//
|
|
255
253
|
// store successful probe results and reuse them in the apply loop so
|
|
256
|
-
//
|
|
254
|
+
// readSeekMapContent is invoked at most once per replace_symbol edit.
|
|
257
255
|
const replaceSymbolRanges: { start: number; end: number }[] = [];
|
|
258
256
|
const rsProbeResults: { type: "ok"; content: string; replacement: string; warnings: string[]; range: { start: number; end: number } }[] = [];
|
|
259
257
|
try {
|
|
@@ -365,7 +363,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
365
363
|
// Apply pass: reuse all probe results. The probe pass resolved every
|
|
366
364
|
// replace_symbol against originalNormalized; apply those replacements in
|
|
367
365
|
// reverse source order so original line ranges stay valid and no second
|
|
368
|
-
// replaceSymbol/
|
|
366
|
+
// replaceSymbol/readSeekMapContent call is needed.
|
|
369
367
|
const replaceSymbolWarnings: string[] = [];
|
|
370
368
|
if (rsProbeResults.length > 0) {
|
|
371
369
|
const lines = originalNormalized.split("\n");
|
|
@@ -454,7 +452,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
454
452
|
filePath: absolutePath,
|
|
455
453
|
before: originalNormalized,
|
|
456
454
|
after: result,
|
|
457
|
-
});
|
|
455
|
+
}, { signal });
|
|
458
456
|
if (regression) {
|
|
459
457
|
const lines = regression.errorLines.join(", ");
|
|
460
458
|
const message = `syntax-regression: lines ${lines}`;
|
|
@@ -516,23 +514,9 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
516
514
|
if (syntaxWarning) warnings.push(syntaxWarning);
|
|
517
515
|
// Semantic classification
|
|
518
516
|
const internalClassification = classifyEdit(originalNormalized, result);
|
|
519
|
-
const
|
|
520
|
-
let semanticSummary: SemanticSummary = {
|
|
517
|
+
const semanticSummary: SemanticSummary = {
|
|
521
518
|
classification: internalClassification.classification,
|
|
522
|
-
difftasticAvailable: difftAvailable,
|
|
523
519
|
};
|
|
524
|
-
|
|
525
|
-
if (difftAvailable) {
|
|
526
|
-
const ext = path.split(".").pop() ?? "txt";
|
|
527
|
-
const difftResult = await runDifftastic(originalNormalized, result, ext);
|
|
528
|
-
if (difftResult) {
|
|
529
|
-
semanticSummary = {
|
|
530
|
-
classification: difftResult.classification,
|
|
531
|
-
difftasticAvailable: true,
|
|
532
|
-
...(difftResult.movedBlocks > 0 ? { movedBlocks: difftResult.movedBlocks } : {}),
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
520
|
const builtOutput = buildEditOutput({
|
|
537
521
|
path: absolutePath,
|
|
538
522
|
displayPath: path,
|
|
@@ -553,10 +537,10 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
553
537
|
patch: builtOutput.patch,
|
|
554
538
|
diffData,
|
|
555
539
|
firstChangedLine: anchorResult.firstChangedLine ?? diffResult.firstChangedLine,
|
|
556
|
-
|
|
540
|
+
readSeekValue: builtOutput.readSeekValue,
|
|
557
541
|
} as EditToolDetails & {
|
|
558
542
|
diffData: typeof diffData;
|
|
559
|
-
|
|
543
|
+
readSeekValue: {
|
|
560
544
|
tool: string;
|
|
561
545
|
ok: boolean;
|
|
562
546
|
path: string;
|
|
@@ -581,11 +565,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
581
565
|
|
|
582
566
|
export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}) {
|
|
583
567
|
const tool = registerReadSeekTool(pi, {
|
|
584
|
-
|
|
585
|
-
pythonName: "edit",
|
|
586
|
-
defaultExposure: "not-safe-by-default",
|
|
587
|
-
}, {
|
|
588
|
-
name: "edit",
|
|
568
|
+
name: "readSeek_edit",
|
|
589
569
|
label: "Edit",
|
|
590
570
|
description: EDIT_PROMPT_METADATA.description,
|
|
591
571
|
promptSnippet: EDIT_PROMPT_METADATA.promptSnippet,
|
|
@@ -631,7 +611,7 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
631
611
|
previewKey,
|
|
632
612
|
() => buildPendingEditPreviewData(args ?? {}, context.cwd ?? process.cwd()),
|
|
633
613
|
);
|
|
634
|
-
const expanded = !!context.expanded
|
|
614
|
+
const expanded = !!context.expanded;
|
|
635
615
|
const preview2 = pendingPreviewLines(text, preview, expanded);
|
|
636
616
|
if (preview2.diffData) {
|
|
637
617
|
return upsertDiffComponent(context.lastComponent, { prefixLines: preview2.lines, diffData: preview2.diffData, theme, expanded: true });
|
|
@@ -639,7 +619,7 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
639
619
|
return upsertTextComponent(context.lastComponent, clampLinesToWidth(preview2.lines, context.width).join("\n"));
|
|
640
620
|
},
|
|
641
621
|
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
642
|
-
const { isPartial, isError, expanded
|
|
622
|
+
const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options, rest);
|
|
643
623
|
|
|
644
624
|
if (isPartial) {
|
|
645
625
|
return renderPendingResult("pending edit", width);
|
|
@@ -652,13 +632,13 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
652
632
|
.join("\n") ?? "";
|
|
653
633
|
const details = result.details ?? {};
|
|
654
634
|
const diff: string = details.diff ?? "";
|
|
655
|
-
const
|
|
635
|
+
const readSeekValue = details.readSeekValue as {
|
|
656
636
|
warnings?: string[];
|
|
657
637
|
noopEdits?: unknown[];
|
|
658
638
|
} | undefined;
|
|
659
|
-
const warnings =
|
|
660
|
-
const noopEdits =
|
|
661
|
-
const semanticClassification = (
|
|
639
|
+
const warnings = readSeekValue?.warnings ?? [];
|
|
640
|
+
const noopEdits = readSeekValue?.noopEdits ?? [];
|
|
641
|
+
const semanticClassification = (readSeekValue as any)?.semanticSummary?.classification as string | undefined;
|
|
662
642
|
|
|
663
643
|
const info = formatEditResultText({
|
|
664
644
|
isError: isError || !!result.isError,
|
|
@@ -669,7 +649,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
669
649
|
semanticClassification: semanticClassification as any,
|
|
670
650
|
});
|
|
671
651
|
|
|
672
|
-
const expanded = baseExpanded || resolveEditDiffDisplay() === "expanded";
|
|
673
652
|
const diffData = (details as any).diffData;
|
|
674
653
|
const stats = diffData?.stats ?? { added: 0, removed: 0 };
|
|
675
654
|
let text = "";
|
package/src/file-map.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import type { FileMap } from "./readseek/types.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readSeekMap } from "./readseek-client.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Fetch a structural file map from readseek, which maintains its own on-disk
|
|
@@ -10,7 +10,7 @@ import { readseekMap } from "./readseek-client.js";
|
|
|
10
10
|
export async function getOrGenerateMap(absPath: string): Promise<FileMap | null> {
|
|
11
11
|
try {
|
|
12
12
|
const { size } = await stat(absPath);
|
|
13
|
-
return await
|
|
13
|
+
return await readSeekMap(absPath, size);
|
|
14
14
|
} catch {
|
|
15
15
|
return null;
|
|
16
16
|
}
|
package/src/grep-budget.ts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolveReadSeekJsonSettings } from "./readseek-settings.js";
|
|
3
3
|
|
|
4
|
-
const POSITIVE_BASE10_INT = /^[1-9][0-9]*$/;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Strict positive base-10 integer parser used by readseek env knobs.
|
|
8
|
-
*
|
|
9
|
-
* Accepts: trimmed strings matching /^[1-9][0-9]*$/ that parse to a finite
|
|
10
|
-
* positive integer.
|
|
11
|
-
*
|
|
12
|
-
* Rejects: undefined, empty, whitespace-only, "0", negative, signed, hex
|
|
13
|
-
* ("0x10"), exponent ("1e3"), decimal ("3.14"), separators ("1,000" /
|
|
14
|
-
* "1_000"), embedded whitespace ("5 5").
|
|
15
|
-
*
|
|
16
|
-
* Returns `undefined` on rejection; never throws.
|
|
17
|
-
*/
|
|
18
|
-
function parsePositiveBase10Int(raw: string | undefined | null): number | undefined {
|
|
19
|
-
if (raw === undefined || raw === null) return undefined;
|
|
20
|
-
const trimmed = String(raw).trim();
|
|
21
|
-
if (!POSITIVE_BASE10_INT.test(trimmed)) return undefined;
|
|
22
|
-
const parsed = Number.parseInt(trimmed, 10);
|
|
23
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
24
|
-
return parsed;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
4
|
export interface GrepOutputBudget {
|
|
28
5
|
maxLines: number;
|
|
29
6
|
maxBytes: number;
|
|
@@ -31,7 +8,7 @@ export interface GrepOutputBudget {
|
|
|
31
8
|
|
|
32
9
|
/**
|
|
33
10
|
* Effective grep-output ceilings used as clamp upper bounds and as the
|
|
34
|
-
* fallback defaults when
|
|
11
|
+
* fallback defaults when the settings are unset.
|
|
35
12
|
*
|
|
36
13
|
* The bytes ceiling is the already-tightened 50 KiB used by `buildGrepOutput`
|
|
37
14
|
* today, NOT the unclamped `DEFAULT_MAX_BYTES`.
|
|
@@ -39,41 +16,20 @@ export interface GrepOutputBudget {
|
|
|
39
16
|
export const GREP_OUTPUT_DEFAULT_MAX_LINES = DEFAULT_MAX_LINES;
|
|
40
17
|
export const GREP_OUTPUT_DEFAULT_MAX_BYTES = Math.min(DEFAULT_MAX_BYTES, 50 * 1024);
|
|
41
18
|
|
|
42
|
-
function
|
|
43
|
-
const parsed = parsePositiveBase10Int(rawEnvValue);
|
|
44
|
-
return parsed === undefined ? undefined : Math.min(parsed, ceiling);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function resolveDimension(rawEnvValue: string | undefined, jsonValue: number | undefined, ceiling: number): number {
|
|
48
|
-
if (rawEnvValue !== undefined) {
|
|
49
|
-
const envValue = resolveEnvDimension(rawEnvValue, ceiling);
|
|
50
|
-
if (envValue !== undefined) return envValue;
|
|
51
|
-
}
|
|
19
|
+
function resolveDimension(jsonValue: number | undefined, ceiling: number): number {
|
|
52
20
|
if (jsonValue !== undefined) return Math.min(jsonValue, ceiling);
|
|
53
21
|
return ceiling;
|
|
54
22
|
}
|
|
55
23
|
|
|
56
24
|
/**
|
|
57
|
-
* Resolve the effective grep visible-output budget
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* Invalid / zero / negative env values fall back to the current defaults.
|
|
62
|
-
* Above-default values clamp to the current defaults. Below-default values
|
|
63
|
-
* are used as-is.
|
|
25
|
+
* Resolve the effective grep visible-output budget from the readseek
|
|
26
|
+
* settings. Below-default values are used as-is; above-default values clamp
|
|
27
|
+
* to the current defaults.
|
|
64
28
|
*/
|
|
65
29
|
export function resolveGrepOutputBudget(): GrepOutputBudget {
|
|
66
30
|
const settings = resolveReadSeekJsonSettings().settings.grep;
|
|
67
31
|
return {
|
|
68
|
-
maxLines: resolveDimension(
|
|
69
|
-
|
|
70
|
-
settings?.maxLines,
|
|
71
|
-
GREP_OUTPUT_DEFAULT_MAX_LINES,
|
|
72
|
-
),
|
|
73
|
-
maxBytes: resolveDimension(
|
|
74
|
-
process.env.READSEEK_GREP_MAX_BYTES,
|
|
75
|
-
settings?.maxBytes,
|
|
76
|
-
GREP_OUTPUT_DEFAULT_MAX_BYTES,
|
|
77
|
-
),
|
|
32
|
+
maxLines: resolveDimension(settings?.maxLines, GREP_OUTPUT_DEFAULT_MAX_LINES),
|
|
33
|
+
maxBytes: resolveDimension(settings?.maxBytes, GREP_OUTPUT_DEFAULT_MAX_BYTES),
|
|
78
34
|
};
|
|
79
35
|
}
|
package/src/grep-output.ts
CHANGED
|
@@ -68,7 +68,7 @@ interface BuildGrepOutputInput {
|
|
|
68
68
|
|
|
69
69
|
interface GrepOutputResult {
|
|
70
70
|
text: string;
|
|
71
|
-
|
|
71
|
+
readSeekValue: {
|
|
72
72
|
tool: "grep";
|
|
73
73
|
summary: boolean;
|
|
74
74
|
totalMatches: number;
|
|
@@ -159,7 +159,7 @@ export function buildGrepOutput(input: BuildGrepOutputInput): GrepOutputResult {
|
|
|
159
159
|
if (truncated.truncated) {
|
|
160
160
|
text = `${truncated.content}\n\n[Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Refine pattern or increase limit.]`;
|
|
161
161
|
}
|
|
162
|
-
const
|
|
162
|
+
const readSeekValue: GrepOutputResult["readSeekValue"] = {
|
|
163
163
|
tool: "grep",
|
|
164
164
|
summary: input.summary,
|
|
165
165
|
totalMatches: input.totalMatches,
|
|
@@ -171,11 +171,11 @@ export function buildGrepOutput(input: BuildGrepOutputInput): GrepOutputResult {
|
|
|
171
171
|
})),
|
|
172
172
|
};
|
|
173
173
|
if (!input.summary && input.scopeMode === "symbol") {
|
|
174
|
-
|
|
174
|
+
readSeekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
return {
|
|
178
178
|
text,
|
|
179
|
-
|
|
179
|
+
readSeekValue,
|
|
180
180
|
};
|
|
181
181
|
}
|
package/src/grep.ts
CHANGED
|
@@ -168,7 +168,6 @@ function escapeForRegex(s: string): string {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
interface GrepToolOptions {
|
|
171
|
-
searchGuideline?: string;
|
|
172
171
|
onFileAnchored?: FileAnchoredCallback;
|
|
173
172
|
}
|
|
174
173
|
|
|
@@ -253,7 +252,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
253
252
|
),
|
|
254
253
|
details: {
|
|
255
254
|
...(typeof result.details === "object" && result.details !== null ? result.details : {}),
|
|
256
|
-
|
|
255
|
+
readSeekValue: {
|
|
257
256
|
tool: "grep",
|
|
258
257
|
summary: !!p.summary,
|
|
259
258
|
totalMatches: 0,
|
|
@@ -410,7 +409,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
410
409
|
...result,
|
|
411
410
|
details: {
|
|
412
411
|
...passthroughDetails,
|
|
413
|
-
|
|
412
|
+
readSeekValue: {
|
|
414
413
|
tool: "grep",
|
|
415
414
|
summary: true,
|
|
416
415
|
totalMatches: 0,
|
|
@@ -436,7 +435,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
436
435
|
...passthroughDetails,
|
|
437
436
|
hashlinePassthrough: true,
|
|
438
437
|
hashlineWarning: warning,
|
|
439
|
-
|
|
438
|
+
readSeekValue: {
|
|
440
439
|
tool: "grep",
|
|
441
440
|
summary: !!p.summary,
|
|
442
441
|
totalMatches: 0,
|
|
@@ -477,20 +476,20 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
477
476
|
renderedGroups = scoped.groups;
|
|
478
477
|
scopeWarnings = scoped.warnings;
|
|
479
478
|
}
|
|
480
|
-
const
|
|
479
|
+
const readSeekRecords = recordsFromGroups(renderedGroups);
|
|
481
480
|
const builtOutput = buildGrepOutput({
|
|
482
481
|
summary: !!summary,
|
|
483
482
|
totalMatches,
|
|
484
483
|
groups: renderedGroups,
|
|
485
484
|
limit: effectiveLimit,
|
|
486
|
-
records:
|
|
485
|
+
records: readSeekRecords,
|
|
487
486
|
scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
|
|
488
487
|
scopeWarnings,
|
|
489
488
|
passthroughLines,
|
|
490
489
|
});
|
|
491
490
|
|
|
492
|
-
if (!summary &&
|
|
493
|
-
const anchoredPaths = new Set(
|
|
491
|
+
if (!summary && readSeekRecords.length > 0) {
|
|
492
|
+
const anchoredPaths = new Set(readSeekRecords.map((record) => record.path));
|
|
494
493
|
for (const absolutePath of anchoredPaths) {
|
|
495
494
|
onFileAnchored?.(absolutePath);
|
|
496
495
|
}
|
|
@@ -508,25 +507,19 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
508
507
|
),
|
|
509
508
|
details: {
|
|
510
509
|
...compactDetails,
|
|
511
|
-
|
|
510
|
+
readSeekValue: builtOutput.readSeekValue,
|
|
512
511
|
},
|
|
513
512
|
};
|
|
514
513
|
}
|
|
515
514
|
|
|
516
515
|
export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}) {
|
|
517
516
|
const tool = registerReadSeekTool(pi, {
|
|
518
|
-
|
|
519
|
-
pythonName: "grep",
|
|
520
|
-
defaultExposure: "safe-by-default",
|
|
521
|
-
}, {
|
|
522
|
-
name: "grep",
|
|
517
|
+
name: "readSeek_grep",
|
|
523
518
|
label: "grep",
|
|
524
519
|
description: GREP_PROMPT_METADATA.description,
|
|
525
520
|
parameters: grepSchema,
|
|
526
521
|
promptSnippet: GREP_PROMPT_METADATA.promptSnippet,
|
|
527
|
-
promptGuidelines:
|
|
528
|
-
? [GREP_PROMPT_METADATA.promptGuidelines[0], options.searchGuideline]
|
|
529
|
-
: GREP_PROMPT_METADATA.promptGuidelines,
|
|
522
|
+
promptGuidelines: GREP_PROMPT_METADATA.promptGuidelines,
|
|
530
523
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
531
524
|
return executeGrep({
|
|
532
525
|
toolCallId,
|
|
@@ -558,14 +551,14 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
|
|
|
558
551
|
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
559
552
|
const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
|
|
560
553
|
|
|
561
|
-
if (isPartial) return renderPendingResult("pending
|
|
554
|
+
if (isPartial) return renderPendingResult("pending grep", width);
|
|
562
555
|
|
|
563
556
|
const content = result.content?.[0];
|
|
564
557
|
const textContent = content?.type === "text" ? content.text : "";
|
|
565
558
|
|
|
566
559
|
if (isError || result.isError) return renderErrorResult(textContent, { expanded, width });
|
|
567
560
|
|
|
568
|
-
const
|
|
561
|
+
const readSeekValue = (result.details as any)?.readSeekValue as {
|
|
569
562
|
tool: "grep";
|
|
570
563
|
summary: boolean;
|
|
571
564
|
totalMatches: number;
|
|
@@ -575,26 +568,26 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
|
|
|
575
568
|
const hasBinaryWarning = textContent.includes("appears to be a binary file");
|
|
576
569
|
|
|
577
570
|
const fileSet = new Set<string>();
|
|
578
|
-
for (const r of
|
|
571
|
+
for (const r of readSeekValue?.records ?? []) {
|
|
579
572
|
if (r.path) fileSet.add(r.path);
|
|
580
573
|
}
|
|
581
574
|
|
|
582
575
|
const info = formatGrepResultText({
|
|
583
|
-
totalMatches:
|
|
584
|
-
summary:
|
|
585
|
-
records:
|
|
576
|
+
totalMatches: readSeekValue?.totalMatches ?? 0,
|
|
577
|
+
summary: readSeekValue?.summary ?? false,
|
|
578
|
+
records: readSeekValue?.records ?? [],
|
|
586
579
|
fileCount: fileSet.size,
|
|
587
580
|
hasBinaryWarning,
|
|
588
581
|
});
|
|
589
582
|
|
|
590
583
|
if (info.noMatches && !hasBinaryWarning) return new Text(summaryLine("no matches"), 0, 0);
|
|
591
|
-
const matchCount =
|
|
584
|
+
const matchCount = readSeekValue?.totalMatches ?? 0;
|
|
592
585
|
const matchWord = matchCount === 1 ? "match" : "matches";
|
|
593
586
|
let text = summaryLine(`${matchCount} ${matchWord} returned`, { hidden: !!textContent && !expanded });
|
|
594
587
|
for (const badge of info.badges) text += theme.fg(badge.startsWith("⚠") ? "warning" : "dim", ` ${badge}`);
|
|
595
|
-
if (expanded &&
|
|
588
|
+
if (expanded && readSeekValue?.records) {
|
|
596
589
|
const fileCounts = new Map<string, number>();
|
|
597
|
-
for (const r of
|
|
590
|
+
for (const r of readSeekValue.records) if (r.path && r.kind === "match") fileCounts.set(r.path, (fileCounts.get(r.path) ?? 0) + 1);
|
|
598
591
|
for (const [filePath, count] of [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20)) {
|
|
599
592
|
const display = path.relative(cwd, filePath) || filePath;
|
|
600
593
|
text += "\n" + theme.fg("dim", ` ${display} (${count})`);
|
package/src/hover.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
|
8
8
|
import { buildToolErrorResult } from "./readseek-value.js";
|
|
9
9
|
import { resolveToCwd } from "./path-utils.js";
|
|
10
10
|
import { formatFsError } from "./fs-error.js";
|
|
11
|
-
import { classifyReadSeekFailure,
|
|
11
|
+
import { classifyReadSeekFailure, readSeekIdentify } from "./readseek-client.js";
|
|
12
12
|
import { filePathParam, registerReadSeekTool } from "./register-tool.js";
|
|
13
13
|
|
|
14
14
|
import { clampLinesToWidth, linkToolPath, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
@@ -55,7 +55,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
|
-
const output = await
|
|
58
|
+
const output = await readSeekIdentify(filePath, content, {
|
|
59
59
|
line: p.line,
|
|
60
60
|
column: p.column,
|
|
61
61
|
signal,
|
|
@@ -77,7 +77,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
|
|
|
77
77
|
return {
|
|
78
78
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
79
79
|
details: {
|
|
80
|
-
|
|
80
|
+
readSeekValue: {
|
|
81
81
|
tool: "hover",
|
|
82
82
|
ok: true,
|
|
83
83
|
path: filePath,
|
|
@@ -93,11 +93,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
|
|
|
93
93
|
|
|
94
94
|
export function registerHoverTool(pi: ExtensionAPI) {
|
|
95
95
|
registerReadSeekTool(pi, {
|
|
96
|
-
|
|
97
|
-
pythonName: "hover",
|
|
98
|
-
defaultExposure: "opt-in",
|
|
99
|
-
}, {
|
|
100
|
-
name: "hover",
|
|
96
|
+
name: "readSeek_hover",
|
|
101
97
|
label: "Hover",
|
|
102
98
|
description: HOVER_PROMPT_METADATA.description,
|
|
103
99
|
promptSnippet: HOVER_PROMPT_METADATA.promptSnippet,
|
package/src/read-output.ts
CHANGED
|
@@ -63,7 +63,7 @@ interface ReadOutputInput {
|
|
|
63
63
|
interface ReadOutputResult {
|
|
64
64
|
text: string;
|
|
65
65
|
lines: ReadSeekLine[];
|
|
66
|
-
|
|
66
|
+
readSeekValue: {
|
|
67
67
|
tool: "read";
|
|
68
68
|
path: string;
|
|
69
69
|
range: {
|
|
@@ -141,7 +141,7 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
|
|
|
141
141
|
text = `${warnings.map((warning) => warning.message).join("\n\n")}\n\n${text}`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
const
|
|
144
|
+
const readSeekValue: ReadOutputResult["readSeekValue"] = {
|
|
145
145
|
tool: "read",
|
|
146
146
|
path: input.path,
|
|
147
147
|
range: {
|
|
@@ -160,7 +160,7 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
|
|
|
160
160
|
};
|
|
161
161
|
|
|
162
162
|
if (input.bundle) {
|
|
163
|
-
|
|
163
|
+
readSeekValue.bundle = {
|
|
164
164
|
mode: input.bundle.mode,
|
|
165
165
|
applied: input.bundle.applied,
|
|
166
166
|
localSupport: input.bundle.localSupport.map((item) => {
|
|
@@ -181,6 +181,6 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
|
|
|
181
181
|
return {
|
|
182
182
|
text,
|
|
183
183
|
lines,
|
|
184
|
-
|
|
184
|
+
readSeekValue,
|
|
185
185
|
};
|
|
186
186
|
}
|