pi-readseek 0.5.6 → 0.5.8

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/index.ts CHANGED
@@ -12,16 +12,31 @@ import { SessionAnchors } from "./src/session-anchors.js";
12
12
  import { readSeekBinaryAvailability } from "./src/readseek-client.js";
13
13
  import { resolveReadSeekJsonSettings, type ReadSeekSettingsWarning } from "./src/readseek-settings.js";
14
14
 
15
- const READSEEK_TOOL_NAMES = [
16
- "readSeek_read",
17
- "readSeek_edit",
18
- "readSeek_grep",
19
- "readSeek_search",
20
- "readSeek_refs",
21
- "readSeek_rename",
22
- "readSeek_hover",
23
- "readSeek_write",
24
- "readSeek_def",
15
+ /** Built-in tool names that pi-readseek can replace with a readSeek-backed implementation. */
16
+ const REPLACEABLE_TOOLS = {
17
+ read: "readSeek_read",
18
+ edit: "readSeek_edit",
19
+ write: "readSeek_write",
20
+ grep: "readSeek_grep",
21
+ } as const;
22
+
23
+ type ReplacedBuiltIn = keyof typeof REPLACEABLE_TOOLS;
24
+
25
+ /**
26
+ * Canonical list of readSeek tools in registration/activation order. Each
27
+ * replaceable entry carries the built-in name it swaps in when replaced; null
28
+ * entries are readSeek-only and never replaced.
29
+ */
30
+ const READSEEK_TOOL_ENTRIES: ReadonlyArray<{ builtIn: ReplacedBuiltIn | null; readSeekName: string }> = [
31
+ { builtIn: "read", readSeekName: "readSeek_read" },
32
+ { builtIn: "edit", readSeekName: "readSeek_edit" },
33
+ { builtIn: "grep", readSeekName: "readSeek_grep" },
34
+ { builtIn: null, readSeekName: "readSeek_search" },
35
+ { builtIn: null, readSeekName: "readSeek_refs" },
36
+ { builtIn: null, readSeekName: "readSeek_rename" },
37
+ { builtIn: null, readSeekName: "readSeek_hover" },
38
+ { builtIn: "write", readSeekName: "readSeek_write" },
39
+ { builtIn: null, readSeekName: "readSeek_def" },
25
40
  ];
26
41
 
27
42
  function formatSettingsWarning(warning: ReadSeekSettingsWarning): string {
@@ -33,19 +48,45 @@ export default function piReadSeekExtension(pi: ExtensionAPI): void {
33
48
  const markAnchored = (absolutePath: string) => sessionAnchors.markAnchored(absolutePath);
34
49
  const hasFreshAnchors = (absolutePath: string) => sessionAnchors.hasFreshAnchors(absolutePath);
35
50
 
36
- registerReadTool(pi, { onSuccessfulRead: markAnchored });
37
- registerEditTool(pi, { wasReadInSession: hasFreshAnchors });
38
- registerGrepTool(pi, { onFileAnchored: markAnchored });
51
+ // Resolve replacedTools at load time so the readSeek implementation is
52
+ // registered under the built-in name (e.g. "edit") when replaced. Extension
53
+ // tool registrations override built-ins of the same name in pi's tool
54
+ // definition registry, so registering under "edit" makes calls to `edit`
55
+ // dispatch to the readSeek implementation.
56
+ const { settings } = resolveReadSeekJsonSettings();
57
+ const replacedBuiltIns = new Set<ReplacedBuiltIn>(
58
+ (settings.replacedTools ?? []) as ReplacedBuiltIn[],
59
+ );
60
+ // Only swap registration under the built-in name when the readSeek binary
61
+ // is available; otherwise keep readSeek_* names so pi's built-ins stay
62
+ // intact and usable.
63
+ const binaryAvailable = readSeekBinaryAvailability().available;
64
+ const swap = (builtIn: ReplacedBuiltIn, readSeekName: string) =>
65
+ binaryAvailable && replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
66
+
67
+ const readName = swap("read", "readSeek_read");
68
+ const editName = swap("edit", "readSeek_edit");
69
+ const writeName = swap("write", "readSeek_write");
70
+ const grepName = swap("grep", "readSeek_grep");
71
+ const toolAliases = {
72
+ readSeek_read: readName,
73
+ readSeek_edit: editName,
74
+ readSeek_grep: grepName,
75
+ readSeek_write: writeName,
76
+ };
77
+
78
+ registerReadTool(pi, { onSuccessfulRead: markAnchored, name: readName });
79
+ registerEditTool(pi, { wasReadInSession: hasFreshAnchors, name: editName, toolAliases });
80
+ registerGrepTool(pi, { onFileAnchored: markAnchored, name: grepName });
39
81
  registerSgTool(pi, { onFileAnchored: markAnchored });
40
82
  registerRefsTool(pi, { onFileAnchored: markAnchored });
41
83
  registerRenameTool(pi);
42
84
  registerHoverTool(pi);
43
85
  registerDefTool(pi, { onFileAnchored: markAnchored });
44
- registerWriteTool(pi, { onFileAnchored: markAnchored });
86
+ registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
45
87
 
46
88
  pi.on("session_start", (_event, ctx) => {
47
- const { settings, warnings } = resolveReadSeekJsonSettings();
48
- const replacedTools = new Set<string>(settings.replacedTools ?? []);
89
+ const { warnings } = resolveReadSeekJsonSettings();
49
90
  const problems = warnings.map(formatSettingsWarning);
50
91
 
51
92
  const availability = readSeekBinaryAvailability();
@@ -60,8 +101,24 @@ export default function piReadSeekExtension(pi: ExtensionAPI): void {
60
101
 
61
102
  if (!availability.available) return;
62
103
 
63
- const activeTools = [...pi.getActiveTools(), ...READSEEK_TOOL_NAMES]
64
- .filter((name) => !replacedTools.has(name));
104
+ // readSeek tools were registered at load under names chosen from the
105
+ // load-time replacedTools. Replaced built-ins are registered under the
106
+ // built-in name, so the built-in stays active (now readSeek-backed);
107
+ // their readSeek_* variant was never registered and is excluded.
108
+ const activeReadSeekNames = READSEEK_TOOL_ENTRIES.map((entry) =>
109
+ entry.builtIn && replacedBuiltIns.has(entry.builtIn) ? entry.builtIn : entry.readSeekName,
110
+ );
111
+ const inactiveReadSeekNames = new Set(
112
+ READSEEK_TOOL_ENTRIES.filter(
113
+ (entry): entry is { builtIn: ReplacedBuiltIn; readSeekName: string } =>
114
+ entry.builtIn !== null && replacedBuiltIns.has(entry.builtIn),
115
+ ).map((entry) => entry.readSeekName),
116
+ );
117
+
118
+ const activeTools = [...pi.getActiveTools(), ...activeReadSeekNames].filter(
119
+ (name) => !inactiveReadSeekNames.has(name),
120
+ );
65
121
  pi.setActiveTools([...new Set(activeTools)]);
66
122
  });
67
- }
123
+
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
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": {
package/src/def.ts CHANGED
@@ -15,7 +15,7 @@ import type { FileAnchoredCallback } from "./tool-types.js";
15
15
 
16
16
  const DEF_PROMPT_METADATA = defineToolPromptMetadata({
17
17
  promptUrl: new URL("../prompts/def.md", import.meta.url),
18
- promptSnippet: "Find structural symbol definitions with readseek",
18
+ promptSnippet: "Find structural definitions for a symbol",
19
19
  });
20
20
 
21
21
  type DefParams = {
package/src/edit.ts CHANGED
@@ -69,10 +69,6 @@ const hashlineEditSchema = Type.Object(
69
69
 
70
70
  type HashlineParams = Static<typeof hashlineEditSchema>;
71
71
 
72
- const EDIT_PROMPT_METADATA = defineToolPromptMetadata({
73
- promptUrl: new URL("../prompts/edit.md", import.meta.url),
74
- promptSnippet: "Edit files using hash-verified anchors from readSeek_read/readSeek_grep/readSeek_search/readSeek_write",
75
- });
76
72
 
77
73
  function buildEditError(
78
74
  path: string,
@@ -110,6 +106,8 @@ function mapEditFileError(err: any, filePath: string, _displayPath: string, _pha
110
106
  export interface EditToolOptions {
111
107
  wasReadInSession?: FreshAnchorsPredicate;
112
108
  syntaxValidate?: SyntaxValidateOptions["syntaxValidate"];
109
+ name?: string;
110
+ toolAliases?: Readonly<Record<string, string>>;
113
111
  }
114
112
 
115
113
  export interface ExecuteEditOptions {
@@ -564,12 +562,19 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
564
562
 
565
563
 
566
564
  export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}) {
565
+ const name = options.name ?? "readSeek_edit";
566
+ const promptMetadata = defineToolPromptMetadata({
567
+ promptUrl: new URL("../prompts/edit.md", import.meta.url),
568
+ promptSnippet: "Edit with fresh hash-verified anchors",
569
+ registeredName: name,
570
+ toolAliases: options.toolAliases,
571
+ });
567
572
  const tool = registerReadSeekTool(pi, {
568
- name: "readSeek_edit",
573
+ name,
569
574
  label: "Edit",
570
- description: EDIT_PROMPT_METADATA.description,
571
- promptSnippet: EDIT_PROMPT_METADATA.promptSnippet,
572
- promptGuidelines: EDIT_PROMPT_METADATA.promptGuidelines,
575
+ description: promptMetadata.description,
576
+ promptSnippet: promptMetadata.promptSnippet,
577
+ promptGuidelines: promptMetadata.promptGuidelines,
573
578
  parameters: hashlineEditSchema,
574
579
  renderShell: "default" as const,
575
580
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
package/src/grep.ts CHANGED
@@ -24,10 +24,6 @@ import type { FileAnchoredCallback } from "./tool-types.js";
24
24
  import { optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
25
25
  import { searchPathParam } from "./readseek-params.js";
26
26
 
27
- const GREP_PROMPT_METADATA = defineToolPromptMetadata({
28
- promptUrl: new URL("../prompts/grep.md", import.meta.url),
29
- promptSnippet: "Search file contents and return edit-ready hashline anchors",
30
- });
31
27
 
32
28
  const grepSchema = Type.Object({
33
29
  pattern: Type.String({ description: "Pattern to search" }),
@@ -169,6 +165,7 @@ function escapeForRegex(s: string): string {
169
165
 
170
166
  interface GrepToolOptions {
171
167
  onFileAnchored?: FileAnchoredCallback;
168
+ name?: string;
172
169
  }
173
170
 
174
171
  export interface ExecuteGrepOptions {
@@ -513,13 +510,19 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
513
510
  }
514
511
 
515
512
  export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}) {
513
+ const name = options.name ?? "readSeek_grep";
514
+ const promptMetadata = defineToolPromptMetadata({
515
+ promptUrl: new URL("../prompts/grep.md", import.meta.url),
516
+ promptSnippet: "Search file text with edit-ready anchors",
517
+ registeredName: name,
518
+ });
516
519
  const tool = registerReadSeekTool(pi, {
517
- name: "readSeek_grep",
520
+ name,
518
521
  label: "grep",
519
- description: GREP_PROMPT_METADATA.description,
522
+ description: promptMetadata.description,
520
523
  parameters: grepSchema,
521
- promptSnippet: GREP_PROMPT_METADATA.promptSnippet,
522
- promptGuidelines: GREP_PROMPT_METADATA.promptGuidelines,
524
+ promptSnippet: promptMetadata.promptSnippet,
525
+ promptGuidelines: promptMetadata.promptGuidelines,
523
526
  async execute(toolCallId, params, signal, onUpdate, ctx) {
524
527
  return executeGrep({
525
528
  toolCallId,
package/src/hover.ts CHANGED
@@ -15,7 +15,7 @@ import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult,
15
15
 
16
16
  const HOVER_PROMPT_METADATA = defineToolPromptMetadata({
17
17
  promptUrl: new URL("../prompts/hover.md", import.meta.url),
18
- promptSnippet: "Identify the identifier and enclosing symbol at a cursor position",
18
+ promptSnippet: "Identify a cursor token and enclosing symbol",
19
19
  });
20
20
 
21
21
  const hoverSchema = Type.Object({
package/src/read.ts CHANGED
@@ -33,10 +33,6 @@ import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult,
33
33
  import type { FileAnchoredCallback } from "./tool-types.js";
34
34
  import { filePathParam, mapParam, optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
35
35
 
36
- const READ_PROMPT_METADATA = defineToolPromptMetadata({
37
- promptUrl: new URL("../prompts/read.md", import.meta.url),
38
- promptSnippet: "Read text files or images; text reads include hashline anchors and optional maps/symbol lookup, image reads include the attachment plus OCR text, an image caption, and detected objects",
39
- });
40
36
 
41
37
  interface ReadParams {
42
38
  path: string;
@@ -49,6 +45,7 @@ interface ReadParams {
49
45
 
50
46
  interface ReadToolOptions {
51
47
  onSuccessfulRead?: FileAnchoredCallback;
48
+ name?: string;
52
49
  }
53
50
 
54
51
  export interface ExecuteReadOptions {
@@ -459,12 +456,18 @@ function splitReadSeekLines(text: string): string[] {
459
456
  }
460
457
 
461
458
  export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}) {
459
+ const name = options.name ?? "readSeek_read";
460
+ const promptMetadata = defineToolPromptMetadata({
461
+ promptUrl: new URL("../prompts/read.md", import.meta.url),
462
+ promptSnippet: "Read files or images with anchors, maps, symbols, and OCR",
463
+ registeredName: name,
464
+ });
462
465
  const tool = registerReadSeekTool(pi, {
463
- name: "readSeek_read",
466
+ name,
464
467
  label: "Read",
465
- description: READ_PROMPT_METADATA.description,
466
- promptSnippet: READ_PROMPT_METADATA.promptSnippet,
467
- promptGuidelines: READ_PROMPT_METADATA.promptGuidelines,
468
+ description: promptMetadata.description,
469
+ promptSnippet: promptMetadata.promptSnippet,
470
+ promptGuidelines: promptMetadata.promptGuidelines,
468
471
  parameters: Type.Object({
469
472
  path: filePathParam(),
470
473
  offset: optionalIntOrString("Start line (1-indexed)"),
package/src/refs.ts CHANGED
@@ -27,7 +27,7 @@ type RefsParams = {
27
27
 
28
28
  const REFS_PROMPT_METADATA = defineToolPromptMetadata({
29
29
  promptUrl: new URL("../prompts/refs.md", import.meta.url),
30
- promptSnippet: "Find references to an identifier with readseek and return edit-ready anchors",
30
+ promptSnippet: "Find identifier references with enclosing symbols",
31
31
  });
32
32
 
33
33
  interface RefsToolOptions {
package/src/rename.ts CHANGED
@@ -13,7 +13,7 @@ import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult,
13
13
 
14
14
  const RENAME_PROMPT_METADATA = defineToolPromptMetadata({
15
15
  promptUrl: new URL("../prompts/rename.md", import.meta.url),
16
- promptSnippet: "Rename an identifier with binding-accurate readseek rename",
16
+ promptSnippet: "Rename a binding accurately from its cursor",
17
17
  });
18
18
 
19
19
  const renameSchema = Type.Object({
package/src/sg.ts CHANGED
@@ -49,7 +49,7 @@ export function mergeRanges(ranges: SgRange[]): SgRange[] {
49
49
 
50
50
  const SG_PROMPT_METADATA = defineToolPromptMetadata({
51
51
  promptUrl: new URL("../prompts/sg.md", import.meta.url),
52
- promptSnippet: "Search code structurally with readseek and return edit-ready anchors",
52
+ promptSnippet: "Search code by AST pattern with edit-ready anchors",
53
53
  });
54
54
 
55
55
  interface SgToolOptions {
@@ -12,24 +12,42 @@ const COMPACT_DESCRIPTIONS: Record<string, string> = {
12
12
  "refs.md": "Find references to an identifier and return anchored usages with enclosing symbols.",
13
13
  };
14
14
 
15
+ const REPLACEABLE_TOOL_GUIDELINES: Record<string, { readSeekName: string; builtInName: string; benefit: string }> = {
16
+ "read.md": {
17
+ readSeekName: "readSeek_read",
18
+ builtInName: "read",
19
+ benefit: "it provides LINE:HASH anchors for safe edits.",
20
+ },
21
+ "edit.md": {
22
+ readSeekName: "readSeek_edit",
23
+ builtInName: "edit",
24
+ benefit: "it verifies fresh LINE:HASH anchors.",
25
+ },
26
+ "grep.md": {
27
+ readSeekName: "readSeek_grep",
28
+ builtInName: "grep",
29
+ benefit: "it returns edit-ready anchors.",
30
+ },
31
+ "write.md": {
32
+ readSeekName: "readSeek_write",
33
+ builtInName: "write",
34
+ benefit: "it returns LINE:HASH anchors.",
35
+ },
36
+ };
37
+
15
38
  const COMPACT_GUIDELINES: Record<string, string[]> = {
16
39
  "read.md": [
17
- "Use readSeek_read for file contents, images/screenshots, ranges, symbols, and edit anchors.",
18
- "Use map or symbol mode before pulling large code files into context.",
19
- "Use readSeek_read for images; it returns the image attachment plus OCR text, a caption, and detected objects, so you don't need separate OCR tools.",
40
+ "Use readSeek_read map or symbol mode before pulling large code files into context.",
41
+ "Use readSeek_read for images; it returns the image attachment plus OCR text, a caption, and detected objects.",
20
42
  ],
21
43
  "edit.md": [
22
- "Use readSeek_edit with fresh LINE:HASH anchors for existing files.",
23
- "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical.",
44
+ "With readSeek_edit, prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical.",
24
45
  ],
25
46
  "grep.md": [
26
- "Use readSeek_grep for text search and edit-ready matching anchors.",
27
47
  "Use readSeek_grep summary mode for broad count/file discovery before narrowing.",
28
48
  ],
29
-
30
49
  "write.md": [
31
- "Use readSeek_write to create files or intentionally overwrite whole files.",
32
- "Use readSeek_edit rather than readSeek_write for small changes or appends to existing files.",
50
+ "Use anchored edits rather than readSeek_write for small changes or appends to existing files.",
33
51
  ],
34
52
  "sg.md": [
35
53
  "Use readSeek_search for AST-shaped code patterns.",
@@ -62,16 +80,41 @@ function promptFileName(promptUrl: URL): string {
62
80
  return promptUrl.pathname.split("/").pop() ?? "";
63
81
  }
64
82
 
83
+ function rewriteToolAliases(value: string, toolAliases: Readonly<Record<string, string>> | undefined): string {
84
+ if (!toolAliases) return value;
85
+ return Object.entries(toolAliases).reduce(
86
+ (rewritten, [canonicalName, registeredName]) => rewritten.replaceAll(canonicalName, registeredName),
87
+ value,
88
+ );
89
+ }
90
+
65
91
  export function defineToolPromptMetadata(options: {
66
92
  promptUrl: URL;
67
93
  promptSnippet: string;
94
+ registeredName?: string;
95
+ toolAliases?: Readonly<Record<string, string>>;
68
96
  }): ToolPromptMetadata {
69
97
  const prompt = loadPrompt(options.promptUrl);
70
98
  const fileName = promptFileName(options.promptUrl);
71
99
  const compactDescription = COMPACT_DESCRIPTIONS[fileName];
100
+ const replaceable = REPLACEABLE_TOOL_GUIDELINES[fileName];
101
+ const registeredName = options.registeredName ?? replaceable?.readSeekName;
102
+ const preferenceGuideline = replaceable && registeredName
103
+ ? registeredName === replaceable.readSeekName
104
+ ? `Prefer ${registeredName} over ${replaceable.builtInName} when both are available; ${replaceable.benefit}`
105
+ : `Use ${registeredName}; ${replaceable.benefit}`
106
+ : undefined;
72
107
  return {
73
- description: compactDescription ?? firstPromptParagraph(prompt),
74
- promptSnippet: options.promptSnippet,
75
- promptGuidelines: COMPACT_GUIDELINES[fileName] ?? [],
108
+ description: rewriteToolAliases(compactDescription ?? firstPromptParagraph(prompt), options.toolAliases),
109
+ promptSnippet: rewriteToolAliases(options.promptSnippet, options.toolAliases),
110
+ promptGuidelines: [
111
+ ...(preferenceGuideline ? [preferenceGuideline] : []),
112
+ ...(COMPACT_GUIDELINES[fileName] ?? []).map((guideline) =>
113
+ rewriteToolAliases(
114
+ registeredName && replaceable ? guideline.replaceAll(replaceable.readSeekName, registeredName) : guideline,
115
+ options.toolAliases,
116
+ ),
117
+ ),
118
+ ],
76
119
  };
77
120
  }
package/src/write.ts CHANGED
@@ -66,10 +66,6 @@ function pendingWritePreviewParts(summary: string, preview: PendingDiffPreviewRe
66
66
  return { lines: [summary, headerLine], diffData: expanded ? diffData : undefined };
67
67
  }
68
68
 
69
- const WRITE_PROMPT_METADATA = defineToolPromptMetadata({
70
- promptUrl: new URL("../prompts/write.md", import.meta.url),
71
- promptSnippet: "Create or overwrite a complete file and return edit anchors",
72
- });
73
69
 
74
70
  type WriteDiffFields = {
75
71
  diff?: string;
@@ -123,6 +119,7 @@ function generateWriteDiff(previousContent: string, nextContent: string): { diff
123
119
 
124
120
  export interface WriteToolOptions {
125
121
  onFileAnchored?: FileAnchoredCallback;
122
+ name?: string;
126
123
  }
127
124
 
128
125
  function buildWriteFsErrorResult(err: any, absolutePath: string) {
@@ -241,12 +238,18 @@ export async function executeWrite(opts: {
241
238
  }
242
239
 
243
240
  export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions = {}) {
241
+ const name = options.name ?? "readSeek_write";
242
+ const promptMetadata = defineToolPromptMetadata({
243
+ promptUrl: new URL("../prompts/write.md", import.meta.url),
244
+ promptSnippet: "Create or overwrite a file with edit anchors",
245
+ registeredName: name,
246
+ });
244
247
  const tool = registerReadSeekTool(pi, {
245
- name: "readSeek_write",
248
+ name,
246
249
  label: "write",
247
- description: WRITE_PROMPT_METADATA.description,
248
- promptSnippet: WRITE_PROMPT_METADATA.promptSnippet,
249
- promptGuidelines: WRITE_PROMPT_METADATA.promptGuidelines,
250
+ description: promptMetadata.description,
251
+ promptSnippet: promptMetadata.promptSnippet,
252
+ promptGuidelines: promptMetadata.promptGuidelines,
250
253
  parameters: Type.Object({
251
254
  path: filePathParam(),
252
255
  content: Type.String({ description: "File content" }),