pi-readseek 0.4.30 → 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/src/refs.ts CHANGED
@@ -5,10 +5,10 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
5
5
  import { statSearchPathOrError } from "./stat-search-path.js";
6
6
  import { buildReadSeekLineWithHash, buildToolErrorResult } from "./readseek-value.js";
7
7
  import { resolveToCwd } from "./path-utils.js";
8
- import { classifyReadSeekFailure, readseekRefs, type ReadSeekReference } from "./readseek-client.js";
8
+ import { classifyReadSeekFailure, readSeekRefs, type ReadSeekReference } from "./readseek-client.js";
9
9
  import { buildRefsOutput, type RefsOutputFile, type RefsOutputLine } from "./refs-output.js";
10
10
  import type { FileAnchoredCallback } from "./tool-types.js";
11
- import { langParam, readseekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
11
+ import { langParam, readSeekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
12
12
  import { registerReadSeekTool } from "./register-tool.js";
13
13
 
14
14
  import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
@@ -94,7 +94,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
94
94
  if (!statResult.ok) return statResult.error;
95
95
 
96
96
  try {
97
- const references = await readseekRefs(searchPath, p.name, {
97
+ const references = await readSeekRefs(searchPath, p.name, {
98
98
  scope: p.scope,
99
99
  line: p.line,
100
100
  column: p.column,
@@ -111,7 +111,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
111
111
  }
112
112
  return {
113
113
  content: [{ type: "text", text: builtOutput.text }],
114
- details: { readseekValue: builtOutput.readseekValue },
114
+ details: { readSeekValue: builtOutput.readSeekValue },
115
115
  };
116
116
  } catch (err: any) {
117
117
  const failure = classifyReadSeekFailure(err);
@@ -124,11 +124,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
124
124
 
125
125
  export function registerRefsTool(pi: ExtensionAPI, options: RefsToolOptions = {}) {
126
126
  const tool = registerReadSeekTool(pi, {
127
- policy: "read-only",
128
- pythonName: "refs",
129
- defaultExposure: "opt-in",
130
- }, {
131
- name: "refs",
127
+ name: "readSeek_refs",
132
128
  label: "References",
133
129
  description: REFS_PROMPT_METADATA.description,
134
130
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
@@ -140,7 +136,7 @@ export function registerRefsTool(pi: ExtensionAPI, options: RefsToolOptions = {}
140
136
  scope: Type.Optional(Type.Boolean({ description: "Restrict to the binding under line/column (single file)" })),
141
137
  line: Type.Optional(Type.Number({ description: "One-based cursor line, used with scope" })),
142
138
  column: Type.Optional(Type.Number({ description: "One-based cursor byte column, used with scope" })),
143
- ...readseekGitSearchParams(),
139
+ ...readSeekGitSearchParams(),
144
140
  }),
145
141
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
146
142
  return executeRefs({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
@@ -20,48 +20,10 @@ export function mapParam() {
20
20
  return Type.Optional(Type.Boolean({ description: "Append structural map" }));
21
21
  }
22
22
 
23
- export type ReadSeekToolPolicy = "read-only" | "mutating";
24
-
25
- export type ReadSeekToolExposure = "safe-by-default" | "opt-in" | "not-safe-by-default";
26
-
27
- /**
28
- * Per-tool fields of the pi tool config (`ptc`) that genuinely differ between
29
- * readseek tools. The remaining fields are constant or derived.
30
- */
31
- export interface ReadSeekToolConfig {
32
- policy: ReadSeekToolPolicy;
33
- pythonName: string;
34
- defaultExposure: ReadSeekToolExposure;
35
- }
36
-
37
- export interface ReadSeekToolPtc extends ReadSeekToolConfig {
38
- callable: true;
39
- enabled: true;
40
- readOnly: boolean;
41
- }
42
-
43
23
  type ToolSpec = Parameters<ExtensionAPI["registerTool"]>[0];
44
24
 
45
- /**
46
- * Attach the standard `ptc` envelope to a tool definition and register it.
47
- *
48
- * `callable` and `enabled` are always true and `readOnly` is derived from
49
- * `policy`, so callers supply only the fields that vary between tools.
50
- */
51
- export function registerReadSeekTool<T extends ToolSpec>(
52
- pi: ExtensionAPI,
53
- config: ReadSeekToolConfig,
54
- tool: T,
55
- ): T & { ptc: ReadSeekToolPtc } {
56
- const ptc: ReadSeekToolPtc = {
57
- callable: true,
58
- enabled: true,
59
- policy: config.policy,
60
- readOnly: config.policy === "read-only",
61
- pythonName: config.pythonName,
62
- defaultExposure: config.defaultExposure,
63
- };
64
- const registered = { ...tool, ptc };
65
- pi.registerTool(registered);
66
- return registered;
25
+ /** Register a readseek-backed tool definition. */
26
+ export function registerReadSeekTool<T extends ToolSpec>(pi: ExtensionAPI, tool: T): T {
27
+ pi.registerTool(tool);
28
+ return tool;
67
29
  }
package/src/rename.ts CHANGED
@@ -6,7 +6,7 @@ import { Text } from "@earendil-works/pi-tui";
6
6
  import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
7
7
  import { buildToolErrorResult } from "./readseek-value.js";
8
8
  import { resolveToCwd } from "./path-utils.js";
9
- import { classifyReadSeekFailure, readseekRename, type RenameOutput } from "./readseek-client.js";
9
+ import { classifyReadSeekFailure, readSeekRename, type RenameOutput } from "./readseek-client.js";
10
10
  import { filePathParam, registerReadSeekTool } from "./register-tool.js";
11
11
 
12
12
  import { clampLinesToWidth, linkToolPath, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
@@ -54,7 +54,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
54
54
  const filePath = resolveToCwd(p.path, cwd);
55
55
 
56
56
  try {
57
- const output = await readseekRename(filePath, {
57
+ const output = await readSeekRename(filePath, {
58
58
  to: p.to,
59
59
  line: p.line,
60
60
  column: p.column,
@@ -92,7 +92,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
92
92
  return {
93
93
  content: [{ type: "text", text }],
94
94
  details: {
95
- readseekValue: {
95
+ readSeekValue: {
96
96
  tool: "rename",
97
97
  ok: true,
98
98
  path: filePath,
@@ -108,11 +108,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
108
108
 
109
109
  export function registerRenameTool(pi: ExtensionAPI) {
110
110
  registerReadSeekTool(pi, {
111
- policy: "mutating",
112
- pythonName: "rename",
113
- defaultExposure: "not-safe-by-default",
114
- }, {
115
- name: "rename",
111
+ name: "readSeek_rename",
116
112
  label: "Rename",
117
113
  description: RENAME_PROMPT_METADATA.description,
118
114
  promptSnippet: RENAME_PROMPT_METADATA.promptSnippet,
@@ -138,8 +134,8 @@ export function registerRenameTool(pi: ExtensionAPI) {
138
134
 
139
135
  const content = result.content?.[0];
140
136
  const textContent = content?.type === "text" ? content.text : "";
141
- const readseekValue = (result.details as any)?.readseekValue;
142
- const output = readseekValue?.output as RenameOutput | undefined;
137
+ const readSeekValue = (result.details as any)?.readSeekValue;
138
+ const output = readSeekValue?.output as RenameOutput | undefined;
143
139
 
144
140
  if (isError || result.isError) {
145
141
  return new Text(textContent || "rename failed", 0, 0);
@@ -1,4 +1,4 @@
1
- import { readseekMapContent } from "./readseek-client.js";
1
+ import { readSeekMapContent } from "./readseek-client.js";
2
2
  import { findSymbol } from "./readseek/symbol-lookup.js";
3
3
  import { formatAmbiguous, formatNotFound } from "./readseek/symbol-error-format.js";
4
4
  import { normalizeToLF } from "./edit-diff.js";
@@ -33,7 +33,7 @@ function reindent(text: string, indent: string): string {
33
33
  }
34
34
 
35
35
  export async function replaceSymbol(input: ReplaceSymbolInput): Promise<ReplaceSymbolResult> {
36
- const map = await readseekMapContent(input.filePath, input.content);
36
+ const map = await readSeekMapContent(input.filePath, input.content);
37
37
  if (!map) {
38
38
  return {
39
39
  type: "unsupported",
package/src/sg-output.ts CHANGED
@@ -15,7 +15,7 @@ export interface BuildSgOutputInput {
15
15
 
16
16
  export interface SgOutputResult {
17
17
  text: string;
18
- readseekValue: {
18
+ readSeekValue: {
19
19
  tool: "search";
20
20
  files: Array<{
21
21
  path: string;
@@ -29,7 +29,7 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
29
29
  if (input.files.length === 0) {
30
30
  return {
31
31
  text: `No matches found for pattern: ${input.pattern}`,
32
- readseekValue: {
32
+ readSeekValue: {
33
33
  tool: "search",
34
34
  files: [],
35
35
  },
@@ -38,7 +38,7 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
38
38
 
39
39
  return {
40
40
  text: formatAnchoredFileBlocks(input.files),
41
- readseekValue: {
41
+ readSeekValue: {
42
42
  tool: "search",
43
43
  files: input.files.map((file) => ({
44
44
  path: file.path,
package/src/sg.ts CHANGED
@@ -7,10 +7,10 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
7
7
  import { buildReadSeekLineWithHash, buildToolErrorResult, type ReadSeekLine } from "./readseek-value.js";
8
8
  import { resolveToCwd } from "./path-utils.js";
9
9
  import { statSearchPathOrError } from "./stat-search-path.js";
10
- import { classifyReadSeekFailure, readseekSearch, type ReadSeekHashline, type ReadSeekSearchFileOutput } from "./readseek-client.js";
10
+ import { classifyReadSeekFailure, readSeekSearch, type ReadSeekHashline, type ReadSeekSearchFileOutput } from "./readseek-client.js";
11
11
  import { buildSgOutput } from "./sg-output.js";
12
12
  import type { FileAnchoredCallback } from "./tool-types.js";
13
- import { langParam, readseekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
13
+ import { langParam, readSeekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
14
14
  import { registerReadSeekTool } from "./register-tool.js";
15
15
 
16
16
  import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
@@ -66,7 +66,7 @@ export interface ExecuteSgOptions {
66
66
  onFileAnchored?: FileAnchoredCallback;
67
67
  }
68
68
 
69
- function readseekLineFromSearch(line: ReadSeekHashline): ReadSeekLine {
69
+ function readSeekLineFromSearch(line: ReadSeekHashline): ReadSeekLine {
70
70
  return buildReadSeekLineWithHash(line.line, line.hash, line.text);
71
71
  }
72
72
 
@@ -74,7 +74,7 @@ function linesFromSearchResult(result: ReadSeekSearchFileOutput, ranges: SgRange
74
74
  const lineMap = new Map<number, ReadSeekLine>();
75
75
  for (const match of result.matches) {
76
76
  for (const line of match.hashlines) {
77
- lineMap.set(line.line, readseekLineFromSearch(line));
77
+ lineMap.set(line.line, readSeekLineFromSearch(line));
78
78
  }
79
79
  }
80
80
 
@@ -83,16 +83,16 @@ function linesFromSearchResult(result: ReadSeekSearchFileOutput, ranges: SgRange
83
83
  for (const range of ranges) {
84
84
  for (let line = range.startLine; line <= range.endLine; line++) {
85
85
  if (seen.has(line)) continue;
86
- const readseekLine = lineMap.get(line);
87
- if (!readseekLine) continue;
86
+ const readSeekLine = lineMap.get(line);
87
+ if (!readSeekLine) continue;
88
88
  seen.add(line);
89
- lines.push(readseekLine);
89
+ lines.push(readSeekLine);
90
90
  }
91
91
  }
92
92
  return lines;
93
93
  }
94
94
 
95
- function readseekLanguageForPath(language: string | undefined, searchPath: string, isFile: boolean): string | undefined {
95
+ function readSeekLanguageForPath(language: string | undefined, searchPath: string, isFile: boolean): string | undefined {
96
96
  if (language === "typescript" && isFile && path.extname(searchPath).toLowerCase() === ".tsx") return "tsx";
97
97
  return language;
98
98
  }
@@ -112,8 +112,8 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
112
112
  const searchPathIsFile = statResult.stats.isFile();
113
113
 
114
114
  try {
115
- const effectiveLang = readseekLanguageForPath(p.lang, searchPath, searchPathIsFile);
116
- const results = await readseekSearch(searchPath, p.pattern, {
115
+ const effectiveLang = readSeekLanguageForPath(p.lang, searchPath, searchPathIsFile);
116
+ const results = await readSeekSearch(searchPath, p.pattern, {
117
117
  language: effectiveLang,
118
118
  cached: p.cached,
119
119
  others: p.others,
@@ -125,12 +125,12 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
125
125
  return {
126
126
  content: [{ type: "text", text: emptyOutput.text }],
127
127
  details: {
128
- readseekValue: emptyOutput.readseekValue,
128
+ readSeekValue: emptyOutput.readSeekValue,
129
129
  },
130
130
  };
131
131
  }
132
132
 
133
- const readseekFiles: Array<{
133
+ const readSeekFiles: Array<{
134
134
  displayPath: string;
135
135
  path: string;
136
136
  ranges: SgRange[];
@@ -145,7 +145,7 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
145
145
  const mergedRanges = mergeRanges(ranges);
146
146
  const lines = linesFromSearchResult(result, mergedRanges);
147
147
  if (lines.length === 0) continue;
148
- readseekFiles.push({
148
+ readSeekFiles.push({
149
149
  displayPath: display,
150
150
  path: abs,
151
151
  ranges: mergedRanges.map((range) => ({ ...range })),
@@ -153,27 +153,27 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
153
153
  });
154
154
  }
155
155
 
156
- if (readseekFiles.length === 0) {
156
+ if (readSeekFiles.length === 0) {
157
157
  const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
158
158
  return {
159
159
  content: [{ type: "text", text: emptyOutput.text }],
160
160
  details: {
161
- readseekValue: emptyOutput.readseekValue,
161
+ readSeekValue: emptyOutput.readSeekValue,
162
162
  },
163
163
  };
164
164
  }
165
165
 
166
166
  const builtOutput = buildSgOutput({
167
167
  pattern: p.pattern,
168
- files: readseekFiles,
168
+ files: readSeekFiles,
169
169
  });
170
- for (const readseekFile of readseekFiles) {
171
- onFileAnchored?.(readseekFile.path);
170
+ for (const readSeekFile of readSeekFiles) {
171
+ onFileAnchored?.(readSeekFile.path);
172
172
  }
173
173
  return {
174
174
  content: [{ type: "text", text: builtOutput.text }],
175
175
  details: {
176
- readseekValue: builtOutput.readseekValue,
176
+ readSeekValue: builtOutput.readSeekValue,
177
177
  },
178
178
  };
179
179
  } catch (err: any) {
@@ -184,11 +184,7 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
184
184
 
185
185
  export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
186
186
  const tool = registerReadSeekTool(pi, {
187
- policy: "read-only",
188
- pythonName: "search",
189
- defaultExposure: "opt-in",
190
- }, {
191
- name: "search",
187
+ name: "readSeek_search",
192
188
  label: "Structural Search",
193
189
  description: SG_PROMPT_METADATA.description,
194
190
  promptSnippet: SG_PROMPT_METADATA.promptSnippet,
@@ -197,7 +193,7 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
197
193
  pattern: Type.String({ description: "AST pattern" }),
198
194
  lang: langParam(),
199
195
  path: searchPathParam(),
200
- ...readseekGitSearchParams(),
196
+ ...readSeekGitSearchParams(),
201
197
  }),
202
198
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
203
199
  return executeSg({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
@@ -1,25 +1,15 @@
1
+ import { resolveReadSeekSyntaxValidation } from "./readseek-settings.js";
2
+
1
3
  export type SyntaxValidateMode = "warn" | "block" | "off";
2
4
 
3
5
  export interface SyntaxValidateOptions {
4
6
  syntaxValidate?: SyntaxValidateMode;
5
7
  }
6
8
 
7
- const VALID = new Set<SyntaxValidateMode>(["warn", "block", "off"]);
8
9
  const DEFAULT: SyntaxValidateMode = "warn";
9
10
 
10
- function coerce(value: unknown): SyntaxValidateMode | undefined {
11
- if (typeof value !== "string") return undefined;
12
- return VALID.has(value as SyntaxValidateMode)
13
- ? (value as SyntaxValidateMode)
14
- : undefined;
15
- }
16
-
17
11
  export function resolveSyntaxValidateMode(
18
12
  opts: SyntaxValidateOptions,
19
13
  ): SyntaxValidateMode {
20
- const fromOpt = coerce(opts.syntaxValidate);
21
- if (fromOpt) return fromOpt;
22
- const fromEnv = coerce(process.env.READSEEK_SYNTAX_VALIDATE);
23
- if (fromEnv) return fromEnv;
24
- return DEFAULT;
14
+ return opts.syntaxValidate ?? resolveReadSeekSyntaxValidation() ?? DEFAULT;
25
15
  }
@@ -4,7 +4,7 @@ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@earendil-work
4
4
 
5
5
  const COMPACT_DESCRIPTIONS: Record<string, string> = {
6
6
  "read.md": "Read text files/images by path; text has LINE:HASH anchors, images return the attachment plus OCR-extracted text.",
7
- "edit.md": "Edit existing text files using fresh LINE:HASH anchors from read, grep, search, or write.",
7
+ "edit.md": "Edit existing text files using fresh LINE:HASH anchors from readSeek_read, readSeek_grep, readSeek_search, or readSeek_write.",
8
8
  "grep.md": "Search file contents; non-summary results include LINE:HASH anchors for edits.",
9
9
 
10
10
  "write.md": "Create or overwrite a complete file and return anchors.",
@@ -14,30 +14,30 @@ const COMPACT_DESCRIPTIONS: Record<string, string> = {
14
14
 
15
15
  const COMPACT_GUIDELINES: Record<string, string[]> = {
16
16
  "read.md": [
17
- "Use read for file contents, images/screenshots, ranges, symbols, and edit anchors.",
17
+ "Use readSeek_read for file contents, images/screenshots, ranges, symbols, and edit anchors.",
18
18
  "Use map or symbol mode before pulling large code files into context.",
19
- "Use read for images; it returns the image attachment plus OCR-extracted text, so you don't need separate OCR tools.",
19
+ "Use readSeek_read for images; it returns the image attachment plus OCR-extracted text, so you don't need separate OCR tools.",
20
20
  ],
21
21
  "edit.md": [
22
- "Use edit with fresh LINE:HASH anchors for existing files.",
22
+ "Use readSeek_edit with fresh LINE:HASH anchors for existing files.",
23
23
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical.",
24
24
  ],
25
25
  "grep.md": [
26
- "Use grep for text search and edit-ready matching anchors.",
27
- "Use grep summary mode for broad count/file discovery before narrowing.",
26
+ "Use readSeek_grep for text search and edit-ready matching anchors.",
27
+ "Use readSeek_grep summary mode for broad count/file discovery before narrowing.",
28
28
  ],
29
29
 
30
30
  "write.md": [
31
- "Use write to create files or intentionally overwrite whole files.",
32
- "Use edit rather than write for small changes or appends to existing files.",
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.",
33
33
  ],
34
34
  "sg.md": [
35
- "Use search for AST-shaped code patterns.",
36
- "Use grep instead of search for plain text.",
35
+ "Use readSeek_search for AST-shaped code patterns.",
36
+ "Use readSeek_grep instead of readSeek_search for plain text.",
37
37
  ],
38
38
  "refs.md": [
39
- "Use refs to find every usage of an identifier before renaming or deleting it.",
40
- "Use refs with scope plus line/column to follow a specific binding instead of every same-named identifier.",
39
+ "Use readSeek_refs to find every usage of an identifier before renaming or deleting it.",
40
+ "Use readSeek_refs with scope plus line/column to follow a specific binding instead of every same-named identifier.",
41
41
  ],
42
42
  };
43
43
 
@@ -238,10 +238,10 @@ export function renderAnchoredFilesResult(
238
238
  const textContent = content?.type === "text" ? content.text : "";
239
239
  if (isError || result.isError) return renderErrorResult(textContent, { expanded, width });
240
240
 
241
- const readseekValue = (result.details as any)?.readseekValue as
241
+ const readSeekValue = (result.details as any)?.readSeekValue as
242
242
  | { files: Array<{ path: string; lines: any[] }> }
243
243
  | undefined;
244
- const files = readseekValue?.files ?? [];
244
+ const files = readSeekValue?.files ?? [];
245
245
  if (files.length === 0) return new Text(summaryLine(labels.emptyLabel), 0, 0);
246
246
 
247
247
  const fileCount = files.length;
package/src/write.ts CHANGED
@@ -80,7 +80,7 @@ export interface WriteResult extends WriteDiffFields {
80
80
  text: string;
81
81
  warnings: string[];
82
82
  writeState?: "created" | "overwritten";
83
- readseekValue: {
83
+ readSeekValue: {
84
84
  tool: "write";
85
85
  path: string;
86
86
  lines: ReadSeekLine[];
@@ -143,24 +143,24 @@ export async function executeWrite(opts: {
143
143
 
144
144
  const { path: filePath, content, map: requestMap, cwd } = opts;
145
145
  const warnings: string[] = [];
146
- const readseekWarnings: ReadSeekWarning[] = [];
146
+ const readSeekWarnings: ReadSeekWarning[] = [];
147
147
 
148
148
  if (hasBareCarriageReturn(content)) {
149
- const message = "File content contains bare CR (\\r) line endings; write refuses to emit anchors that read/edit would normalize differently.";
149
+ const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_read/readSeek_edit would normalize differently.";
150
150
  warnings.push(message);
151
- readseekWarnings.push(buildReadSeekWarning("bare-cr", message));
151
+ readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
152
152
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}\n⚠️ ${message}`, {
153
153
  path: filePath,
154
- extra: { lines: [], warnings: readseekWarnings },
154
+ extra: { lines: [], warnings: readSeekWarnings },
155
155
  });
156
156
  }
157
157
  if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
158
158
  const message = "File content appears to be binary.";
159
159
  warnings.push(message);
160
- readseekWarnings.push(buildReadSeekWarning("binary-content", message));
160
+ readSeekWarnings.push(buildReadSeekWarning("binary-content", message));
161
161
  return buildToolErrorResult("write", "binary-content", `Cannot write ${filePath}\n⚠️ ${message} — refusing to write.`, {
162
162
  path: filePath,
163
- extra: { lines: [], warnings: readseekWarnings },
163
+ extra: { lines: [], warnings: readSeekWarnings },
164
164
  });
165
165
  }
166
166
 
@@ -173,13 +173,13 @@ export async function executeWrite(opts: {
173
173
  await writeFile(filePath, content, "utf-8");
174
174
  // Compute hashlines
175
175
  const rawLines = content.split("\n");
176
- const readseekLines: ReadSeekLine[] = [];
176
+ const readSeekLines: ReadSeekLine[] = [];
177
177
  const displayLines: string[] = [];
178
178
 
179
179
  for (let i = 0; i < rawLines.length; i++) {
180
180
  const lineNum = i + 1;
181
- const readseekLine = buildReadSeekLine(lineNum, rawLines[i]);
182
- readseekLines.push(readseekLine);
181
+ const readSeekLine = buildReadSeekLine(lineNum, rawLines[i]);
182
+ readSeekLines.push(readSeekLine);
183
183
  displayLines.push(formatHashlineDisplay(lineNum, rawLines[i]));
184
184
  }
185
185
 
@@ -188,9 +188,9 @@ export async function executeWrite(opts: {
188
188
  let text = truncated.content;
189
189
  if (truncated.truncated) {
190
190
  if (truncated.truncatedBy === "lines") {
191
- text += `\n[… ${truncated.totalLines - truncated.outputLines} more lines not shown — full anchors in readseekValue]`;
191
+ text += `\n[… ${truncated.totalLines - truncated.outputLines} more lines not shown — full anchors in readSeekValue]`;
192
192
  } else {
193
- text += "\n[… output truncated at 50 KB — full anchors in readseekValue]";
193
+ text += "\n[… output truncated at 50 KB — full anchors in readSeekValue]";
194
194
  }
195
195
  }
196
196
 
@@ -228,11 +228,11 @@ export async function executeWrite(opts: {
228
228
  writeState: existedBeforeWrite ? "overwritten" : "created",
229
229
  diff: diffResult.diff,
230
230
  diffData,
231
- readseekValue: {
231
+ readSeekValue: {
232
232
  tool: "write",
233
233
  path: displayPath,
234
- lines: readseekLines,
235
- warnings: readseekWarnings,
234
+ lines: readSeekLines,
235
+ warnings: readSeekWarnings,
236
236
  diff: diffResult.diff,
237
237
  diffData,
238
238
  ...(requestMap ? { map: { appended: mapAppended } } : {}),
@@ -242,11 +242,7 @@ export async function executeWrite(opts: {
242
242
 
243
243
  export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions = {}) {
244
244
  const tool = registerReadSeekTool(pi, {
245
- policy: "mutating",
246
- pythonName: "write",
247
- defaultExposure: "not-safe-by-default",
248
- }, {
249
- name: "write",
245
+ name: "readSeek_write",
250
246
  label: "write",
251
247
  description: WRITE_PROMPT_METADATA.description,
252
248
  promptSnippet: WRITE_PROMPT_METADATA.promptSnippet,
@@ -275,7 +271,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
275
271
 
276
272
  if (isToolErrorResult(result)) return result;
277
273
 
278
- if (result.readseekValue.lines.length > 0) {
274
+ if (result.readSeekValue.lines.length > 0) {
279
275
  options.onFileAnchored?.(absolutePath);
280
276
  }
281
277
 
@@ -285,7 +281,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
285
281
  ...(result.diff !== undefined ? { diff: result.diff } : {}),
286
282
  ...(result.diffData !== undefined ? { diffData: result.diffData } : {}),
287
283
  ...(result.writeState ? { writeState: result.writeState } : {}),
288
- readseekValue: result.readseekValue,
284
+ readSeekValue: result.readSeekValue,
289
285
  warnings: result.warnings,
290
286
  },
291
287
  };
@@ -326,7 +322,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
326
322
  if (isPartial) return renderPendingResult("pending write", width);
327
323
  const details = result.details ?? {};
328
324
  const output = result.content?.[0]?.type === "text" ? result.content[0].text : "";
329
- if (result.isError || details.readseekValue?.ok === false) {
325
+ if (result.isError || details.readSeekValue?.ok === false) {
330
326
  return renderErrorResult(output, { expanded, width, fallback: "write failed" });
331
327
  }
332
328
  const diffData = details.diffData;
@@ -335,12 +331,12 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
335
331
  // instead of a diff body — every line is an add, so the gutter, line
336
332
  // numbers, and red/green tinting are noise.
337
333
  if (state === "created") {
338
- const readseekLines = (details.readseekValue?.lines ?? []) as Array<{ raw: string }>;
339
- const hasContent = readseekLines.length > 0;
334
+ const readSeekLines = (details.readSeekValue?.lines ?? []) as Array<{ raw: string }>;
335
+ const hasContent = readSeekLines.length > 0;
340
336
  const header = summaryLine(state, { hidden: hasContent && !expanded });
341
337
  const lines = header.split("\n");
342
338
  if (expanded && hasContent) {
343
- const content = readseekLines.map((l) => l.raw).join("\n");
339
+ const content = readSeekLines.map((l) => l.raw).join("\n");
344
340
  lines.push(...formatContentPreviewLines(content, theme));
345
341
  }
346
342
  return new Text(clampLinesToWidth(lines, width).join("\n"), 0, 0);