pi-readseek 0.4.12 → 0.4.14
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 +1 -1
- package/package.json +2 -2
- package/src/edit-output.ts +3 -3
- package/src/edit-syntax-validate.ts +5 -5
- package/src/edit.ts +38 -6
- package/src/file-map.ts +17 -0
- package/src/grep-budget.ts +3 -3
- package/src/grep-output.ts +8 -8
- package/src/grep-render-helpers.ts +3 -3
- package/src/grep-symbol-scope.ts +4 -4
- package/src/grep.ts +7 -7
- package/src/hashline.ts +5 -5
- package/src/pending-diff-preview.ts +3 -3
- package/src/read-output.ts +21 -21
- package/src/read-render-helpers.ts +2 -2
- package/src/read.ts +43 -31
- package/src/readseek/constants.ts +0 -4
- package/src/readseek/formatter.ts +9 -74
- package/src/readseek/symbol-lookup.ts +1 -43
- package/src/readseek/types.ts +0 -20
- package/src/readseek-client.ts +165 -69
- package/src/readseek-settings.ts +17 -17
- package/src/readseek-value.ts +27 -27
- package/src/refs-output.ts +4 -4
- package/src/refs.ts +10 -10
- package/src/register-tool.ts +10 -10
- package/src/replace-symbol.ts +4 -3
- package/src/sg-output.ts +5 -5
- package/src/sg.ts +12 -12
- package/src/tool-prompt-metadata.ts +5 -5
- package/src/write.ts +18 -33
- package/src/image-detect.ts +0 -47
- package/src/map-cache.ts +0 -82
package/src/read.ts
CHANGED
|
@@ -13,12 +13,11 @@ 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 {
|
|
16
|
+
import { buildReadSeekWarning, buildToolErrorResult, renderReadSeekLines, type ReadSeekLine, type ReadSeekWarning } from "./readseek-value.js";
|
|
17
17
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
18
|
-
import { isSupportedImageBuffer } from "./image-detect.js";
|
|
19
18
|
import { resolveToCwd } from "./path-utils.js";
|
|
20
19
|
import { throwIfAborted } from "./runtime.js";
|
|
21
|
-
import { getOrGenerateMap } from "./map
|
|
20
|
+
import { getOrGenerateMap } from "./file-map.js";
|
|
22
21
|
import { formatFileMapWithBudget } from "./readseek/formatter.js";
|
|
23
22
|
import { findSymbol, type SymbolMatch } from "./readseek/symbol-lookup.js";
|
|
24
23
|
import { formatAmbiguous, formatNotFound } from "./readseek/symbol-error-format.js";
|
|
@@ -26,15 +25,15 @@ import { buildReadOutput } from "./read-output.js";
|
|
|
26
25
|
|
|
27
26
|
import { buildLocalBundle } from "./read-local-bundle.js";
|
|
28
27
|
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
29
|
-
import { readseekRead } from "./readseek-client.js";
|
|
28
|
+
import { readseekRead, readseekDetect, type ReadSeekDetection } from "./readseek-client.js";
|
|
30
29
|
import { formatReadCallText, formatReadResultText } from "./read-render-helpers.js";
|
|
31
30
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine, wrapReadHashlinesForWidth } from "./tui-render-utils.js";
|
|
32
31
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
33
|
-
import {
|
|
32
|
+
import { registerReadSeekTool } from "./register-tool.js";
|
|
34
33
|
|
|
35
34
|
const READ_PROMPT_METADATA = defineToolPromptMetadata({
|
|
36
35
|
promptUrl: new URL("../prompts/read.md", import.meta.url),
|
|
37
|
-
promptSnippet: "Read text files or images; text reads include hashline anchors and optional maps/symbol lookup",
|
|
36
|
+
promptSnippet: "Read text files or images; text reads include hashline anchors and optional maps/symbol lookup, image reads include the attachment plus OCR text",
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
interface ReadParams {
|
|
@@ -138,15 +137,8 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
138
137
|
const message = "Cannot combine map with symbol. Use one or the other.";
|
|
139
138
|
return buildToolErrorResult("read", "invalid-params-combo", message, { path: rawParams.path });
|
|
140
139
|
}
|
|
141
|
-
// Delegate images to the built-in read tool
|
|
142
140
|
throwIfAborted(signal);
|
|
143
141
|
const ext = rawPath.split(".").pop()?.toLowerCase() ?? "";
|
|
144
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) {
|
|
145
|
-
const builtinRead = createReadTool(cwd);
|
|
146
|
-
return succeed(await builtinRead.execute(toolCallId, p, signal, onUpdate));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
throwIfAborted(signal);
|
|
150
142
|
let rawBuffer: Buffer;
|
|
151
143
|
try {
|
|
152
144
|
rawBuffer = await fsReadFile(absolutePath);
|
|
@@ -168,17 +160,37 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
168
160
|
return buildToolErrorResult("read", "fs-error", message, { path: rawParams.path, details: { fsCode: code, fsMessage: err?.message } });
|
|
169
161
|
}
|
|
170
162
|
|
|
171
|
-
if (isSupportedImageBuffer(rawBuffer)) {
|
|
172
|
-
const builtinRead = createReadTool(cwd);
|
|
173
|
-
return succeed(await builtinRead.execute(toolCallId, p, signal, onUpdate));
|
|
174
|
-
}
|
|
175
163
|
const hasBinaryContent = looksLikeBinary(rawBuffer);
|
|
164
|
+
if (hasBinaryContent) {
|
|
165
|
+
// Images are always binary; classify and OCR in a single readseek detect call.
|
|
166
|
+
let detection: ReadSeekDetection | undefined;
|
|
167
|
+
try {
|
|
168
|
+
detection = await readseekDetect(absolutePath, { ocr: true, signal });
|
|
169
|
+
} catch {
|
|
170
|
+
// detect unavailable — fall through to binary-as-text handling below
|
|
171
|
+
}
|
|
172
|
+
if (detection?.image) {
|
|
173
|
+
const builtinRead = createReadTool(cwd);
|
|
174
|
+
const builtinResult = await builtinRead.execute(toolCallId, p, signal, onUpdate);
|
|
175
|
+
const ocrText = detection.ocr?.text?.trim();
|
|
176
|
+
if (ocrText) {
|
|
177
|
+
return succeed({
|
|
178
|
+
...builtinResult,
|
|
179
|
+
content: [
|
|
180
|
+
...(builtinResult.content ?? []),
|
|
181
|
+
{ type: "text" as const, text: `OCR text extracted from image:\n${ocrText}` },
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return succeed(builtinResult);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
176
188
|
throwIfAborted(signal);
|
|
177
189
|
const rawText = rawBuffer.toString("utf-8");
|
|
178
190
|
const normalized = normalizeToLF(stripBom(rawText).text);
|
|
179
|
-
const allLines =
|
|
191
|
+
const allLines = splitReadSeekLines(normalized);
|
|
180
192
|
const total = allLines.length;
|
|
181
|
-
const structuredWarnings:
|
|
193
|
+
const structuredWarnings: ReadSeekWarning[] = [];
|
|
182
194
|
let startLine = p.offset !== undefined ? p.offset : 1;
|
|
183
195
|
let endIdx = p.limit !== undefined ? Math.min(startLine - 1 + p.limit, total) : total;
|
|
184
196
|
if (p.offset !== undefined && startLine > total) {
|
|
@@ -203,7 +215,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
203
215
|
};
|
|
204
216
|
lines: string[];
|
|
205
217
|
}>;
|
|
206
|
-
warnings:
|
|
218
|
+
warnings: ReadSeekWarning[];
|
|
207
219
|
}
|
|
208
220
|
| null = null;
|
|
209
221
|
if (p.symbol) {
|
|
@@ -248,7 +260,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
248
260
|
lines.push(` To confirm: ${confirmHint}.]`);
|
|
249
261
|
const bannerText = lines.join("\n");
|
|
250
262
|
structuredWarnings.push(
|
|
251
|
-
|
|
263
|
+
buildReadSeekWarning("fuzzy-symbol-match", bannerText, {
|
|
252
264
|
tier: lookup.tier,
|
|
253
265
|
symbol: lookup.symbol,
|
|
254
266
|
otherCandidates: lookup.otherCandidates,
|
|
@@ -261,7 +273,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
261
273
|
if (p.bundle === "local") {
|
|
262
274
|
if (!symbolFileMap) {
|
|
263
275
|
const extLabel = ext || "unknown";
|
|
264
|
-
const warning =
|
|
276
|
+
const warning = buildReadSeekWarning(
|
|
265
277
|
"bundle-unmappable",
|
|
266
278
|
`[Warning: local bundle unavailable because symbol mapping is not available for .${extLabel} files — showing plain symbol read]`,
|
|
267
279
|
);
|
|
@@ -282,7 +294,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
282
294
|
} else {
|
|
283
295
|
const bundle = buildLocalBundle(symbolFileMap, symbolMatch, allLines);
|
|
284
296
|
if (!bundle) {
|
|
285
|
-
const warning =
|
|
297
|
+
const warning = buildReadSeekWarning(
|
|
286
298
|
"bundle-context-unavailable",
|
|
287
299
|
`[Warning: local bundle context could not be determined for symbol '${symbolMatch.name}' — showing plain symbol read]`,
|
|
288
300
|
);
|
|
@@ -333,7 +345,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
333
345
|
: `readseek returned ${readseekOutput.hashlines.length} lines for requested range ${startLine}-${endIdx} (${expectedLineCount} expected)`;
|
|
334
346
|
return buildToolErrorResult("read", "readseek-output-mismatch", message, { path: rawParams.path });
|
|
335
347
|
}
|
|
336
|
-
const readseekLines:
|
|
348
|
+
const readseekLines: ReadSeekLine[] = readseekOutput.hashlines.map((line) => ({
|
|
337
349
|
line: line.line,
|
|
338
350
|
hash: line.hash,
|
|
339
351
|
anchor: `${line.line}:${line.hash}`,
|
|
@@ -341,7 +353,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
341
353
|
display: escapeControlCharsForDisplay(line.text),
|
|
342
354
|
}));
|
|
343
355
|
const selected = readseekLines.map((line) => line.raw);
|
|
344
|
-
const formatted =
|
|
356
|
+
const formatted = renderReadSeekLines(readseekLines);
|
|
345
357
|
|
|
346
358
|
const truncation = truncateHead(formatted, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
347
359
|
|
|
@@ -363,17 +375,17 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
363
375
|
}
|
|
364
376
|
|
|
365
377
|
if (symbolWarning) {
|
|
366
|
-
structuredWarnings.push(
|
|
378
|
+
structuredWarnings.push(buildReadSeekWarning("symbol-warning", symbolWarning.trim()));
|
|
367
379
|
}
|
|
368
380
|
|
|
369
381
|
if (hasBinaryContent) {
|
|
370
382
|
const warning = "[Warning: file appears to be binary — output may be garbled]";
|
|
371
|
-
structuredWarnings.push(
|
|
383
|
+
structuredWarnings.push(buildReadSeekWarning("binary-content", warning));
|
|
372
384
|
}
|
|
373
385
|
|
|
374
386
|
if (hasBareCarriageReturn(rawText)) {
|
|
375
387
|
const warning = "[Warning: file contains bare CR (\\r) line endings — line numbering may be inconsistent with grep and other tools]";
|
|
376
|
-
structuredWarnings.push(
|
|
388
|
+
structuredWarnings.push(buildReadSeekWarning("bare-cr", warning));
|
|
377
389
|
}
|
|
378
390
|
|
|
379
391
|
const readOutput = buildReadOutput({
|
|
@@ -420,14 +432,14 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
420
432
|
});
|
|
421
433
|
}
|
|
422
434
|
|
|
423
|
-
function
|
|
435
|
+
function splitReadSeekLines(text: string): string[] {
|
|
424
436
|
if (text.length === 0) return [];
|
|
425
437
|
const withoutTrailingTerminator = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
426
438
|
return withoutTrailingTerminator.split("\n");
|
|
427
439
|
}
|
|
428
440
|
|
|
429
441
|
export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}) {
|
|
430
|
-
const tool =
|
|
442
|
+
const tool = registerReadSeekTool(pi, {
|
|
431
443
|
policy: "read-only",
|
|
432
444
|
pythonName: "read",
|
|
433
445
|
defaultExposure: "safe-by-default",
|
|
@@ -497,7 +509,7 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
497
509
|
return new Text(clampLinesToWidth([summaryLine(errorText)], width).join("\n"), 0, 0);
|
|
498
510
|
}
|
|
499
511
|
|
|
500
|
-
const readseekValue = (result.details as any)?.readseekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings:
|
|
512
|
+
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
513
|
if (!readseekValue) {
|
|
502
514
|
const lines = textContent.split("\n").filter(Boolean).length || textContent.split("\n").length;
|
|
503
515
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
197
|
+
function reduceToLevel(map: FileMap, level: DetailLevel): FileMap {
|
|
240
198
|
if (level === DetailLevel.Outline) {
|
|
241
|
-
// Remove all children
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) };
|
package/src/readseek/types.ts
CHANGED
|
@@ -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
|
-
}
|