pi-readseek 0.4.12 → 0.4.13

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/read.ts CHANGED
@@ -13,12 +13,12 @@ import { Text } from "@earendil-works/pi-tui";
13
13
  import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
14
14
  import { normalizeToLF, stripBom, hasBareCarriageReturn } from "./edit-diff.js";
15
15
  import { ensureHashInit, escapeControlCharsForDisplay } from "./hashline.js";
16
- import { buildReadseekWarning, buildToolErrorResult, renderReadseekLines, type ReadseekLine, type ReadseekWarning } from "./readseek-value.js";
16
+ import { buildReadSeekWarning, buildToolErrorResult, renderReadSeekLines, type ReadSeekLine, type ReadSeekWarning } from "./readseek-value.js";
17
17
  import { looksLikeBinary } from "./binary-detect.js";
18
18
  import { isSupportedImageBuffer } from "./image-detect.js";
19
19
  import { resolveToCwd } from "./path-utils.js";
20
20
  import { throwIfAborted } from "./runtime.js";
21
- import { getOrGenerateMap } from "./map-cache.js";
21
+ import { getOrGenerateMap } from "./file-map.js";
22
22
  import { formatFileMapWithBudget } from "./readseek/formatter.js";
23
23
  import { findSymbol, type SymbolMatch } from "./readseek/symbol-lookup.js";
24
24
  import { formatAmbiguous, formatNotFound } from "./readseek/symbol-error-format.js";
@@ -30,7 +30,7 @@ import { readseekRead } from "./readseek-client.js";
30
30
  import { formatReadCallText, formatReadResultText } from "./read-render-helpers.js";
31
31
  import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine, wrapReadHashlinesForWidth } from "./tui-render-utils.js";
32
32
  import type { FileAnchoredCallback } from "./tool-types.js";
33
- import { registerReadseekTool } from "./register-tool.js";
33
+ import { registerReadSeekTool } from "./register-tool.js";
34
34
 
35
35
  const READ_PROMPT_METADATA = defineToolPromptMetadata({
36
36
  promptUrl: new URL("../prompts/read.md", import.meta.url),
@@ -176,9 +176,9 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
176
176
  throwIfAborted(signal);
177
177
  const rawText = rawBuffer.toString("utf-8");
178
178
  const normalized = normalizeToLF(stripBom(rawText).text);
179
- const allLines = splitReadseekLines(normalized);
179
+ const allLines = splitReadSeekLines(normalized);
180
180
  const total = allLines.length;
181
- const structuredWarnings: ReadseekWarning[] = [];
181
+ const structuredWarnings: ReadSeekWarning[] = [];
182
182
  let startLine = p.offset !== undefined ? p.offset : 1;
183
183
  let endIdx = p.limit !== undefined ? Math.min(startLine - 1 + p.limit, total) : total;
184
184
  if (p.offset !== undefined && startLine > total) {
@@ -203,7 +203,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
203
203
  };
204
204
  lines: string[];
205
205
  }>;
206
- warnings: ReadseekWarning[];
206
+ warnings: ReadSeekWarning[];
207
207
  }
208
208
  | null = null;
209
209
  if (p.symbol) {
@@ -248,7 +248,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
248
248
  lines.push(` To confirm: ${confirmHint}.]`);
249
249
  const bannerText = lines.join("\n");
250
250
  structuredWarnings.push(
251
- buildReadseekWarning("fuzzy-symbol-match", bannerText, {
251
+ buildReadSeekWarning("fuzzy-symbol-match", bannerText, {
252
252
  tier: lookup.tier,
253
253
  symbol: lookup.symbol,
254
254
  otherCandidates: lookup.otherCandidates,
@@ -261,7 +261,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
261
261
  if (p.bundle === "local") {
262
262
  if (!symbolFileMap) {
263
263
  const extLabel = ext || "unknown";
264
- const warning = buildReadseekWarning(
264
+ const warning = buildReadSeekWarning(
265
265
  "bundle-unmappable",
266
266
  `[Warning: local bundle unavailable because symbol mapping is not available for .${extLabel} files — showing plain symbol read]`,
267
267
  );
@@ -282,7 +282,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
282
282
  } else {
283
283
  const bundle = buildLocalBundle(symbolFileMap, symbolMatch, allLines);
284
284
  if (!bundle) {
285
- const warning = buildReadseekWarning(
285
+ const warning = buildReadSeekWarning(
286
286
  "bundle-context-unavailable",
287
287
  `[Warning: local bundle context could not be determined for symbol '${symbolMatch.name}' — showing plain symbol read]`,
288
288
  );
@@ -333,7 +333,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
333
333
  : `readseek returned ${readseekOutput.hashlines.length} lines for requested range ${startLine}-${endIdx} (${expectedLineCount} expected)`;
334
334
  return buildToolErrorResult("read", "readseek-output-mismatch", message, { path: rawParams.path });
335
335
  }
336
- const readseekLines: ReadseekLine[] = readseekOutput.hashlines.map((line) => ({
336
+ const readseekLines: ReadSeekLine[] = readseekOutput.hashlines.map((line) => ({
337
337
  line: line.line,
338
338
  hash: line.hash,
339
339
  anchor: `${line.line}:${line.hash}`,
@@ -341,7 +341,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
341
341
  display: escapeControlCharsForDisplay(line.text),
342
342
  }));
343
343
  const selected = readseekLines.map((line) => line.raw);
344
- const formatted = renderReadseekLines(readseekLines);
344
+ const formatted = renderReadSeekLines(readseekLines);
345
345
 
346
346
  const truncation = truncateHead(formatted, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
347
347
 
@@ -363,17 +363,17 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
363
363
  }
364
364
 
365
365
  if (symbolWarning) {
366
- structuredWarnings.push(buildReadseekWarning("symbol-warning", symbolWarning.trim()));
366
+ structuredWarnings.push(buildReadSeekWarning("symbol-warning", symbolWarning.trim()));
367
367
  }
368
368
 
369
369
  if (hasBinaryContent) {
370
370
  const warning = "[Warning: file appears to be binary — output may be garbled]";
371
- structuredWarnings.push(buildReadseekWarning("binary-content", warning));
371
+ structuredWarnings.push(buildReadSeekWarning("binary-content", warning));
372
372
  }
373
373
 
374
374
  if (hasBareCarriageReturn(rawText)) {
375
375
  const warning = "[Warning: file contains bare CR (\\r) line endings — line numbering may be inconsistent with grep and other tools]";
376
- structuredWarnings.push(buildReadseekWarning("bare-cr", warning));
376
+ structuredWarnings.push(buildReadSeekWarning("bare-cr", warning));
377
377
  }
378
378
 
379
379
  const readOutput = buildReadOutput({
@@ -420,14 +420,14 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
420
420
  });
421
421
  }
422
422
 
423
- function splitReadseekLines(text: string): string[] {
423
+ function splitReadSeekLines(text: string): string[] {
424
424
  if (text.length === 0) return [];
425
425
  const withoutTrailingTerminator = text.endsWith("\n") ? text.slice(0, -1) : text;
426
426
  return withoutTrailingTerminator.split("\n");
427
427
  }
428
428
 
429
429
  export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}) {
430
- const tool = registerReadseekTool(pi, {
430
+ const tool = registerReadSeekTool(pi, {
431
431
  policy: "read-only",
432
432
  pythonName: "read",
433
433
  defaultExposure: "safe-by-default",
@@ -497,7 +497,7 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
497
497
  return new Text(clampLinesToWidth([summaryLine(errorText)], width).join("\n"), 0, 0);
498
498
  }
499
499
 
500
- const readseekValue = (result.details as any)?.readseekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings: ReadseekWarning[] } | undefined;
500
+ const readseekValue = (result.details as any)?.readseekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings: ReadSeekWarning[] } | undefined;
501
501
  if (!readseekValue) {
502
502
  const lines = textContent.split("\n").filter(Boolean).length || textContent.split("\n").length;
503
503
  return new Text(summaryLine(`loaded ${lines} ${lines === 1 ? "line" : "lines"}`, { hidden: !!textContent && !expanded }), 0, 0);
@@ -2,10 +2,6 @@
2
2
  * Constants for thresholds.
3
3
  */
4
4
  export const THRESHOLDS = {
5
- /** Maximum lines before truncation */
6
- MAX_LINES: 2000,
7
- /** Maximum bytes before truncation */
8
- MAX_BYTES: 50 * 1024,
9
5
  /** Maximum map size in bytes */
10
6
  MAX_MAP_BYTES: 25 * 1024,
11
7
  /** Target size for full detail */
@@ -30,37 +30,14 @@ function formatNumber(n: number): string {
30
30
  /**
31
31
  * Format a symbol for display.
32
32
  */
33
- function formatSymbol(
34
- symbol: FileSymbol,
35
- level: DetailLevel,
36
- indent = 0
37
- ): string {
33
+ function formatSymbol(symbol: FileSymbol, indent = 0): string {
38
34
  const prefix = " ".repeat(indent);
39
35
  const lineRange =
40
36
  symbol.startLine === symbol.endLine
41
37
  ? `[${symbol.startLine}]`
42
38
  : `[${symbol.startLine}-${symbol.endLine}]`;
43
39
 
44
- let { name } = symbol;
45
-
46
- if (level === DetailLevel.Full) {
47
- if (symbol.signature) {
48
- // Check whether the signature already contains the symbol name.
49
- // Full-declaration signatures (e.g. Rust "pub fn foo(x: i32) -> bool")
50
- // include the name; partial signatures (e.g. Python "(x, y) -> None")
51
- // do not and should be appended.
52
- if (symbol.signature.includes(name)) {
53
- name = symbol.signature;
54
- } else {
55
- if (symbol.modifiers?.length) {
56
- name = `${symbol.modifiers.join(" ")} ${name}`;
57
- }
58
- name = `${name}${symbol.signature}`;
59
- }
60
- } else if (symbol.modifiers?.length) {
61
- name = `${symbol.modifiers.join(" ")} ${name}`;
62
- }
63
- }
40
+ const { name } = symbol;
64
41
 
65
42
  // Format based on kind
66
43
  let formatted: string;
@@ -88,11 +65,6 @@ function formatSymbol(
88
65
  }
89
66
  }
90
67
 
91
- // Append docstring at Full detail level
92
- if (level === DetailLevel.Full && symbol.docstring) {
93
- formatted += ` — ${symbol.docstring}`;
94
- }
95
-
96
68
  return formatted;
97
69
  }
98
70
 
@@ -107,7 +79,7 @@ function formatSymbols(
107
79
  const lines: string[] = [];
108
80
 
109
81
  for (const symbol of symbols) {
110
- lines.push(formatSymbol(symbol, level, indent));
82
+ lines.push(formatSymbol(symbol, indent));
111
83
 
112
84
  // Add children for full, compact, and minimal levels (not outline or truncated)
113
85
  if (
@@ -118,7 +90,7 @@ function formatSymbols(
118
90
  // For minimal, flatten children
119
91
  if (level === DetailLevel.Minimal) {
120
92
  for (const child of symbol.children) {
121
- lines.push(formatSymbol(child, level, indent + 1));
93
+ lines.push(formatSymbol(child, indent + 1));
122
94
  }
123
95
  } else {
124
96
  lines.push(...formatSymbols(symbol.children, level, indent + 1));
@@ -132,7 +104,7 @@ function formatSymbols(
132
104
  /**
133
105
  * Format a complete file map to a string.
134
106
  */
135
- export function formatFileMap(map: FileMap, level?: DetailLevel): string {
107
+ function formatFileMap(map: FileMap, level?: DetailLevel): string {
136
108
  const effectiveLevel = level ?? map.detailLevel;
137
109
  const fileName = basename(map.path);
138
110
 
@@ -165,20 +137,6 @@ export function formatFileMap(map: FileMap, level?: DetailLevel): string {
165
137
  lines.push("");
166
138
  }
167
139
 
168
- // Add imports if present and not outline or truncated level
169
- if (
170
- effectiveLevel !== DetailLevel.Outline &&
171
- effectiveLevel !== DetailLevel.Truncated &&
172
- map.imports.length > 0
173
- ) {
174
- const importList =
175
- map.imports.length > 10
176
- ? [...map.imports.slice(0, 10), `...${map.imports.length - 10} more`]
177
- : map.imports;
178
- lines.push(`imports: ${importList.join(", ")}`);
179
- lines.push("");
180
- }
181
-
182
140
  // Add symbols
183
141
  if (map.truncatedInfo) {
184
142
  // Truncated format: first half, separator, second half
@@ -236,13 +194,12 @@ export function formatFileMap(map: FileMap, level?: DetailLevel): string {
236
194
  /**
237
195
  * Reduce detail level of a file map.
238
196
  */
239
- export function reduceToLevel(map: FileMap, level: DetailLevel): FileMap {
197
+ function reduceToLevel(map: FileMap, level: DetailLevel): FileMap {
240
198
  if (level === DetailLevel.Outline) {
241
- // Remove all children and signatures
199
+ // Remove all children
242
200
  return {
243
201
  ...map,
244
202
  detailLevel: DetailLevel.Outline,
245
- imports: [],
246
203
  symbols: map.symbols.map((s) => ({
247
204
  name: s.name,
248
205
  kind: s.kind,
@@ -253,7 +210,6 @@ export function reduceToLevel(map: FileMap, level: DetailLevel): FileMap {
253
210
  }
254
211
 
255
212
  if (level === DetailLevel.Minimal) {
256
- // Remove signatures and docstrings but keep children flattened
257
213
  return {
258
214
  ...map,
259
215
  detailLevel: DetailLevel.Minimal,
@@ -262,48 +218,28 @@ export function reduceToLevel(map: FileMap, level: DetailLevel): FileMap {
262
218
  kind: s.kind,
263
219
  startLine: s.startLine,
264
220
  endLine: s.endLine,
265
- isExported: s.isExported,
266
221
  children: s.children?.map((c) => ({
267
222
  name: c.name,
268
223
  kind: c.kind,
269
224
  startLine: c.startLine,
270
225
  endLine: c.endLine,
271
- isExported: c.isExported,
272
226
  })),
273
227
  })),
274
228
  };
275
229
  }
276
230
 
277
231
  if (level === DetailLevel.Compact) {
278
- // Remove signatures but keep structure
279
- return {
280
- ...map,
281
- detailLevel: DetailLevel.Compact,
282
- symbols: map.symbols.map((s) => stripSignatures(s)),
283
- };
232
+ return { ...map, detailLevel: DetailLevel.Compact };
284
233
  }
285
234
 
286
235
  return { ...map, detailLevel: DetailLevel.Full };
287
236
  }
288
237
 
289
- function stripSignatures(symbol: FileSymbol): FileSymbol {
290
- return {
291
- name: symbol.name,
292
- kind: symbol.kind,
293
- startLine: symbol.startLine,
294
- endLine: symbol.endLine,
295
- modifiers: symbol.modifiers,
296
- docstring: symbol.docstring,
297
- isExported: symbol.isExported,
298
- children: symbol.children?.map(stripSignatures),
299
- };
300
- }
301
-
302
238
  /**
303
239
  * Reduce a file map to truncated form: first N + last N symbols only.
304
240
  * Used when even Outline level exceeds budget.
305
241
  */
306
- export function reduceToTruncated(
242
+ function reduceToTruncated(
307
243
  map: FileMap,
308
244
  symbolsEach: number = THRESHOLDS.TRUNCATED_SYMBOLS_EACH
309
245
  ): FileMap {
@@ -333,7 +269,6 @@ export function reduceToTruncated(
333
269
  ...map,
334
270
  symbols: [...firstSymbols, ...lastSymbols],
335
271
  detailLevel: DetailLevel.Truncated,
336
- imports: [],
337
272
  truncatedInfo: {
338
273
  totalSymbols: total,
339
274
  shownSymbols: symbolsEach * 2,
@@ -9,7 +9,7 @@ export interface SymbolMatch {
9
9
  parentName?: string;
10
10
  }
11
11
 
12
- export type SymbolLookupResult =
12
+ type SymbolLookupResult =
13
13
  | { type: "found"; symbol: SymbolMatch }
14
14
  | { type: "ambiguous"; candidates: SymbolMatch[] }
15
15
  | {
@@ -88,22 +88,6 @@ function preferJavaTopLevelType(map: FileMap, candidates: SymbolCandidate[]): Sy
88
88
  return topLevelTypes.length === 1 ? topLevelTypes[0] : undefined;
89
89
  }
90
90
 
91
- function javaPackageName(map: FileMap): string | undefined {
92
- if (map.language !== "Java") return undefined;
93
- for (const entry of map.imports) {
94
- const match = /^package\s+([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*;?$/.exec(entry);
95
- if (match) return match[1];
96
- }
97
- return undefined;
98
- }
99
-
100
- function stripJavaPackagePrefix(map: FileMap, query: string): string | undefined {
101
- const packageName = javaPackageName(map);
102
- if (!packageName) return undefined;
103
- const prefix = `${packageName}.`;
104
- return query.startsWith(prefix) ? query.slice(prefix.length) : undefined;
105
- }
106
-
107
91
  function resolveDotPathSuffix(symbols: FileSymbol[], parts: string[]): SymbolCandidate[] {
108
92
  for (let start = 1; start < parts.length; start++) {
109
93
  const candidates = resolveDotPath(symbols, parts.slice(start));
@@ -118,7 +102,6 @@ export function findSymbol(map: FileMap, query: string): SymbolLookupResult {
118
102
  if (map.symbols.length === 0) return { type: "not-found" };
119
103
 
120
104
  const allSymbols = flattenSymbols(map.symbols);
121
- const javaRelativeQuery = stripJavaPackagePrefix(map, q);
122
105
 
123
106
  const atLineMatch = /^(.+?)@(\d+)$/.exec(q);
124
107
  if (atLineMatch) {
@@ -189,16 +172,6 @@ export function findSymbol(map: FileMap, query: string): SymbolLookupResult {
189
172
  return { type: "ambiguous", candidates: toMatches(exact.slice(0, 5)) };
190
173
  }
191
174
 
192
- if (javaRelativeQuery) {
193
- const javaExact = allSymbols.filter((c) => c.symbol.name === javaRelativeQuery);
194
- if (javaExact.length === 1) return { type: "found", symbol: toMatch(javaExact[0].symbol, javaExact[0].parentName) };
195
- if (javaExact.length > 1) {
196
- const preferred = preferJavaTopLevelType(map, javaExact);
197
- if (preferred) return { type: "found", symbol: toMatch(preferred.symbol, preferred.parentName) };
198
- return { type: "ambiguous", candidates: toMatches(javaExact.slice(0, 5)) };
199
- }
200
- }
201
-
202
175
  if (q.includes(".")) {
203
176
  const parts = q.split(".").map((p) => p.trim());
204
177
  if (parts.length >= 2 && parts.every((p) => p.length > 0)) {
@@ -212,21 +185,6 @@ export function findSymbol(map: FileMap, query: string): SymbolLookupResult {
212
185
  candidates: toMatches(candidates.slice(0, 5)),
213
186
  };
214
187
  }
215
- const javaParts = javaRelativeQuery?.includes(".")
216
- ? javaRelativeQuery.split(".").map((p) => p.trim())
217
- : [];
218
- if (javaParts.length >= 2 && javaParts.every((p) => p.length > 0)) {
219
- const javaCandidates = resolveDotPath(map.symbols, javaParts);
220
- if (javaCandidates.length === 1) {
221
- return { type: "found", symbol: toMatch(javaCandidates[0].symbol, javaCandidates[0].parentName) };
222
- }
223
- if (javaCandidates.length > 1) {
224
- return {
225
- type: "ambiguous",
226
- candidates: toMatches(javaCandidates.slice(0, 5)),
227
- };
228
- }
229
- }
230
188
  const suffixCandidates = resolveDotPathSuffix(map.symbols, parts);
231
189
  if (suffixCandidates.length === 1) {
232
190
  return { type: "found", symbol: toMatch(suffixCandidates[0].symbol, suffixCandidates[0].parentName) };
@@ -12,16 +12,8 @@ export interface FileSymbol {
12
12
  startLine: number;
13
13
  /** Ending line number (1-indexed) */
14
14
  endLine: number;
15
- /** Optional signature (for functions/methods) */
16
- signature?: string;
17
15
  /** Child symbols (for nested structures like methods in classes) */
18
16
  children?: FileSymbol[];
19
- /** Additional modifiers (async, static, etc.) */
20
- modifiers?: string[];
21
- /** First line of JSDoc / docstring (if present) */
22
- docstring?: string;
23
- /** Whether this symbol is exported from its module */
24
- isExported?: boolean;
25
17
  }
26
18
 
27
19
  /**
@@ -50,20 +42,8 @@ export interface FileMap {
50
42
  language: string;
51
43
  /** Extracted symbols */
52
44
  symbols: FileSymbol[];
53
- /** Module imports / dependencies */
54
- imports: string[];
55
45
  /** Detail level used for this map */
56
46
  detailLevel: DetailLevel;
57
47
  /** Truncation metadata (present when symbols are truncated) */
58
48
  truncatedInfo?: TruncatedInfo;
59
49
  }
60
-
61
- /**
62
- * Language information.
63
- */
64
- export interface LanguageInfo {
65
- /** Language identifier */
66
- id: string;
67
- /** Display name */
68
- name: string;
69
- }