pi-readseek 0.7.7 → 0.7.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.
Files changed (60) hide show
  1. package/README.md +28 -46
  2. package/dist/index.js +7333 -0
  3. package/package.json +8 -7
  4. package/index.ts +0 -147
  5. package/src/binary-detect.ts +0 -22
  6. package/src/check.ts +0 -92
  7. package/src/coerce-obvious-int.ts +0 -41
  8. package/src/confusable-hyphens.ts +0 -6
  9. package/src/def.ts +0 -143
  10. package/src/diff-data.ts +0 -302
  11. package/src/edit-classify.ts +0 -58
  12. package/src/edit-diff.ts +0 -359
  13. package/src/edit-output.ts +0 -110
  14. package/src/edit-render-helpers.ts +0 -141
  15. package/src/edit-syntax-validate.ts +0 -65
  16. package/src/edit.ts +0 -686
  17. package/src/file-map.ts +0 -17
  18. package/src/fs-error.ts +0 -33
  19. package/src/grep-budget.ts +0 -35
  20. package/src/grep-output.ts +0 -181
  21. package/src/grep-render-helpers.ts +0 -77
  22. package/src/grep-symbol-scope.ts +0 -184
  23. package/src/grep.ts +0 -608
  24. package/src/hashline.ts +0 -736
  25. package/src/hover.ts +0 -140
  26. package/src/path-utils.ts +0 -24
  27. package/src/pending-diff-preview.ts +0 -269
  28. package/src/read-local-bundle.ts +0 -75
  29. package/src/read-output.ts +0 -186
  30. package/src/read-render-helpers.ts +0 -104
  31. package/src/read.ts +0 -661
  32. package/src/readseek/constants.ts +0 -17
  33. package/src/readseek/enums.ts +0 -38
  34. package/src/readseek/formatter.ts +0 -350
  35. package/src/readseek/symbol-error-format.ts +0 -18
  36. package/src/readseek/symbol-lookup.ts +0 -240
  37. package/src/readseek/symbol-tree.ts +0 -21
  38. package/src/readseek/types.ts +0 -49
  39. package/src/readseek-client.ts +0 -1250
  40. package/src/readseek-params.ts +0 -39
  41. package/src/readseek-settings.ts +0 -263
  42. package/src/readseek-value.ts +0 -184
  43. package/src/refs-output.ts +0 -47
  44. package/src/refs.ts +0 -161
  45. package/src/register-tool.ts +0 -29
  46. package/src/rename.ts +0 -182
  47. package/src/replace-symbol.ts +0 -77
  48. package/src/runtime.ts +0 -3
  49. package/src/search-output.ts +0 -50
  50. package/src/search.ts +0 -214
  51. package/src/session-anchors.ts +0 -34
  52. package/src/stat-search-path.ts +0 -29
  53. package/src/syntax-validate-mode.ts +0 -15
  54. package/src/tool-prompt-metadata.ts +0 -107
  55. package/src/tool-types.ts +0 -12
  56. package/src/tui-diff-component.ts +0 -108
  57. package/src/tui-diff-renderer.ts +0 -92
  58. package/src/tui-render-utils.ts +0 -286
  59. package/src/view.ts +0 -158
  60. package/src/write.ts +0 -357
@@ -1,1250 +0,0 @@
1
- import { spawn, type StdioOptions } from "node:child_process";
2
- import { existsSync, readFileSync, statSync } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import { homedir } from "node:os";
5
- import path from "node:path";
6
-
7
- import { DetailLevel } from "./readseek/enums.js";
8
- import type { FileMap, FileSymbol } from "./readseek/types.js";
9
- import { SymbolKind } from "./readseek/enums.js";
10
- import { resolveReadSeekTimeoutMs } from "./readseek-settings.js";
11
-
12
- export interface ReadSeekHashline {
13
- line: number;
14
- hash: string;
15
- text: string;
16
- }
17
-
18
- interface ReadSeekReadOutput {
19
- file: string;
20
- language: string;
21
- line_count: number;
22
- file_hash: string;
23
- start_line: number;
24
- end_line: number;
25
- hashlines: ReadSeekHashline[];
26
- }
27
-
28
- interface ReadSeekSymbol {
29
- kind: string;
30
- name: string;
31
- qualified_name: string;
32
- start_line: number;
33
- end_line: number;
34
- start_hash: string;
35
- end_hash: string;
36
- }
37
-
38
- interface ReadSeekMapOutput {
39
- file: string;
40
- language: string;
41
- line_count: number;
42
- file_hash: string;
43
- symbols: ReadSeekSymbol[];
44
- }
45
-
46
- interface ReadSeekSearchCapture {
47
- name: string;
48
- start_line: number;
49
- end_line: number;
50
- start_hash: string;
51
- end_hash: string;
52
- hashlines: ReadSeekHashline[];
53
- }
54
-
55
- interface ReadSeekSearchMatch {
56
- pattern_index: number;
57
- start_line: number;
58
- end_line: number;
59
- start_hash: string;
60
- end_hash: string;
61
- hashlines: ReadSeekHashline[];
62
- captures: ReadSeekSearchCapture[];
63
- }
64
-
65
- export interface ReadSeekSearchFileOutput {
66
- file: string;
67
- language: string;
68
- file_hash: string;
69
- matches: ReadSeekSearchMatch[];
70
- }
71
-
72
- interface ReadSeekSearchOutput {
73
- results: ReadSeekSearchFileOutput[];
74
- }
75
-
76
- export interface ReadSeekReference {
77
- file: string;
78
- line: number;
79
- column: number;
80
- line_hash: string;
81
- text: string;
82
- enclosingSymbol?: string;
83
- }
84
-
85
- interface ReadSeekRefsOutput {
86
- references: ReadSeekReference[];
87
- }
88
-
89
- interface ReadSeekRefsOptions {
90
- scope?: boolean;
91
- line?: number;
92
- column?: number;
93
- language?: string;
94
- cached?: boolean;
95
- others?: boolean;
96
- ignored?: boolean;
97
- signal?: AbortSignal;
98
- }
99
-
100
- export interface ReadSeekDiagnostic {
101
- kind: "error" | "missing";
102
- start_line: number;
103
- end_line: number;
104
- }
105
-
106
- export interface ReadSeekCheckOutput {
107
- errorCount: number;
108
- missingCount: number;
109
- diagnostics: ReadSeekDiagnostic[];
110
- }
111
-
112
- export type ReadSeekOcrText = string;
113
-
114
- export interface ReadSeekDetectedObject {
115
- label: string;
116
- bbox: [number, number, number, number];
117
- }
118
-
119
- export type ReadSeekImageMode = "all" | "ocr" | "caption" | "objects";
120
-
121
- export interface ReadSeekPreparedImage {
122
- mime: string;
123
- encoding: "base64";
124
- data: string;
125
- }
126
-
127
- export interface ReadSeekPdfImage {
128
- page: number;
129
- width: number;
130
- height: number;
131
- mime: string;
132
- mode: "none" | ReadSeekImageMode;
133
- encoding?: "base64";
134
- data?: string;
135
- ocr?: ReadSeekOcrText;
136
- caption?: string;
137
- objects?: ReadSeekDetectedObject[];
138
- }
139
-
140
- export interface ReadSeekPdfOutput {
141
- format: "pdf";
142
- pages: number;
143
- markdown: string;
144
- images: ReadSeekPdfImage[];
145
- }
146
-
147
- export type ReadSeekDetection =
148
- | {
149
- kind: "source";
150
- type: string;
151
- file: string;
152
- language: string;
153
- engine?: string;
154
- supported: boolean;
155
- mime?: string;
156
- syntax?: string;
157
- }
158
- | {
159
- kind: "image";
160
- type: string;
161
- file: string;
162
- mime?: string;
163
- format: string;
164
- width: number;
165
- height: number;
166
- animated: boolean;
167
- encoding?: "base64";
168
- data?: string;
169
- ocr?: ReadSeekOcrText;
170
- caption?: string;
171
- objects?: ReadSeekDetectedObject[];
172
- }
173
- | {
174
- kind: "text";
175
- type: string;
176
- file: string;
177
- mime?: string;
178
- }
179
- | {
180
- kind: "pdf";
181
- type: "application/pdf";
182
- file: string;
183
- mime?: string;
184
- format: "pdf";
185
- pages: number;
186
- };
187
-
188
- type ReadSeekImageDetection = Extract<ReadSeekDetection, { kind: "image" }>;
189
-
190
- interface ReadSeekSearchOptions {
191
- language?: string;
192
- cached?: boolean;
193
- others?: boolean;
194
- ignored?: boolean;
195
- signal?: AbortSignal;
196
- }
197
-
198
- function normalizeLanguage(language: string): string {
199
- return language === "java" ? "Java" : language;
200
- }
201
-
202
- function normalizeKind(kind: string): FileSymbol["kind"] {
203
- if (kind === "constructor") return SymbolKind.Method;
204
- if (Object.values(SymbolKind).includes(kind as SymbolKind)) return kind as FileSymbol["kind"];
205
- return SymbolKind.Unknown;
206
- }
207
-
208
- function parentQualifiedNameFor(qualifiedName: string): string {
209
- const lastDot = qualifiedName.lastIndexOf(".");
210
- return lastDot === -1 ? "" : qualifiedName.slice(0, lastDot);
211
- }
212
-
213
- function symbolsFromReadSeek(symbols: ReadSeekSymbol[]): FileSymbol[] {
214
- const symbolsByQualifiedName = new Map<string, FileSymbol[]>();
215
- const entries: Array<{ parentQualifiedName: string; symbol: FileSymbol }> = [];
216
-
217
- for (const symbol of symbols) {
218
- const parentQualifiedName = parentQualifiedNameFor(symbol.qualified_name);
219
- const fileSymbol: FileSymbol = {
220
- name: symbol.name,
221
- kind: normalizeKind(symbol.kind),
222
- startLine: symbol.start_line,
223
- endLine: symbol.end_line,
224
- };
225
- const bucket = symbolsByQualifiedName.get(symbol.qualified_name);
226
- if (bucket) bucket.push(fileSymbol);
227
- else symbolsByQualifiedName.set(symbol.qualified_name, [fileSymbol]);
228
- entries.push({ parentQualifiedName, symbol: fileSymbol });
229
- }
230
-
231
- const roots: FileSymbol[] = [];
232
- for (const entry of entries) {
233
- const parent = entry.parentQualifiedName
234
- ? symbolsByQualifiedName.get(entry.parentQualifiedName)?.[0]
235
- : undefined;
236
- if (!parent) {
237
- roots.push(entry.symbol);
238
- continue;
239
- }
240
-
241
- parent.children ??= [];
242
- parent.children.push(entry.symbol);
243
- }
244
-
245
- return roots;
246
- }
247
-
248
- const require = createRequire(import.meta.url);
249
- const READSEEK_REPO_PACKAGE_NAMES = new Set(["@jarkkojs/readseek", "readseek"]);
250
- let defaultReadSeekDirInit: Promise<string | null> | undefined;
251
-
252
- function readSeekPackageDir(): string {
253
- return path.dirname(require.resolve("@jarkkojs/readseek/package.json"));
254
- }
255
-
256
- const READSEEK_PLATFORM_PACKAGES: Record<string, string> = {
257
- "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
258
- "linux-arm64": "@jarkkojs/readseek-linux-arm64",
259
- "linux-x64": "@jarkkojs/readseek-linux-x64",
260
- "win32-x64": "@jarkkojs/readseek-win32-x64",
261
- };
262
-
263
- function readSeekPlatform(): string {
264
- return `${process.platform}-${process.arch}`;
265
- }
266
-
267
- function readSeekBinaryPath(): string {
268
- const platform = readSeekPlatform();
269
- const platformPackage = READSEEK_PLATFORM_PACKAGES[platform];
270
- if (!platformPackage) {
271
- const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
272
- throw new Error(`@jarkkojs/readseek ships no binary for ${platform}; it supports ${supported}`);
273
- }
274
-
275
- const packageJson = require.resolve(`${platformPackage}/package.json`, { paths: [readSeekPackageDir()] });
276
- return path.join(path.dirname(packageJson), "bin", process.platform === "win32" ? "readseek.exe" : "readseek");
277
- }
278
-
279
- /**
280
- * Report whether a readseek binary can be resolved for the running platform,
281
- * along with the reason when it cannot. Used to keep the readseek tools out of
282
- * the active set on hosts that readseek publishes no binary for.
283
- */
284
- export function readSeekBinaryAvailability(): { available: true } | { available: false; reason: string } {
285
- try {
286
- readSeekBinaryPath();
287
- return { available: true };
288
- } catch (err) {
289
- return { available: false, reason: classifyReadSeekFailure(err).message };
290
- }
291
- }
292
-
293
- interface ReadSeekFailure {
294
- code: "readseek-not-installed" | "readseek-execution-error";
295
- message: string;
296
- hint?: string;
297
- }
298
-
299
- /**
300
- * Classify an error thrown while invoking readseek into the shared failure
301
- * taxonomy: a missing binary or package (`readseek-not-installed`, with an
302
- * install hint) versus any other execution error.
303
- */
304
- export function classifyReadSeekFailure(err: unknown): ReadSeekFailure {
305
- const message = String((err as { message?: unknown } | null)?.message || err);
306
- const missing =
307
- (err as { code?: unknown } | null)?.code === "ENOENT" ||
308
- /Cannot find package|Cannot find module|no such file/i.test(message);
309
- if (missing) {
310
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
311
- }
312
- return { code: "readseek-execution-error", message };
313
- }
314
-
315
- function directoryExists(dirPath: string): boolean {
316
- try {
317
- return statSync(dirPath).isDirectory();
318
- } catch {
319
- return false;
320
- }
321
- }
322
-
323
- const ownRepositoryByDir = new Map<string, boolean>();
324
-
325
- function isOwnReadSeekRepository(cwd = process.cwd()): boolean {
326
- const start = path.resolve(cwd);
327
- const cached = ownRepositoryByDir.get(start);
328
- if (cached !== undefined) return cached;
329
-
330
- let dir = start;
331
- let result = false;
332
- while (true) {
333
- const packageJsonPath = path.join(dir, "package.json");
334
- if (existsSync(packageJsonPath)) {
335
- try {
336
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { name?: unknown };
337
- if (typeof packageJson.name === "string" && READSEEK_REPO_PACKAGE_NAMES.has(packageJson.name)) {
338
- result = true;
339
- break;
340
- }
341
- } catch {
342
- // Ignore unreadable or invalid package manifests while walking up.
343
- }
344
- }
345
-
346
- const parent = path.dirname(dir);
347
- if (parent === dir) break;
348
- dir = parent;
349
- }
350
-
351
- ownRepositoryByDir.set(start, result);
352
- return result;
353
- }
354
-
355
- function defaultReadSeekDir(): string | null {
356
- const home = homedir();
357
- return home ? path.join(home, ".pi", "readseek") : null;
358
- }
359
-
360
- const DEFAULT_READSEEK_TIMEOUT_MS = 120_000;
361
- const DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60_000;
362
-
363
- function readSeekTimeoutMs(): number {
364
- return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
365
- }
366
-
367
- const READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
368
-
369
- function readSeekErrorMessage(stderr: string): string {
370
- return stderr
371
- .replace(READSEEK_USAGE_HINT, "")
372
- .replace(/^error:\s*/i, "")
373
- .replace(/\s*\n\s*/g, " ")
374
- .trim();
375
- }
376
-
377
- async function spawnReadSeekRaw(args: string[], options: RunReadSeekOptions = {}): Promise<string> {
378
- return new Promise<string>((resolve, reject) => {
379
- let settled = false;
380
- let timeout: ReturnType<typeof setTimeout> | undefined;
381
- const fail = (error: Error): void => {
382
- if (settled) return;
383
- settled = true;
384
- if (timeout !== undefined) clearTimeout(timeout);
385
- reject(error);
386
- };
387
- const succeed = (value: string): void => {
388
- if (settled) return;
389
- settled = true;
390
- if (timeout !== undefined) clearTimeout(timeout);
391
- resolve(value);
392
- };
393
-
394
- const stdin = options.stdin;
395
- const stdio: StdioOptions = [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"];
396
- const child = spawn(readSeekBinaryPath(), args, { stdio, signal: options.signal });
397
- const timeoutMs = options.timeoutMs ?? readSeekTimeoutMs();
398
- timeout = setTimeout(() => {
399
- child.kill("SIGKILL");
400
- fail(new Error(`readseek timed out after ${timeoutMs} ms`));
401
- }, timeoutMs);
402
- timeout.unref?.();
403
- const childStdout = child.stdout;
404
- const childStderr = child.stderr;
405
- const childStdin = child.stdin;
406
- if (!childStdout || !childStderr) {
407
- child.kill();
408
- fail(new Error("readseek stdio streams are unavailable"));
409
- return;
410
- }
411
- const stdoutChunks: Buffer[] = [];
412
- const stderrChunks: Buffer[] = [];
413
- let stdoutBytes = 0;
414
-
415
- childStdout.on("data", (chunk: Buffer) => {
416
- if (settled) return;
417
- stdoutBytes += chunk.length;
418
- if (stdoutBytes > 32 * 1024 * 1024) {
419
- child.kill();
420
- fail(new Error("readseek output exceeded 32 MiB"));
421
- return;
422
- }
423
- stdoutChunks.push(chunk);
424
- });
425
- childStderr.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
426
- child.on("error", (error: any) => fail(error));
427
- if (stdin !== undefined) {
428
- if (!childStdin) {
429
- child.kill();
430
- fail(new Error("readseek stdin stream is unavailable"));
431
- return;
432
- }
433
- childStdin.on("error", (error: any) => {
434
- if (error?.code !== "EPIPE") fail(error);
435
- });
436
- childStdin.end(stdin, "utf-8");
437
- }
438
- child.on("close", (code, signal) => {
439
- const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
440
- const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
441
- if (code === 0) succeed(stdout);
442
- else if (signal) fail(new Error(readSeekErrorMessage(stderr) || `readseek killed by signal ${signal}`));
443
- else fail(new Error(readSeekErrorMessage(stderr) || `readseek exited with status ${code}`));
444
- });
445
- });
446
- }
447
-
448
- async function ensureDefaultReadSeekDir(): Promise<string | null> {
449
- const dir = defaultReadSeekDir();
450
- if (!dir) return null;
451
- if (directoryExists(dir)) return dir;
452
-
453
- defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"])
454
- .then(() => (directoryExists(dir) ? dir : null))
455
- .catch(() => null)
456
- .finally(() => {
457
- defaultReadSeekDirInit = undefined;
458
- });
459
- return defaultReadSeekDirInit;
460
- }
461
-
462
- async function readSeekInvocationArgs(args: string[]): Promise<string[]> {
463
- if (isOwnReadSeekRepository()) return args;
464
-
465
- const readSeekDir = await ensureDefaultReadSeekDir();
466
- return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : args;
467
- }
468
-
469
- interface RunReadSeekOptions {
470
- signal?: AbortSignal;
471
- stdin?: string;
472
- timeoutMs?: number;
473
- }
474
-
475
- async function runReadSeekRaw(args: string[], options: RunReadSeekOptions = {}): Promise<string> {
476
- return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
477
- }
478
-
479
- async function runReadSeek(args: string[], options: RunReadSeekOptions = {}): Promise<unknown> {
480
- const stdout = await runReadSeekRaw(args, options);
481
- return JSON.parse(stdout) as unknown;
482
- }
483
-
484
- export interface ReadSeekViewOptions {
485
- node?: string;
486
- page?: number;
487
- kind?: string;
488
- depth?: number;
489
- outline?: boolean;
490
- signal?: AbortSignal;
491
- }
492
-
493
- export async function readSeekView(filePath: string, options: ReadSeekViewOptions = {}): Promise<string> {
494
- const args = ["view", filePath];
495
- if (options.node !== undefined) args.push("--node", options.node);
496
- if (options.page !== undefined) args.push("--page", String(options.page));
497
- if (options.kind !== undefined) args.push("--kind", options.kind);
498
- if (options.depth !== undefined) args.push("--depth", String(options.depth));
499
- if (options.outline) args.push("--outline");
500
- return runReadSeekRaw(args, { signal: options.signal });
501
- }
502
-
503
- let visionInvocationTail = Promise.resolve();
504
-
505
- /**
506
- * Serialize local vision-model processes. Concurrent processes each consume all
507
- * available CPU cores, which makes every invocation exceed its own timeout.
508
- */
509
- async function runReadSeekVision(args: string[], options: RunReadSeekOptions = {}): Promise<unknown> {
510
- let release!: () => void;
511
- const gate = new Promise<void>((resolve) => {
512
- release = resolve;
513
- });
514
- const predecessor = visionInvocationTail;
515
- visionInvocationTail = predecessor.then(() => gate);
516
- const signal = options.signal;
517
-
518
- try {
519
- if (signal) {
520
- signal.throwIfAborted();
521
- await new Promise<void>((resolve, reject) => {
522
- const onAbort = (): void => {
523
- signal.removeEventListener("abort", onAbort);
524
- reject(signal.reason);
525
- };
526
- signal.addEventListener("abort", onAbort, { once: true });
527
- predecessor.then(
528
- () => {
529
- signal.removeEventListener("abort", onAbort);
530
- resolve();
531
- },
532
- (error) => {
533
- signal.removeEventListener("abort", onAbort);
534
- reject(error);
535
- },
536
- );
537
- });
538
- } else {
539
- await predecessor;
540
- }
541
- signal?.throwIfAborted();
542
- return await runReadSeek(args, {
543
- ...options,
544
- timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS,
545
- });
546
- } finally {
547
- release();
548
- }
549
- }
550
-
551
- function requireNumber(value: unknown, field: string): number {
552
- if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new Error(`invalid readseek ${field}: expected safe integer`);
553
- return value;
554
- }
555
-
556
- function requireString(value: unknown, field: string): string {
557
- if (typeof value !== "string") throw new Error(`invalid readseek ${field}`);
558
- return value;
559
- }
560
-
561
- function requireBoolean(value: unknown, field: string): boolean {
562
- if (typeof value !== "boolean") throw new Error(`invalid readseek ${field}: expected boolean`);
563
- return value;
564
- }
565
-
566
- function parseReadOutput(value: unknown): ReadSeekReadOutput {
567
- if (!value || typeof value !== "object") throw new Error("invalid readseek read output");
568
- const output = value as Record<string, unknown>;
569
- const hashlines = output.hashlines;
570
- if (!Array.isArray(hashlines)) throw new Error("invalid readseek hashlines");
571
- return {
572
- file: requireString(output.file, "file"),
573
- language: requireString(output.language, "language"),
574
- line_count: requireNumber(output.line_count, "line_count"),
575
- file_hash: requireString(output.file_hash, "file_hash"),
576
- start_line: requireNumber(output.start_line, "start_line"),
577
- end_line: requireNumber(output.end_line, "end_line"),
578
- hashlines: hashlines.map((line) => parseHashline(line, "hashline")),
579
- };
580
- }
581
-
582
- function parseMapOutput(value: unknown): ReadSeekMapOutput {
583
- if (!value || typeof value !== "object") throw new Error("invalid readseek map output");
584
- const output = value as Record<string, unknown>;
585
- const symbols = output.symbols;
586
- if (!Array.isArray(symbols)) throw new Error("invalid readseek symbols");
587
- return {
588
- file: requireString(output.file, "file"),
589
- language: requireString(output.language, "language"),
590
- line_count: requireNumber(output.line_count, "line_count"),
591
- file_hash: requireString(output.file_hash, "file_hash"),
592
- symbols: symbols.map((symbol) => {
593
- if (!symbol || typeof symbol !== "object") throw new Error("invalid readseek symbol");
594
- const item = symbol as Record<string, unknown>;
595
- return {
596
- kind: requireString(item.kind, "symbol.kind"),
597
- name: requireString(item.name, "symbol.name"),
598
- qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
599
- start_line: requireNumber(item.start_line, "symbol.start_line"),
600
- end_line: requireNumber(item.end_line, "symbol.end_line"),
601
- start_hash: requireString(item.start_hash, "symbol.start_hash"),
602
- end_hash: requireString(item.end_hash, "symbol.end_hash"),
603
- };
604
- }),
605
- };
606
- }
607
-
608
- function parseHashline(value: unknown, field: string): ReadSeekHashline {
609
- if (!value || typeof value !== "object") throw new Error(`invalid readseek ${field}`);
610
- const item = value as Record<string, unknown>;
611
- return {
612
- line: requireNumber(item.line, `${field}.line`),
613
- hash: requireString(item.hash, `${field}.hash`),
614
- text: requireString(item.text, `${field}.text`),
615
- };
616
- }
617
-
618
- function parseSearchHashlines(value: unknown, field: string): ReadSeekHashline[] {
619
- if (!Array.isArray(value)) throw new Error(`invalid readseek ${field}`);
620
- return value.map((line) => parseHashline(line, field));
621
- }
622
-
623
- function parseSearchOutput(value: unknown): ReadSeekSearchOutput {
624
- if (!value || typeof value !== "object") throw new Error("invalid readseek search output");
625
- const output = value as Record<string, unknown>;
626
- if (!Array.isArray(output.results)) throw new Error("invalid readseek search results");
627
- return {
628
- results: output.results.map((result) => {
629
- if (!result || typeof result !== "object") throw new Error("invalid readseek search result");
630
- const file = result as Record<string, unknown>;
631
- if (!Array.isArray(file.matches)) throw new Error("invalid readseek search matches");
632
- return {
633
- file: requireString(file.file, "search.file"),
634
- language: requireString(file.language, "search.language"),
635
- file_hash: requireString(file.file_hash, "search.file_hash"),
636
- matches: file.matches.map((match) => {
637
- if (!match || typeof match !== "object") throw new Error("invalid readseek search match");
638
- const item = match as Record<string, unknown>;
639
- if (!Array.isArray(item.captures)) throw new Error("invalid readseek search captures");
640
- return {
641
- pattern_index: item.pattern_index === undefined ? 0 : requireNumber(item.pattern_index, "search.match.pattern_index"),
642
- start_line: requireNumber(item.start_line, "search.match.start_line"),
643
- end_line: requireNumber(item.end_line, "search.match.end_line"),
644
- start_hash: requireString(item.start_hash, "search.match.start_hash"),
645
- end_hash: requireString(item.end_hash, "search.match.end_hash"),
646
- hashlines: parseSearchHashlines(item.hashlines, "search.match.hashlines"),
647
- captures: item.captures.map((capture) => {
648
- if (!capture || typeof capture !== "object") throw new Error("invalid readseek search capture");
649
- const captureItem = capture as Record<string, unknown>;
650
- return {
651
- name: requireString(captureItem.name, "search.capture.name"),
652
- start_line: requireNumber(captureItem.start_line, "search.capture.start_line"),
653
- end_line: requireNumber(captureItem.end_line, "search.capture.end_line"),
654
- start_hash: requireString(captureItem.start_hash, "search.capture.start_hash"),
655
- end_hash: requireString(captureItem.end_hash, "search.capture.end_hash"),
656
- hashlines: parseSearchHashlines(captureItem.hashlines, "search.capture.hashlines"),
657
- };
658
- }),
659
- };
660
- }),
661
- };
662
- }),
663
- };
664
- }
665
-
666
- export async function readSeekRead(
667
- filePath: string,
668
- startLine?: number,
669
- endLine?: number,
670
- options: { signal?: AbortSignal } = {},
671
- ): Promise<ReadSeekReadOutput> {
672
- const args = ["read", startLine === undefined ? filePath : `${filePath}:${startLine}`];
673
- if (endLine !== undefined) args.push("--end", String(endLine));
674
- return parseReadOutput(await runReadSeek(args, { signal: options.signal }));
675
- }
676
-
677
- function fileMapFromReadSeekOutput(output: ReadSeekMapOutput, filePath: string, totalBytes: number): FileMap | null {
678
- if (output.language === "unknown" && output.symbols.length === 0) return null;
679
- return {
680
- path: filePath,
681
- totalLines: output.line_count,
682
- totalBytes,
683
- language: normalizeLanguage(output.language),
684
- detailLevel: DetailLevel.Full,
685
- symbols: symbolsFromReadSeek(output.symbols),
686
- };
687
- }
688
-
689
- export async function readSeekMap(
690
- filePath: string,
691
- totalBytes: number,
692
- options: { signal?: AbortSignal } = {},
693
- ): Promise<FileMap | null> {
694
- const output = parseMapOutput(await runReadSeek(["map", filePath], { signal: options.signal }));
695
- return fileMapFromReadSeekOutput(output, filePath, totalBytes);
696
- }
697
-
698
- export async function readSeekSearch(
699
- target: string,
700
- pattern: string,
701
- options: ReadSeekSearchOptions = {},
702
- ): Promise<ReadSeekSearchFileOutput[]> {
703
- const args = ["search", target, pattern];
704
- if (options.language) args.push("--language", options.language);
705
- if (options.cached) args.push("--cached");
706
- if (options.others) args.push("--others");
707
- if (options.ignored) args.push("--ignored");
708
- return parseSearchOutput(await runReadSeek(args, { signal: options.signal })).results;
709
- }
710
-
711
- export async function readSeekMapContent(
712
- filePath: string,
713
- content: string,
714
- options: { signal?: AbortSignal } = {},
715
- ): Promise<FileMap | null> {
716
- const output = parseMapOutput(
717
- await runReadSeek(["map", `stdin:${filePath}`], { signal: options.signal, stdin: content }),
718
- );
719
- return fileMapFromReadSeekOutput(output, filePath, Buffer.byteLength(content, "utf8"));
720
- }
721
-
722
- function optionalString(value: unknown, field: string): string | undefined {
723
- if (value === undefined || value === null) return undefined;
724
- return requireString(value, field);
725
- }
726
-
727
- function parseRefsOutput(value: unknown): ReadSeekRefsOutput {
728
- if (!value || typeof value !== "object") throw new Error("invalid readseek refs output");
729
- const output = value as Record<string, unknown>;
730
- if (!Array.isArray(output.references)) throw new Error("invalid readseek references");
731
- return {
732
- references: output.references.map((reference) => {
733
- if (!reference || typeof reference !== "object") throw new Error("invalid readseek reference");
734
- const item = reference as Record<string, unknown>;
735
- const symbol = item.symbol;
736
- const enclosing =
737
- symbol && typeof symbol === "object"
738
- ? optionalString((symbol as Record<string, unknown>).qualified_name, "reference.symbol.qualified_name")
739
- : undefined;
740
- return {
741
- file: requireString(item.file, "reference.file"),
742
- line: requireNumber(item.line, "reference.line"),
743
- column: requireNumber(item.column, "reference.column"),
744
- line_hash: requireString(item.line_hash, "reference.line_hash"),
745
- text: requireString(item.text, "reference.text"),
746
- enclosingSymbol: enclosing,
747
- };
748
- }),
749
- };
750
- }
751
-
752
- export async function readSeekRefs(
753
- target: string,
754
- name: string,
755
- options: ReadSeekRefsOptions = {},
756
- ): Promise<ReadSeekReference[]> {
757
- const args = ["refs", target, name];
758
- if (options.scope) args.push("--scope");
759
- if (options.line !== undefined) args.push("--line", String(options.line));
760
- if (options.column !== undefined) args.push("--column", String(options.column));
761
- if (options.language) args.push("--language", options.language);
762
- if (options.cached) args.push("--cached");
763
- if (options.others) args.push("--others");
764
- if (options.ignored) args.push("--ignored");
765
- return parseRefsOutput(await runReadSeek(args, { signal: options.signal })).references;
766
- }
767
-
768
- function parseDiagnosticKind(value: unknown): ReadSeekDiagnostic["kind"] {
769
- if (value === "error" || value === "missing") return value;
770
- throw new Error("invalid readseek diagnostic.kind");
771
- }
772
-
773
- function parseCheckOutput(value: unknown): ReadSeekCheckOutput {
774
- if (!value || typeof value !== "object") throw new Error("invalid readseek check output");
775
- const output = value as Record<string, unknown>;
776
- if (!Array.isArray(output.diagnostics)) throw new Error("invalid readseek diagnostics");
777
- return {
778
- errorCount: requireNumber(output.error_count, "error_count"),
779
- missingCount: requireNumber(output.missing_count, "missing_count"),
780
- diagnostics: output.diagnostics.map((diagnostic) => {
781
- if (!diagnostic || typeof diagnostic !== "object") throw new Error("invalid readseek diagnostic");
782
- const item = diagnostic as Record<string, unknown>;
783
- return {
784
- kind: parseDiagnosticKind(item.kind),
785
- start_line: requireNumber(item.start_line, "diagnostic.start_line"),
786
- end_line: requireNumber(item.end_line, "diagnostic.end_line"),
787
- };
788
- }),
789
- };
790
- }
791
-
792
- export async function readSeekCheck(
793
- filePath: string,
794
- content: string,
795
- options: { signal?: AbortSignal } = {},
796
- ): Promise<ReadSeekCheckOutput> {
797
- return parseCheckOutput(
798
- await runReadSeek(["check", `stdin:${filePath}`], { signal: options.signal, stdin: content }),
799
- );
800
- }
801
-
802
- function parseOcrText(value: unknown): ReadSeekOcrText | undefined {
803
- return optionalString(value, "ocr");
804
- }
805
-
806
- function parseDetectedObjects(value: unknown): ReadSeekDetectedObject[] | undefined {
807
- if (value === undefined || value === null) return undefined;
808
- if (!Array.isArray(value)) throw new Error("invalid readseek detect objects");
809
- return value.map((object) => {
810
- if (!object || typeof object !== "object") throw new Error("invalid readseek detect object");
811
- const item = object as Record<string, unknown>;
812
- const bbox = item.bbox;
813
- if (!Array.isArray(bbox) || bbox.length !== 4) throw new Error("invalid readseek detect object.bbox");
814
- return {
815
- label: requireString(item.label, "object.label"),
816
- bbox: bbox.map((n, i) => requireNumber(n, `object.bbox[${i}]`)) as ReadSeekDetectedObject["bbox"],
817
- };
818
- });
819
- }
820
-
821
- function parseDetectOutput(value: unknown): ReadSeekDetection {
822
- if (!value || typeof value !== "object") throw new Error("invalid readseek detect output");
823
- const output = value as Record<string, unknown>;
824
- const type = requireString(output.type, "type");
825
- const file = requireString(output.file, "file");
826
- const mime = optionalString(output.mime, "mime");
827
-
828
- // readseek 0.4.22 made the detect enum untagged and repurposed `type` to
829
- // carry the actual MIME type. Discriminate structurally: image detections
830
- // carry `format`/`width`/`height`/`animated`; source detections carry
831
- // `language`. Text and binary are byte-identical on the wire and collapse
832
- // to the text variant.
833
- if (type === "application/pdf") {
834
- if (output.format !== "pdf") throw new Error("invalid readseek PDF format");
835
- return {
836
- kind: "pdf",
837
- type,
838
- file,
839
- mime,
840
- format: output.format,
841
- pages: requireNumber(output.pages, "pages"),
842
- };
843
- }
844
- if (output.width !== undefined || output.height !== undefined) {
845
- return {
846
- kind: "image",
847
- type,
848
- file,
849
- mime,
850
- format: requireString(output.format, "format"),
851
- width: requireNumber(output.width, "width"),
852
- height: requireNumber(output.height, "height"),
853
- animated: requireBoolean(output.animated, "animated"),
854
- encoding: output.encoding === undefined ? undefined : parseImageEncoding(output.encoding),
855
- data: output.data === undefined ? undefined : requireString(output.data, "data"),
856
- ocr: parseOcrText(output.ocr),
857
- caption: optionalString(output.caption, "caption"),
858
- objects: parseDetectedObjects(output.objects),
859
- };
860
- }
861
- if (output.language !== undefined) {
862
- return {
863
- kind: "source",
864
- type,
865
- file,
866
- language: requireString(output.language, "language"),
867
- engine: optionalString(output.engine, "engine"),
868
- supported: requireBoolean(output.supported, "supported"),
869
- mime,
870
- syntax: optionalString(output.syntax, "syntax"),
871
- };
872
- }
873
- return { kind: "text", type, file, mime };
874
- }
875
-
876
- function parseImageEncoding(value: unknown): "base64" {
877
- if (value === "base64") return value;
878
- throw new Error("invalid image encoding");
879
- }
880
-
881
- export async function readSeekDetect(
882
- filePath: string,
883
- options: { signal?: AbortSignal } = {},
884
- ): Promise<ReadSeekDetection> {
885
- return parseDetectOutput(await runReadSeek(["detect", filePath], { signal: options.signal }));
886
- }
887
-
888
- /**
889
- * Analyze an image with each requested vision mode and merge the payloads into
890
- * a single detection. readseek accepts one `--image` mode per invocation, so
891
- * modes that fail are dropped as long as at least one produced a payload;
892
- * otherwise the first failure is rethrown.
893
- */
894
- export async function readSeekImage(
895
- filePath: string,
896
- modes: ReadSeekImageMode[],
897
- options: { signal?: AbortSignal } = {},
898
- ): Promise<ReadSeekDetection> {
899
- const requestedModes = modes.length > 1 ? ["all"] : modes;
900
- const results = await Promise.allSettled(
901
- requestedModes.map(async (mode) =>
902
- parseDetectOutput(await runReadSeekVision(["read", "--image", mode, filePath], { signal: options.signal })),
903
- ),
904
- );
905
-
906
- let merged: ReadSeekImageDetection | undefined;
907
- for (const result of results) {
908
- if (result.status !== "fulfilled" || result.value.kind !== "image") continue;
909
- const detection = result.value;
910
- merged = merged === undefined
911
- ? detection
912
- : {
913
- ...merged,
914
- ocr: detection.ocr ?? merged.ocr,
915
- caption: detection.caption ?? merged.caption,
916
- objects: detection.objects ?? merged.objects,
917
- };
918
- }
919
- if (merged !== undefined) return merged;
920
-
921
- const failure = results.find((result) => result.status === "rejected");
922
- if (failure?.status === "rejected") throw failure.reason;
923
- throw new Error(`readseek returned no image analysis for ${filePath}`);
924
- }
925
-
926
- export async function readSeekPreparedImage(
927
- filePath: string,
928
- options: { signal?: AbortSignal } = {},
929
- ): Promise<ReadSeekPreparedImage> {
930
- const output = parseDetectOutput(await runReadSeek(["read", "--image", "none", filePath], { signal: options.signal }));
931
- if (output.kind !== "image" || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
932
- throw new Error(`readseek returned no prepared image for ${filePath}`);
933
- }
934
- return { mime: output.mime, encoding: output.encoding, data: output.data };
935
- }
936
-
937
- function parsePdfImage(value: unknown): ReadSeekPdfImage {
938
- if (!value || typeof value !== "object") throw new Error("invalid readseek PDF image");
939
- const image = value as Record<string, unknown>;
940
- const mode = requireString(image.mode, "PDF image.mode");
941
- if (mode !== "none" && mode !== "all" && mode !== "ocr" && mode !== "caption" && mode !== "objects") {
942
- throw new Error("invalid readseek PDF image.mode");
943
- }
944
- return {
945
- page: requireNumber(image.page, "PDF image.page"),
946
- width: requireNumber(image.width, "PDF image.width"),
947
- height: requireNumber(image.height, "PDF image.height"),
948
- mime: requireString(image.mime, "PDF image.mime"),
949
- mode,
950
- encoding: image.encoding === undefined ? undefined : parseImageEncoding(image.encoding),
951
- data: optionalString(image.data, "PDF image.data"),
952
- ocr: parseOcrText(image.ocr),
953
- caption: optionalString(image.caption, "PDF image.caption"),
954
- objects: parseDetectedObjects(image.objects),
955
- };
956
- }
957
-
958
- function parsePdfOutput(value: unknown): ReadSeekPdfOutput {
959
- if (!value || typeof value !== "object") throw new Error("invalid readseek PDF output");
960
- const output = value as Record<string, unknown>;
961
- if (output.format !== "pdf" || !Array.isArray(output.images)) throw new Error("invalid readseek PDF output");
962
- return {
963
- format: output.format,
964
- pages: requireNumber(output.pages, "PDF pages"),
965
- markdown: requireString(output.markdown, "PDF markdown"),
966
- images: output.images.map(parsePdfImage),
967
- };
968
- }
969
-
970
- export async function readSeekPdf(
971
- filePath: string,
972
- mode: "none" | ReadSeekImageMode,
973
- options: { page?: number; signal?: AbortSignal } = {},
974
- ): Promise<ReadSeekPdfOutput> {
975
- const run = mode === "none" ? runReadSeek : runReadSeekVision;
976
- const args = ["read", "--image", mode, filePath];
977
- if (options.page !== undefined) args.push("--page", String(options.page));
978
- return parsePdfOutput(await run(args, { signal: options.signal }));
979
- }
980
-
981
- // --- Rename ---
982
-
983
- export interface RenameConflict {
984
- line: number;
985
- column: number;
986
- reason: string;
987
- }
988
-
989
- export interface RenameEdit {
990
- line: number;
991
- start_column: number;
992
- end_column: number;
993
- start_byte: number;
994
- end_byte: number;
995
- occurrence: string;
996
- line_hash: string;
997
- text: string;
998
- }
999
-
1000
- export interface RenameFileOutput {
1001
- file: string;
1002
- language: string;
1003
- engine?: string;
1004
- file_hash: string;
1005
- conflicts: RenameConflict[];
1006
- edits: RenameEdit[];
1007
- }
1008
-
1009
- export interface RenameOutput {
1010
- file: string;
1011
- language: string;
1012
- engine?: string;
1013
- file_hash: string;
1014
- old_name: string;
1015
- new_name: string;
1016
- applied: boolean;
1017
- conflicts: RenameConflict[];
1018
- edits: RenameEdit[];
1019
- others: RenameFileOutput[];
1020
- }
1021
-
1022
- interface RenameOptions {
1023
- to: string;
1024
- line: number;
1025
- column?: number;
1026
- workspace?: string;
1027
- apply?: boolean;
1028
- language?: string;
1029
- cached?: boolean;
1030
- others?: boolean;
1031
- ignored?: boolean;
1032
- signal?: AbortSignal;
1033
- }
1034
-
1035
- function parseRenameConflicts(value: unknown, field: string): RenameConflict[] {
1036
- if (value === undefined) return [];
1037
- if (!Array.isArray(value)) throw new Error(`invalid readseek ${field}`);
1038
- return value.map((item) => {
1039
- if (!item || typeof item !== "object") throw new Error(`invalid readseek ${field} entry`);
1040
- const c = item as Record<string, unknown>;
1041
- return {
1042
- line: requireNumber(c.line, `${field}.line`),
1043
- column: requireNumber(c.column, `${field}.column`),
1044
- reason: requireString(c.reason, `${field}.reason`),
1045
- };
1046
- });
1047
- }
1048
-
1049
- function parseRenameEdits(value: unknown, field: string): RenameEdit[] {
1050
- if (value === undefined) return [];
1051
- if (!Array.isArray(value)) throw new Error(`invalid readseek ${field}`);
1052
- return value.map((item) => {
1053
- if (!item || typeof item !== "object") throw new Error(`invalid readseek ${field} entry`);
1054
- const e = item as Record<string, unknown>;
1055
- return {
1056
- line: requireNumber(e.line, `${field}.line`),
1057
- start_column: requireNumber(e.start_column, `${field}.start_column`),
1058
- end_column: requireNumber(e.end_column, `${field}.end_column`),
1059
- start_byte: requireNumber(e.start_byte, `${field}.start_byte`),
1060
- end_byte: requireNumber(e.end_byte, `${field}.end_byte`),
1061
- occurrence: requireString(e.occurrence, `${field}.occurrence`),
1062
- line_hash: requireString(e.line_hash, `${field}.line_hash`),
1063
- text: requireString(e.text, `${field}.text`),
1064
- };
1065
- });
1066
- }
1067
-
1068
- function parseRenameOutput(value: unknown): RenameOutput {
1069
- if (!value || typeof value !== "object") throw new Error("invalid readseek rename output");
1070
- const output = value as Record<string, unknown>;
1071
- const others = output.others;
1072
- if (others !== undefined && !Array.isArray(others)) throw new Error("invalid readseek others");
1073
- return {
1074
- file: requireString(output.file, "file"),
1075
- language: requireString(output.language, "language"),
1076
- engine: optionalString(output.engine, "engine"),
1077
- file_hash: requireString(output.file_hash, "file_hash"),
1078
- old_name: requireString(output.old_name, "old_name"),
1079
- new_name: requireString(output.new_name, "new_name"),
1080
- applied: requireBoolean(output.applied, "applied"),
1081
- conflicts: parseRenameConflicts(output.conflicts, "conflicts"),
1082
- edits: parseRenameEdits(output.edits, "edits"),
1083
- others: (others as unknown[] | undefined)?.map((entry) => {
1084
- if (!entry || typeof entry !== "object") throw new Error("invalid readseek other");
1085
- const o = entry as Record<string, unknown>;
1086
- return {
1087
- file: requireString(o.file, "other.file"),
1088
- language: requireString(o.language, "other.language"),
1089
- engine: optionalString(o.engine, "other.engine"),
1090
- file_hash: requireString(o.file_hash, "other.file_hash"),
1091
- conflicts: parseRenameConflicts(o.conflicts, "other.conflicts"),
1092
- edits: parseRenameEdits(o.edits, "other.edits"),
1093
- };
1094
- }) ?? [],
1095
- };
1096
- }
1097
-
1098
- export async function readSeekRename(
1099
- filePath: string,
1100
- options: RenameOptions,
1101
- ): Promise<RenameOutput> {
1102
- const args = ["rename", filePath, "--line", String(options.line)];
1103
- if (options.column !== undefined) args.push("--column", String(options.column));
1104
- args.push("--to", options.to);
1105
- if (options.apply) args.push("--apply");
1106
- if (options.workspace) args.push("--workspace", options.workspace);
1107
- if (options.language) args.push("--language", options.language);
1108
- if (options.cached) args.push("--cached");
1109
- if (options.others) args.push("--others");
1110
- if (options.ignored) args.push("--ignored");
1111
- return parseRenameOutput(await runReadSeek(args, { signal: options.signal }));
1112
- }
1113
-
1114
- // --- Identify ---
1115
-
1116
- export interface IdentifierOutput {
1117
- text: string;
1118
- start_column: number;
1119
- end_column: number;
1120
- start_byte: number;
1121
- end_byte: number;
1122
- }
1123
-
1124
- export interface IdentifyOutput {
1125
- file: string;
1126
- language: string;
1127
- engine?: string;
1128
- line_count: number;
1129
- file_hash: string;
1130
- line: number;
1131
- column: number;
1132
- line_hash: string;
1133
- identifier?: IdentifierOutput;
1134
- symbol?: {
1135
- name: string;
1136
- kind: string;
1137
- qualified_name: string;
1138
- start_line: number;
1139
- end_line: number;
1140
- };
1141
- }
1142
-
1143
- interface IdentifyOptions {
1144
- line?: number;
1145
- column?: number;
1146
- language?: string;
1147
- signal?: AbortSignal;
1148
- }
1149
-
1150
- function parseIdentifyOutput(value: unknown): IdentifyOutput {
1151
- if (!value || typeof value !== "object") throw new Error("invalid readseek identify output");
1152
- const output = value as Record<string, unknown>;
1153
- const identifier = output.identifier;
1154
- const symbol = output.symbol;
1155
- return {
1156
- file: requireString(output.file, "file"),
1157
- language: requireString(output.language, "language"),
1158
- engine: optionalString(output.engine, "engine"),
1159
- line_count: requireNumber(output.line_count, "line_count"),
1160
- file_hash: requireString(output.file_hash, "file_hash"),
1161
- line: requireNumber(output.line, "line"),
1162
- column: requireNumber(output.column, "column"),
1163
- line_hash: requireString(output.line_hash, "line_hash"),
1164
- identifier: identifier && typeof identifier === "object"
1165
- ? {
1166
- text: requireString((identifier as Record<string, unknown>).text, "identifier.text"),
1167
- start_column: requireNumber((identifier as Record<string, unknown>).start_column, "identifier.start_column"),
1168
- end_column: requireNumber((identifier as Record<string, unknown>).end_column, "identifier.end_column"),
1169
- start_byte: requireNumber((identifier as Record<string, unknown>).start_byte, "identifier.start_byte"),
1170
- end_byte: requireNumber((identifier as Record<string, unknown>).end_byte, "identifier.end_byte"),
1171
- }
1172
- : undefined,
1173
- symbol: symbol && typeof symbol === "object"
1174
- ? {
1175
- name: requireString((symbol as Record<string, unknown>).name, "symbol.name"),
1176
- kind: requireString((symbol as Record<string, unknown>).kind, "symbol.kind"),
1177
- qualified_name: requireString((symbol as Record<string, unknown>).qualified_name, "symbol.qualified_name"),
1178
- start_line: requireNumber((symbol as Record<string, unknown>).start_line, "symbol.start_line"),
1179
- end_line: requireNumber((symbol as Record<string, unknown>).end_line, "symbol.end_line"),
1180
- }
1181
- : undefined,
1182
- };
1183
- }
1184
-
1185
- export async function readSeekIdentify(
1186
- filePath: string,
1187
- content: string,
1188
- options: IdentifyOptions = {},
1189
- ): Promise<IdentifyOutput> {
1190
- const target = options.line === undefined ? `stdin:${filePath}` : `stdin:${filePath}:${options.line}`;
1191
- const args = ["identify", target];
1192
- if (options.column !== undefined) args.push("--column", String(options.column));
1193
- if (options.language) args.push("--language", options.language);
1194
- return parseIdentifyOutput(await runReadSeek(args, { signal: options.signal, stdin: content }));
1195
- }
1196
-
1197
- // --- Def ---
1198
-
1199
- export interface DefLocation {
1200
- file: string;
1201
- line: number;
1202
- column: number;
1203
- line_hash: string;
1204
- text: string;
1205
- kind?: string;
1206
- name?: string;
1207
- qualified_name?: string;
1208
- }
1209
-
1210
- interface DefOptions {
1211
- name: string;
1212
- language?: string;
1213
- cached?: boolean;
1214
- others?: boolean;
1215
- ignored?: boolean;
1216
- signal?: AbortSignal;
1217
- }
1218
-
1219
- function parseDefCompact(value: unknown): DefLocation[] {
1220
- if (!value || typeof value !== "object") throw new Error("invalid readseek def output");
1221
- const output = value as Record<string, unknown>;
1222
- const locations = output.locations;
1223
- if (!Array.isArray(locations)) throw new Error("invalid readseek def locations");
1224
- return locations.map((loc) => {
1225
- if (!loc || typeof loc !== "object") throw new Error("invalid readseek def location");
1226
- const item = loc as Record<string, unknown>;
1227
- return {
1228
- file: requireString(item.file, "location.file"),
1229
- line: requireNumber(item.line, "location.line"),
1230
- column: requireNumber(item.column, "location.column"),
1231
- line_hash: requireString(item.line_hash, "location.line_hash"),
1232
- text: requireString(item.text, "location.text"),
1233
- kind: optionalString(item.kind, "location.kind"),
1234
- name: optionalString(item.name, "location.name"),
1235
- qualified_name: optionalString(item.qualified_name, "location.qualified_name"),
1236
- };
1237
- });
1238
- }
1239
-
1240
- export async function readSeekDef(
1241
- target: string,
1242
- options: DefOptions,
1243
- ): Promise<DefLocation[]> {
1244
- const args = ["def", target, "--format", "plain", options.name];
1245
- if (options.language) args.push("--language", options.language);
1246
- if (options.cached) args.push("--cached");
1247
- if (options.others) args.push("--others");
1248
- if (options.ignored) args.push("--ignored");
1249
- return parseDefCompact(await runReadSeek(args, { signal: options.signal }));
1250
- }