@jarkkojs/readseek-api 0.10.6 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,4 @@
1
1
  // src/contract.ts
2
- var INSPECT_FACETS = [
3
- "metadata",
4
- "content",
5
- "map",
6
- "diagnostics",
7
- "identity"
8
- ];
9
- var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
10
- var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
11
2
  var VISION_LEVELS = ["low", "medium", "high"];
12
3
  var EDIT_VARIANTS = [
13
4
  "set_line",
@@ -18,15 +9,22 @@ var EDIT_VARIANTS = [
18
9
  ];
19
10
  var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
20
11
  var TOOL_NAMES = [
21
- "inspect",
22
- "edit",
23
- "write",
24
- "grep",
25
- "extract",
26
- "search",
27
- "def",
28
- "refs",
29
- "rename"
12
+ "show_content",
13
+ "show_structure",
14
+ "show_metadata",
15
+ "show_assets",
16
+ "check_syntax",
17
+ "check_rules",
18
+ "generate_caption",
19
+ "detect_objects",
20
+ "recognize_text",
21
+ "find_nodes",
22
+ "find_text",
23
+ "find_definitions",
24
+ "find_references",
25
+ "edit_text",
26
+ "rename_symbol",
27
+ "write_file"
30
28
  ];
31
29
  var PARAM_DESCRIPTIONS = {
32
30
  path: "Absolute or relative file path",
@@ -37,31 +35,30 @@ var PARAM_DESCRIPTIONS = {
37
35
  languageOverride: "Language override",
38
36
  languageSymbol: "Language override for symbol replacement",
39
37
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
38
+ patternContext: "Surrounding code context for sub-statement patterns",
39
+ selector: "Target AST node kind to extract from pattern context",
40
40
  grepPattern: "Regex pattern; set literal for exact text",
41
41
  name: "Qualified or unqualified symbol name",
42
42
  refsName: "Identifier to find references for",
43
- scope: "Restrict results to the binding at the cursor",
44
- line: "One-based cursor line; required with scope",
45
- renameLine: "One-based cursor line of the symbol to rename",
46
- column: "One-based cursor byte column for disambiguation",
47
43
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
48
- select: "Facets: metadata, content, map, diagnostics, identity (default content)",
49
- selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
50
- selector: "Positional selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID. Columns require the identity facet, which in turn rejects symbol selectors",
51
- assetSelector: "Document asset selector in the form asset:ID",
52
- query: "KDL selection query, @path, or '-' for standard input",
44
+ at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
45
+ assetAt: "Document selector: page:N, node:ID, or asset:ID",
46
+ rule: "KDL node selection rule, @path, or '-' for standard input",
47
+ rules: "KDL structural checks, @path, or '-' for standard input",
53
48
  stdinName: "File name hint when the target is '-'",
54
49
  end: "Last line to include",
55
50
  limit: "Maximum number of lines to include",
56
- inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
51
+ depth: "Maximum structural depth for structure output; defaults to 1 in agent tools",
57
52
  offset: "One-based starting line",
58
53
  readLimit: "Maximum lines to return",
59
54
  symbolRead: "Symbol name to read",
60
55
  bundle: "Include related symbols from the same file; requires symbol",
61
- image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
62
- visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
63
- visionLevel: "Vision inference level: low (default), medium, or high",
64
- output: "Destination file for extracted binary data; stdout is not supported",
56
+ level: "Explicit inference level: low (default), medium, or high",
57
+ include: "Additional caption tasks: text and/or objects",
58
+ kind: "Restrict structure to this node kind",
59
+ outline: "Return a symbol outline",
60
+ noAnchors: "Omit source line anchors",
61
+ text: "Literal native text query; adapter find_text also supports regex via pattern",
65
62
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
66
63
  editVariant: "Exactly one nested edit variant",
67
64
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -98,15 +95,22 @@ var PARAM_DESCRIPTIONS = {
98
95
  scopeContext: "Context lines within each symbol"
99
96
  };
100
97
  var TOOL_DESCRIPTIONS = {
101
- inspect: "Inspect source content, images, and documents through the native CLI envelope. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
102
- edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
103
- write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
104
- grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
105
- extract: "Extract a selected document asset into a destination file. Use an `asset:ID` selector discovered through `inspect`; extracted binary data is never returned on stdout.",
106
- search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
107
- def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
108
- refs: "Find references or usages of an identifier by name or cursor binding. Results include enclosing symbols and line hashes usable as `LINE:HASH` anchors. Without cursor scope, results are identifier-name matches; with scope, they follow one binding and exclude shadows. Use text search for literals, and use this before renaming or deleting a symbol.",
109
- rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
98
+ show_content: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
99
+ show_structure: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
100
+ show_metadata: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
101
+ show_assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
102
+ check_syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
103
+ check_rules: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
104
+ generate_caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
105
+ detect_objects: "Explicitly infer object labels and bounding boxes using an installed model.",
106
+ recognize_text: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
107
+ find_nodes: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `find_text` for text and regex.",
108
+ find_text: "Search text or regex and return edit-ready `LINE:HASH` anchors. Native CLI text is literal; adapters preserve regex, glob, context, summary, and symbol grouping.",
109
+ find_definitions: "Find a symbol definition or declaration by qualified or unqualified name.",
110
+ find_references: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
111
+ edit_text: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
112
+ rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
113
+ write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
110
114
  };
111
115
  function renderToolDescription(name, aliases = {}) {
112
116
  const desc = TOOL_DESCRIPTIONS[name];
@@ -182,202 +186,36 @@ var editItemSchema = {
182
186
  },
183
187
  description: PARAM_DESCRIPTIONS.editVariant
184
188
  };
189
+ var string = (description) => ({ type: "string", description });
190
+ var integer = (description, minimum = 1) => ({ type: "integer", minimum, description });
191
+ var path = { path: string(PARAM_DESCRIPTIONS.path) };
192
+ var at = { at: string(PARAM_DESCRIPTIONS.at) };
193
+ var lang = { language: string(PARAM_DESCRIPTIONS.language) };
194
+ var stdin = { stdinName: string(PARAM_DESCRIPTIONS.stdinName) };
195
+ var noAnchors = { noAnchors: { type: "boolean", description: PARAM_DESCRIPTIONS.noAnchors } };
196
+ var level = { level: { type: "string", enum: VISION_LEVELS, description: PARAM_DESCRIPTIONS.level } };
197
+ var limit = { limit: integer(PARAM_DESCRIPTIONS.matchLimit) };
198
+ var apply = { apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply }, planHash: string(PARAM_DESCRIPTIONS.planHash) };
199
+ function tool(name, title, properties, required) {
200
+ return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
201
+ }
185
202
  var TOOL_CONTRACTS = [
186
- {
187
- name: "inspect",
188
- title: "Inspect input",
189
- description: TOOL_DESCRIPTIONS.inspect,
190
- inputSchema: {
191
- type: "object",
192
- properties: {
193
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
194
- selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
195
- select: { type: "string", description: PARAM_DESCRIPTIONS.select },
196
- query: { type: "string", description: PARAM_DESCRIPTIONS.query },
197
- end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
198
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
199
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
200
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
201
- visionMode: {
202
- type: "string",
203
- enum: [...VISION_MODES],
204
- description: PARAM_DESCRIPTIONS.visionModeInspect
205
- },
206
- visionLevel: {
207
- type: "string",
208
- enum: [...VISION_LEVELS],
209
- description: PARAM_DESCRIPTIONS.visionLevel
210
- },
211
- stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
212
- },
213
- required: ["path"],
214
- additionalProperties: false
215
- }
216
- },
217
- {
218
- name: "edit",
219
- title: "Hash-verified edit",
220
- description: TOOL_DESCRIPTIONS.edit,
221
- inputSchema: {
222
- type: "object",
223
- properties: {
224
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
225
- edits: {
226
- type: "array",
227
- minItems: 1,
228
- description: PARAM_DESCRIPTIONS.edits,
229
- items: editItemSchema
230
- },
231
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageSymbol },
232
- apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
233
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
234
- postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify }
235
- },
236
- required: ["path", "edits"],
237
- additionalProperties: false
238
- }
239
- },
240
- {
241
- name: "write",
242
- title: "Write file",
243
- description: TOOL_DESCRIPTIONS.write,
244
- inputSchema: {
245
- type: "object",
246
- properties: {
247
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
248
- content: { type: "string", description: PARAM_DESCRIPTIONS.content }
249
- },
250
- required: ["path", "content"],
251
- additionalProperties: false
252
- }
253
- },
254
- {
255
- name: "grep",
256
- title: "Text search",
257
- description: TOOL_DESCRIPTIONS.grep,
258
- inputSchema: {
259
- type: "object",
260
- properties: {
261
- pattern: { type: "string", description: PARAM_DESCRIPTIONS.grepPattern },
262
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
263
- glob: { type: "string", description: PARAM_DESCRIPTIONS.glob },
264
- ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase },
265
- literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal },
266
- context: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.context },
267
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
268
- summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary },
269
- scope: {
270
- type: "string",
271
- enum: ["symbol"],
272
- description: PARAM_DESCRIPTIONS.grepScope
273
- },
274
- scopeContext: {
275
- type: "integer",
276
- minimum: 0,
277
- description: PARAM_DESCRIPTIONS.scopeContext
278
- }
279
- },
280
- required: ["pattern"],
281
- additionalProperties: false
282
- }
283
- },
284
- {
285
- name: "extract",
286
- title: "Extract asset",
287
- description: TOOL_DESCRIPTIONS.extract,
288
- inputSchema: {
289
- type: "object",
290
- properties: {
291
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
292
- selector: {
293
- type: "string",
294
- pattern: "^asset:.+",
295
- description: PARAM_DESCRIPTIONS.assetSelector
296
- },
297
- output: {
298
- type: "string",
299
- minLength: 1,
300
- pattern: "^(?!-$).+",
301
- description: PARAM_DESCRIPTIONS.output
302
- },
303
- stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
304
- },
305
- required: ["path", "selector", "output"],
306
- additionalProperties: false
307
- }
308
- },
309
- {
310
- name: "search",
311
- title: "AST search",
312
- description: TOOL_DESCRIPTIONS.search,
313
- inputSchema: {
314
- type: "object",
315
- properties: {
316
- pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
317
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
318
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
319
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
320
- ...gitScopeSchema
321
- },
322
- required: ["pattern"],
323
- additionalProperties: false
324
- }
325
- },
326
- {
327
- name: "def",
328
- title: "Find definition",
329
- description: TOOL_DESCRIPTIONS.def,
330
- inputSchema: {
331
- type: "object",
332
- properties: {
333
- name: { type: "string", description: PARAM_DESCRIPTIONS.name },
334
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
335
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
336
- ...gitScopeSchema
337
- },
338
- required: ["name"],
339
- additionalProperties: false
340
- }
341
- },
342
- {
343
- name: "refs",
344
- title: "Find references",
345
- description: TOOL_DESCRIPTIONS.refs,
346
- inputSchema: {
347
- type: "object",
348
- properties: {
349
- name: { type: "string", description: PARAM_DESCRIPTIONS.refsName },
350
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
351
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
352
- scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
353
- line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
354
- column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
355
- ...gitScopeSchema
356
- },
357
- additionalProperties: false
358
- }
359
- },
360
- {
361
- name: "rename",
362
- title: "Rename symbol",
363
- description: TOOL_DESCRIPTIONS.rename,
364
- inputSchema: {
365
- type: "object",
366
- properties: {
367
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
368
- line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.renameLine },
369
- column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
370
- to: { type: "string", description: PARAM_DESCRIPTIONS.to },
371
- workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
372
- apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
373
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
374
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
375
- ...gitScopeSchema
376
- },
377
- required: ["path", "line", "to"],
378
- additionalProperties: false
379
- }
380
- }
203
+ tool("show_content", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
204
+ tool("show_structure", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
205
+ tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
206
+ tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
207
+ tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
208
+ tool("check_rules", "Check rules", { ...path, ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
209
+ tool("generate_caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
210
+ tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
211
+ tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
212
+ tool("find_nodes", "Find nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
213
+ tool("find_text", "Find text", { ...path, ...limit, ...gitScopeSchema, text: string(PARAM_DESCRIPTIONS.text), pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, []),
214
+ tool("find_definitions", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
215
+ tool("find_references", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
216
+ tool("edit_text", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
217
+ tool("rename_symbol", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
218
+ tool("write_file", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
381
219
  ];
382
220
  function getToolContract(name) {
383
221
  const contract = TOOL_CONTRACTS.find((entry) => entry.name === name);
@@ -388,70 +226,70 @@ function getToolContract(name) {
388
226
  // src/policy.ts
389
227
  var REPLACEABLE_TOOLS = [
390
228
  {
391
- tool: "inspect",
229
+ tool: "show_content",
392
230
  builtIn: "read",
393
231
  benefit: "it returns LINE:HASH anchors for safe edits."
394
232
  },
395
233
  {
396
- tool: "edit",
234
+ tool: "edit_text",
397
235
  builtIn: "edit",
398
236
  benefit: "it verifies fresh LINE:HASH anchors."
399
237
  },
400
238
  {
401
- tool: "grep",
239
+ tool: "find_text",
402
240
  builtIn: "grep",
403
241
  benefit: "it returns LINE:HASH anchors."
404
242
  },
405
243
  {
406
- tool: "write",
244
+ tool: "write_file",
407
245
  builtIn: "write",
408
246
  benefit: "it returns LINE:HASH anchors."
409
247
  }
410
248
  ];
411
249
  var TOOL_COMPACT_GUIDELINES = {
412
- inspect: [
413
- "Use `inspect` for shallow maps, diagnostics, and identity; use `def`, `refs`, or `search` for targeted symbol questions."
250
+ show_content: [
251
+ "Use `show_structure` for shallow overviews and `check_syntax` for diagnostics; use `find_definitions`, `find_references`, or `find_nodes` for targeted symbol questions."
414
252
  ],
415
- edit: [
253
+ edit_text: [
416
254
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
417
255
  ],
418
- grep: [
419
- "Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
256
+ find_text: [
257
+ "Use `find_text` with summary first for broad searches, then narrow by path, glob, or pattern."
420
258
  ],
421
- write: [
422
- "Use anchored edits rather than `write` for small changes or appends."
259
+ write_file: [
260
+ "Use anchored edits rather than `write_file` for small changes or appends."
423
261
  ],
424
- search: [
425
- "Use `search` for AST patterns; use `grep` for plain text."
262
+ find_nodes: [
263
+ "Use `find_nodes` for AST patterns; use `find_text` for plain text."
426
264
  ],
427
- refs: [
428
- "Use `refs` before changing a symbol; add scope plus line/column to follow one binding."
265
+ find_references: [
266
+ "Use `find_references` before changing a symbol; use at with line:N:C to follow one binding."
429
267
  ]
430
268
  };
431
269
  var BUILTIN_FALLBACKS = {
432
- read: "inspect",
433
- edit: "edit",
434
- write: "write",
435
- grep: "grep",
436
- apply_patch: "edit"
270
+ read: "show_content",
271
+ edit: "edit_text",
272
+ write: "write_file",
273
+ grep: "find_text",
274
+ apply_patch: "edit_text"
437
275
  };
438
276
  var PREFERENCE_LEADS = {
439
- inspect: "Preferred for reading source with anchors and facets.",
440
- edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
441
- write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
442
- grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
443
- search: "Preferred for AST or structural code search with ast-grep-style patterns.",
444
- def: "Preferred for go-to-definition or declaration lookup by name.",
445
- refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
446
- rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
447
- extract: "Preferred for saving a selected document asset to a file."
277
+ show_content: "Preferred for reading source with anchors.",
278
+ edit_text: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
279
+ write_file: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
280
+ find_text: "Preferred text/regex find_nodes; returns edit_text-ready LINE:HASH anchors.",
281
+ find_nodes: "Preferred for AST or structural code find_nodes with ast-find_text-style patterns.",
282
+ find_definitions: "Preferred for go-to-definition or declaration lookup by name.",
283
+ find_references: "Preferred for finding identifier-name matches or binding-specific references before rename_symbol or delete.",
284
+ rename_symbol: "Preferred for renaming a symbol with binding-aware, verified edits.",
285
+ show_assets: "Preferred for saving a selected document asset to a file."
448
286
  };
449
287
  var FALLBACK_TEMPLATES = {
450
- read: "Prefer `inspect` with fresh LINE:HASH anchors.",
451
- edit: "Prefer `edit` with fresh LINE:HASH anchors.",
452
- write: "Prefer `write` so the result returns LINE:HASH anchors.",
453
- grep: "Prefer `grep` for text/regex search with edit-ready anchors.",
454
- apply_patch: "Prefer `edit` for existing text files."
288
+ read: "Prefer `show_content` with fresh LINE:HASH anchors.",
289
+ edit: "Prefer `edit_text` with fresh LINE:HASH anchors.",
290
+ write: "Prefer `write_file` so the result returns LINE:HASH anchors.",
291
+ grep: "Prefer `find_text` for text/regex search with edit-ready anchors.",
292
+ apply_patch: "Prefer `edit_text` for existing text files."
455
293
  };
456
294
  function resolveToolName(name, aliases = {}) {
457
295
  return aliases[name] ?? name;
@@ -463,7 +301,7 @@ function renderCompactGuidelines(name, aliases = {}) {
463
301
  return (TOOL_COMPACT_GUIDELINES[name] ?? []).map((guideline) => rewriteBacktickedToolNames(guideline, aliases));
464
302
  }
465
303
  function renderPreferredToolDescription(name, aliases = {}) {
466
- return rewriteBacktickedToolNames(PREFERENCE_LEADS[name], aliases);
304
+ return rewriteBacktickedToolNames(PREFERENCE_LEADS[name] ?? `Preferred for ${name.replaceAll("_", " ")} with ReadSeek.`, aliases);
467
305
  }
468
306
  function renderFallbackToolDescription(builtIn, aliases = {}) {
469
307
  const template = FALLBACK_TEMPLATES[builtIn];
@@ -473,20 +311,20 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
473
311
  }
474
312
  function renderToolRoutingPolicy(options = {}) {
475
313
  const aliases = options.aliases ?? {};
476
- const inspect = resolveToolName("inspect", aliases);
477
- const edit = resolveToolName("edit", aliases);
478
- const write = resolveToolName("write", aliases);
479
- const grep = resolveToolName("grep", aliases);
480
- const search = resolveToolName("search", aliases);
481
- const def = resolveToolName("def", aliases);
482
- const refs = resolveToolName("refs", aliases);
483
- const rename = resolveToolName("rename", aliases);
484
- const extract = resolveToolName("extract", aliases);
314
+ const show_content = resolveToolName("show_content", aliases);
315
+ const edit_text = resolveToolName("edit_text", aliases);
316
+ const write_file = resolveToolName("write_file", aliases);
317
+ const find_text = resolveToolName("find_text", aliases);
318
+ const find_nodes = resolveToolName("find_nodes", aliases);
319
+ const find_definitions = resolveToolName("find_definitions", aliases);
320
+ const find_references = resolveToolName("find_references", aliases);
321
+ const rename_symbol = resolveToolName("rename_symbol", aliases);
322
+ const show_assets = resolveToolName("show_assets", aliases);
485
323
  if (options.style === "server") {
486
324
  return [
487
325
  "Structural code navigation, document inspection, and hash-verified edits with readseek.",
488
- "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow inspect maps; diagnostics and identity; and document inspection.",
489
- `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
326
+ "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST patterns; shallow show_structure views; check_syntax diagnostics and show_content cursor context; and document inspection.",
327
+ `Use ${edit_text} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
490
328
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
491
329
  "call MCP tools for active navigation, document questions, or anchored edits."
492
330
  ].join(" ");
@@ -496,7 +334,7 @@ function renderToolRoutingPolicy(options = {}) {
496
334
  if (options.preferOverBuiltIns) {
497
335
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
498
336
  }
499
- bullets.push(`- Read with ${inspect} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${inspect} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${inspect} for document structure; run ${inspect} with select diagnostics after source edits for a quick syntax check.`, `- Use ${extract} to save a selected document asset.`);
337
+ bullets.push(`- Read with ${show_content} first; ${edit_text} needs fresh LINE:HASH anchors.`, `- Use ${find_text} for text/regex, ${find_nodes} for AST patterns, ${find_definitions}/${find_references} for targeted symbols, and shallow ${resolveToolName("show_structure", aliases)} structures only for file overviews.`, `- Use ${edit_text} for existing files, ${write_file} for whole-file creation or replacement, and ${rename_symbol} for symbol renames.`, `- Use ${resolveToolName("show_structure", aliases)} for document structure; run ${resolveToolName("check_syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${show_assets} to materialize selected document assets and read their returned paths.`);
500
338
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
501
339
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
502
340
  }
@@ -538,25 +376,9 @@ function parseLineAnchor(anchor) {
538
376
  }
539
377
 
540
378
  // src/validation.ts
541
- function validateRefsNameVsScope(input) {
542
- if (input.scope) {
543
- if (input.line === undefined) {
544
- return { ok: false, error: "scope requires line" };
545
- }
546
- return { ok: true };
547
- }
548
- if (input.line !== undefined || input.column !== undefined) {
549
- return { ok: false, error: "line/column require scope=true" };
550
- }
551
- if (typeof input.name !== "string" || input.name.length === 0) {
552
- return { ok: false, error: "name is required when scope is not set" };
553
- }
554
- return { ok: true };
555
- }
556
- function validateVisionLevelVsMode(input) {
557
- if (input.visionLevel !== undefined && !["all", "ocr", "caption", "objects"].includes(input.visionMode ?? "")) {
558
- return { ok: false, error: "visionLevel requires an analysis visionMode" };
559
- }
379
+ function validateReferencesSelection(input) {
380
+ if (Boolean(input.name) === Boolean(input.at))
381
+ return { ok: false, error: "exactly one of name or at is required" };
560
382
  return { ok: true };
561
383
  }
562
384
  var VARIANT_FIELDS = {
@@ -682,48 +504,8 @@ function coerceNonNegativeInt(value) {
682
504
  }
683
505
  return;
684
506
  }
685
- // src/edit-plan.ts
686
- import { Document, Node, format } from "@bgotink/kdl";
687
- function node(name, properties, children = []) {
688
- const result = Node.create(name);
689
- for (const [key, value] of Object.entries(properties))
690
- result.setProperty(key, value);
691
- if (children.length > 0)
692
- result.children = new Document(children);
693
- return result;
694
- }
695
- function editNode(edit) {
696
- if ("set_line" in edit) {
697
- const value = edit.set_line;
698
- return node("set-line", { anchor: value.anchor, text: value.new_text });
699
- }
700
- if ("replace_lines" in edit) {
701
- const value = edit.replace_lines;
702
- return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
703
- }
704
- if ("insert_after" in edit) {
705
- const value = edit.insert_after;
706
- return node("insert-after", { anchor: value.anchor, text: value.new_text });
707
- }
708
- if ("replace_symbol" in edit) {
709
- const value = edit.replace_symbol;
710
- return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
711
- }
712
- const value = edit.replace;
713
- return node("replace", {
714
- old: value.old_text,
715
- new: value.new_text,
716
- ...value.all === undefined ? {} : { all: value.all }
717
- });
718
- }
719
- function serializeEditPlan(edits) {
720
- const validation = validateExactlyOneEditVariant(edits);
721
- if (!validation.ok)
722
- throw new Error(validation.error);
723
- return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
724
- }
725
507
  // src/argv.ts
726
- var DEFAULT_INSPECT_MAP_DEPTH = 1;
508
+ var DEFAULT_STRUCTURE_DEPTH = 1;
727
509
  function buildLineLocation(line, column) {
728
510
  return column === undefined ? `line:${line}` : `line:${line}:${column}`;
729
511
  }
@@ -753,30 +535,6 @@ function appendApplyAndPlanHash(args, input) {
753
535
  pushOptionalString(args, "--plan-hash", input.planHash);
754
536
  return args;
755
537
  }
756
- function selectArg(select) {
757
- if (select === undefined)
758
- return;
759
- return Array.isArray(select) ? select.join(",") : select;
760
- }
761
- function selectedInspectFacets(select) {
762
- const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
763
- for (const facet of facets) {
764
- if (!INSPECT_FACETS.includes(facet)) {
765
- throw new Error(`unknown inspect facet: ${facet}`);
766
- }
767
- }
768
- return facets;
769
- }
770
- function hasInspectFacet(facets, facet) {
771
- return facets.includes(facet);
772
- }
773
- function validateTarget(command, target, stdinName) {
774
- if (target.length === 0)
775
- throw new Error(`${command} requires a target file path or '-'`);
776
- if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
777
- throw new Error("stdinName requires '-' as the target");
778
- }
779
- }
780
538
  function validatePositiveInteger(value, name) {
781
539
  if (!Number.isSafeInteger(value) || value < 1) {
782
540
  throw new Error(`${name} must be a positive integer`);
@@ -822,1425 +580,1498 @@ function parseSelector(text) {
822
580
  }
823
581
  return { kind, value };
824
582
  }
825
- function buildInspectArgs(input) {
826
- validateTarget("inspect", input.path, input.stdinName);
827
- if (input.selector !== undefined && input.selector.length === 0) {
828
- throw new Error("selector must not be empty");
583
+ function start(verb, object, input) {
584
+ if (!input.path)
585
+ throw new Error(`${verb} ${object} requires a path`);
586
+ if (input.stdinName !== undefined && input.path !== "-")
587
+ throw new Error("stdinName requires '-' as the target");
588
+ const args = [];
589
+ pushOptionalString(args, "--readseek-dir", input.readseekDir);
590
+ args.push(verb, object, input.path);
591
+ pushOptionalString(args, "--stdin-name", input.stdinName);
592
+ return args;
593
+ }
594
+ function at2(args, value) {
595
+ if (value !== undefined) {
596
+ parseSelector(value);
597
+ args.push("--at", value);
829
598
  }
830
- const vision = validateVisionLevelVsMode({
831
- visionMode: input.visionMode,
832
- visionLevel: input.visionLevel
833
- });
834
- if (!vision.ok)
835
- throw new Error(vision.error);
836
- const facets = selectedInspectFacets(input.select);
837
- const hasContent = hasInspectFacet(facets, "content");
838
- const hasIdentity = hasInspectFacet(facets, "identity");
839
- const hasMap = hasInspectFacet(facets, "map");
840
- const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
599
+ }
600
+ function finish(args) {
601
+ args.push("--kdl");
602
+ return args;
603
+ }
604
+ function language(args, input) {
605
+ pushOptionalString(args, "--language", input.language);
606
+ }
607
+ function limit2(args, value) {
608
+ if (value !== undefined)
609
+ validatePositiveInteger(value, "limit");
610
+ pushOptionalNumber(args, "--limit", value);
611
+ }
612
+ function anchors(args, input) {
613
+ if (input.noAnchors)
614
+ args.push("--no-anchors");
615
+ }
616
+ function buildShowContentArgs(input) {
841
617
  if (input.end !== undefined)
842
618
  validatePositiveInteger(input.end, "end");
843
- if (input.limit !== undefined)
844
- validatePositiveInteger(input.limit, "limit");
845
- if (input.end !== undefined && input.limit !== undefined) {
619
+ if (input.end !== undefined && input.limit !== undefined)
846
620
  throw new Error("cannot combine end with limit");
847
- }
848
- if (input.depth !== undefined) {
849
- if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
850
- throw new Error("depth must be a non-negative integer");
851
- }
852
- if (!hasMap)
853
- throw new Error("depth requires the map facet");
854
- }
855
- if (input.selector !== undefined && input.query !== undefined) {
856
- throw new Error("cannot combine selector with query");
857
- }
858
- if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
859
- throw new Error("ranges and queries require the content facet");
860
- }
861
- if (hasIdentity && input.selector === undefined) {
862
- throw new Error("the identity facet requires a selector");
863
- }
864
- if (input.selector !== undefined && !hasContent && !hasIdentity) {
865
- throw new Error("selectors require the content or identity facet");
866
- }
867
- if (input.selector !== undefined) {
868
- const selector = parseSelector(input.selector);
869
- if (selector.kind === "symbol" && hasIdentity) {
870
- throw new Error("the identity facet does not accept symbol selectors");
871
- }
872
- if (selector.column !== undefined && !hasIdentity) {
873
- throw new Error("selector columns require the identity facet");
874
- }
875
- }
876
- const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
877
- if (hasVision && !hasContent) {
878
- throw new Error("vision options require the content facet");
879
- }
880
- if (hasVision && hasSourceFacet) {
881
- throw new Error("vision options cannot be combined with source facets");
882
- }
883
- const args = [];
884
- if (input.readseekDir !== undefined)
885
- args.push("--readseek-dir", input.readseekDir);
886
- args.push("inspect", input.path);
887
- if (input.selector !== undefined)
888
- args.push(input.selector);
889
- const select = selectArg(input.select);
890
- if (select !== undefined)
891
- args.push("--select", select);
892
- pushOptionalString(args, "--query", input.query);
621
+ const args = start("show", "content", input);
622
+ at2(args, input.at);
893
623
  pushOptionalNumber(args, "--end", input.end);
894
- pushOptionalNumber(args, "--limit", input.limit);
624
+ limit2(args, input.limit);
625
+ language(args, input);
626
+ anchors(args, input);
627
+ return finish(args);
628
+ }
629
+ function buildShowStructureArgs(input) {
630
+ if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
631
+ throw new Error("depth must be a non-negative integer");
632
+ const args = start("show", "structure", input);
633
+ at2(args, input.at);
895
634
  pushOptionalNumber(args, "--depth", input.depth);
896
- pushOptionalString(args, "--language", input.language);
897
- pushOptionalString(args, "--vision-mode", input.visionMode);
898
- pushOptionalString(args, "--vision-level", input.visionLevel);
899
- pushOptionalString(args, "--stdin-name", input.stdinName);
900
- args.push("--kdl");
635
+ pushOptionalString(args, "--kind", input.kind);
636
+ language(args, input);
637
+ if (input.outline)
638
+ args.push("--outline");
639
+ return finish(args);
640
+ }
641
+ function buildShowMetadataArgs(input) {
642
+ const args = start("show", "metadata", input);
643
+ language(args, input);
644
+ return finish(args);
645
+ }
646
+ function buildShowAssetsArgs(input) {
647
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
648
+ throw new Error("assets requires a page, node, or asset selector");
649
+ const args = start("show", "assets", input);
650
+ at2(args, input.at);
651
+ return finish(args);
652
+ }
653
+ function buildCheckSyntaxArgs(input) {
654
+ const args = start("check", "syntax", input);
655
+ language(args, input);
656
+ return finish(args);
657
+ }
658
+ function visionArgs(verb, object, input) {
659
+ if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
660
+ throw new Error("invalid vision level");
661
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
662
+ throw new Error("vision requires a document selector");
663
+ const args = start(verb, object, input);
664
+ at2(args, input.at);
665
+ pushOptionalString(args, "--level", input.level);
901
666
  return args;
902
667
  }
903
- function buildExtractArgs(input) {
904
- validateTarget("extract", input.path, input.stdinName);
905
- if (input.selector.length === 0)
906
- throw new Error("extract requires an asset selector");
907
- if (!input.selector.startsWith("asset:") || input.selector === "asset:") {
908
- throw new Error("extract requires an asset: selector");
909
- }
910
- if (input.output.length === 0 || input.output === "-") {
911
- throw new Error("extract output must be a file path");
912
- }
913
- const args = [];
914
- if (input.readseekDir !== undefined)
915
- args.push("--readseek-dir", input.readseekDir);
916
- args.push("--output", input.output, "extract", input.path, input.selector);
917
- pushOptionalString(args, "--stdin-name", input.stdinName);
918
- return args;
668
+ function buildGenerateCaptionArgs(input) {
669
+ const args = visionArgs("generate", "caption", input);
670
+ if (input.include !== undefined) {
671
+ if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
672
+ throw new Error("include requires text or objects");
673
+ args.push("--include", input.include.join(","));
674
+ }
675
+ return finish(args);
676
+ }
677
+ function buildDetectObjectsArgs(input) {
678
+ return finish(visionArgs("detect", "objects", input));
679
+ }
680
+ function buildRecognizeTextArgs(input) {
681
+ return finish(visionArgs("recognize", "text", input));
682
+ }
683
+ function buildFindNodesArgs(input) {
684
+ const hasPattern = Boolean(input.pattern);
685
+ const hasRule = Boolean(input.rule);
686
+ const hasContext = Boolean(input.context);
687
+ if (hasRule) {
688
+ if (hasPattern || hasContext || input.selector)
689
+ throw new Error("rule cannot be combined with pattern, context, or selector");
690
+ } else if (!hasPattern && !hasContext) {
691
+ throw new Error("find nodes requires pattern, context, or rule");
692
+ }
693
+ if (hasContext && !input.selector) {
694
+ throw new Error("context requires selector");
695
+ }
696
+ if (hasPattern && hasContext) {
697
+ throw new Error("pattern and context cannot be combined");
698
+ }
699
+ const args = start("find", "nodes", input);
700
+ pushOptionalString(args, "--pattern", input.pattern);
701
+ pushOptionalString(args, "--context", input.context);
702
+ pushOptionalString(args, "--selector", input.selector);
703
+ pushOptionalString(args, "--rule", input.rule);
704
+ language(args, input);
705
+ limit2(args, input.limit);
706
+ appendGitScopeArgs(args, input);
707
+ anchors(args, input);
708
+ return finish(args);
919
709
  }
920
- function buildSearchArgs(input) {
921
- const args = ["search", input.pattern];
922
- pushOptionalString(args, "--path", input.path);
923
- pushOptionalString(args, "--language", input.language);
924
- pushOptionalNumber(args, "--limit", input.limit);
710
+ function buildFindTextArgs(input) {
711
+ const args = start("find", "text", input);
712
+ args.push("--text", input.text);
713
+ limit2(args, input.limit);
925
714
  appendGitScopeArgs(args, input);
926
- args.push("--kdl");
927
- return args;
715
+ return finish(args);
928
716
  }
929
- function buildDefArgs(input) {
930
- const args = ["def", input.name];
931
- pushOptionalString(args, "--path", input.path);
932
- pushOptionalString(args, "--language", input.language);
717
+ function buildFindDefinitionsArgs(input) {
718
+ const args = start("find", "definitions", input);
719
+ args.push("--name", input.name);
720
+ language(args, input);
933
721
  appendGitScopeArgs(args, input);
934
- args.push("--kdl");
935
- return args;
722
+ anchors(args, input);
723
+ return finish(args);
936
724
  }
937
- function buildRefsArgs(input) {
938
- const validated = validateRefsNameVsScope(input);
725
+ function buildFindReferencesArgs(input) {
726
+ const validated = validateReferencesSelection(input);
939
727
  if (!validated.ok)
940
728
  throw new Error(validated.error);
941
- const args = ["refs"];
942
- if (input.name !== undefined)
943
- args.push(input.name);
944
- pushOptionalString(args, "--path", input.path);
945
- if (input.scope) {
946
- args.push("--scope");
947
- pushOptionalNumber(args, "--line", input.line);
948
- pushOptionalNumber(args, "--column", input.column);
949
- }
950
- pushOptionalString(args, "--language", input.language);
729
+ if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
730
+ throw new Error("references requires a line or hash selector");
731
+ const args = start("find", "references", input);
732
+ pushOptionalString(args, "--name", input.name);
733
+ at2(args, input.at);
734
+ language(args, input);
951
735
  appendGitScopeArgs(args, input);
952
- args.push("--kdl");
953
- return args;
736
+ anchors(args, input);
737
+ return finish(args);
954
738
  }
955
- function buildEditArgs(input) {
956
- const args = ["edit", input.path];
957
- appendApplyAndPlanHash(args, {
958
- apply: input.apply,
959
- planHash: input.planHash,
960
- flagOrder: input.flagOrder ?? "apply-first"
961
- });
962
- pushOptionalString(args, "--language", input.language);
739
+ function buildCheckRulesArgs(input) {
740
+ const args = start("check", "rules", input);
741
+ args.push("--rules", input.rules);
742
+ appendGitScopeArgs(args, input);
743
+ return finish(args);
744
+ }
745
+ function buildEditTextArgs(input) {
746
+ const args = start("edit", "text", input);
747
+ appendApplyAndPlanHash(args, input);
748
+ language(args, input);
749
+ appendGitScopeArgs(args, input);
963
750
  if (input.requestFromStdin)
964
751
  args.push("--edits", "-");
965
- args.push("--kdl");
966
- return args;
967
- }
968
- function buildRenameArgs(input) {
969
- const args = ["rename", input.path, "--at", buildLineLocation(input.line, input.column), "--to", input.to];
752
+ return finish(args);
753
+ }
754
+ function buildRenameSymbolArgs(input) {
755
+ if (!["line", "hash"].includes(parseSelector(input.at).kind))
756
+ throw new Error("rename requires a line or hash selector");
757
+ const args = start("rename", "symbol", input);
758
+ at2(args, input.at);
759
+ args.push("--to", input.to);
970
760
  if (input.workspace !== undefined)
971
761
  args.push(`--workspace=${input.workspace}`);
972
- appendApplyAndPlanHash(args, {
973
- apply: input.apply,
974
- planHash: input.planHash,
975
- flagOrder: input.flagOrder ?? "plan-hash-first"
976
- });
977
- pushOptionalString(args, "--language", input.language);
762
+ appendApplyAndPlanHash(args, { ...input, flagOrder: input.flagOrder ?? "plan-hash-first" });
763
+ language(args, input);
978
764
  appendGitScopeArgs(args, input);
979
- args.push("--kdl");
980
- return args;
981
- }
982
- // src/platform.ts
983
- import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
984
- import { createRequire } from "node:module";
985
- import { homedir } from "node:os";
986
- import path from "node:path";
987
- var READSEEK_BINARY_ENV = "READSEEK_BINARY";
988
- var READSEEK_PLATFORM_PACKAGES = {
989
- "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
990
- "linux-arm64": "@jarkkojs/readseek-linux-arm64",
991
- "linux-x64": "@jarkkojs/readseek-linux-x64",
992
- "win32-x64": "@jarkkojs/readseek-win32-x64"
993
- };
994
- var resolveFromPackage = createRequire(import.meta.url).resolve;
995
- function readSeekBinaryName(platform = process.platform) {
996
- if (platform === "linux")
997
- return "readseek.AppImage";
998
- return platform === "win32" ? "readseek.exe" : "readseek";
765
+ return finish(args);
999
766
  }
1000
- function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
1001
- return `${platform}-${arch}`;
1002
- }
1003
- function resolveReadSeekBinaryOverride(environment = process.env) {
1004
- const binaryPath = environment[READSEEK_BINARY_ENV];
1005
- if (binaryPath === undefined)
1006
- return null;
1007
- if (!path.isAbsolute(binaryPath)) {
1008
- throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
1009
- }
767
+ // src/client-output.ts
768
+ import { Type } from "@sinclair/typebox";
769
+ import { AssertError, Value } from "@sinclair/typebox/value";
770
+ var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
771
+ function parse(schema, value, what) {
1010
772
  try {
1011
- if (!statSync(binaryPath).isFile())
1012
- throw new Error("not a regular file");
1013
- accessSync(binaryPath, fsConstants.X_OK);
773
+ return Value.Parse(OPERATIONS, schema, value);
1014
774
  } catch (error) {
1015
- throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
1016
- cause: error
1017
- });
1018
- }
1019
- return binaryPath;
1020
- }
1021
- function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
1022
- const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
1023
- const platform = options.platform ?? process.platform;
1024
- const arch = options.arch ?? process.arch;
1025
- const key = readSeekPlatformKey(platform, arch);
1026
- const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
1027
- if (!platformPackage) {
1028
- const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
1029
- throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
1030
- }
1031
- const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
1032
- let packageJsonPath;
1033
- try {
1034
- const metaPackageJson = resolve(`${metaPackage}/package.json`);
1035
- const metaPackageDir = path.dirname(metaPackageJson);
1036
- packageJsonPath = resolve(`${platformPackage}/package.json`, {
1037
- paths: [metaPackageDir]
1038
- });
1039
- } catch {
1040
- packageJsonPath = resolve(`${platformPackage}/package.json`);
775
+ if (!(error instanceof AssertError))
776
+ throw error;
777
+ const detail = error.error;
778
+ throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
1041
779
  }
1042
- const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
1043
- accessSync(binaryPath, fsConstants.X_OK);
1044
- return binaryPath;
1045
780
  }
1046
- function resolveReadSeekCacheDir(subPath, options = {}) {
1047
- const env = options.environment ?? process.env;
1048
- const platform = options.platform ?? process.platform;
1049
- const home = options.homedir ? options.homedir() : homedir();
1050
- const cwd = options.cwd ?? process.cwd();
1051
- const localDirName = options.localDirName ?? ".readseek";
1052
- if (localDirName && localDirName.length > 0) {
1053
- const local = path.join(cwd, localDirName);
1054
- if (existsSync(local))
1055
- return local;
1056
- }
1057
- let base;
1058
- if (platform === "win32") {
1059
- base = env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
1060
- } else {
1061
- const xdg = env.XDG_CACHE_HOME?.trim();
1062
- base = xdg && path.isAbsolute(xdg) ? xdg : path.join(home, ".cache");
1063
- }
1064
- const pathModule = platform === "win32" ? path.win32 : path;
1065
- if (subPath === undefined) {
1066
- return pathModule.join(base, "readseek");
1067
- }
1068
- const parts = Array.isArray(subPath) ? subPath : [subPath];
1069
- return pathModule.join(base, ...parts);
781
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
782
+ var Str = Type.String();
783
+ var Bool = Type.Boolean();
784
+ var OptStr = Type.Optional(Str);
785
+ var OptInt = Type.Optional(Int);
786
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
787
+ var Hashlines = Type.Array(Hashline);
788
+ var ReadOutput = Type.Object({
789
+ file: Str,
790
+ language: Str,
791
+ line_count: Int,
792
+ file_hash: Str,
793
+ start_line: Int,
794
+ end_line: Int,
795
+ hashlines: Hashlines,
796
+ truncated: Type.Optional(Bool)
797
+ });
798
+ var SymbolOutput = Type.Object({
799
+ kind: Str,
800
+ name: Str,
801
+ qualified_name: OptStr,
802
+ start_line: Int,
803
+ end_line: Int,
804
+ start_hash: OptStr,
805
+ end_hash: OptStr,
806
+ start_anchor: OptStr,
807
+ end_anchor: OptStr
808
+ });
809
+ function mapNodeShape(child) {
810
+ return Type.Object({
811
+ kind: Str,
812
+ name: Str,
813
+ start_line: Int,
814
+ end_line: Int,
815
+ start_anchor: Str,
816
+ end_anchor: Str,
817
+ children: Type.Array(child)
818
+ });
1070
819
  }
1071
- // src/path.ts
1072
- import { lstatSync, realpathSync } from "node:fs";
1073
- import { homedir as homedir2 } from "node:os";
1074
- import path2 from "node:path";
1075
- var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
1076
- function canonicalPathKey(cwd, filePath) {
1077
- const resolved = path2.resolve(cwd, filePath);
1078
- try {
1079
- return realpathSync.native(resolved);
1080
- } catch {
1081
- return resolved;
1082
- }
820
+ function recursiveStructureNode() {
821
+ return Type.Recursive((self) => mapNodeShape(self));
1083
822
  }
1084
- function expandUserPath(filePath, homedirFn = homedir2) {
1085
- const withoutAt = filePath.startsWith("@") ? filePath.slice(1) : filePath;
1086
- const normalized = withoutAt.replace(UNICODE_SPACES, " ");
1087
- if (normalized === "~")
1088
- return homedirFn();
1089
- if (normalized.startsWith("~/"))
1090
- return homedirFn() + normalized.slice(1);
1091
- return normalized;
823
+ var StructureNode = typeof Type.Recursive === "function" ? recursiveStructureNode() : Type.Cyclic({ StructureNode: mapNodeShape(Type.Ref("StructureNode")) }, "StructureNode");
824
+ var StructureOutput = Type.Object({
825
+ file: Str,
826
+ language: Str,
827
+ line_count: Int,
828
+ file_hash: Str,
829
+ root: Type.Optional(StructureNode),
830
+ symbols: Type.Array(SymbolOutput)
831
+ });
832
+ var SearchSpan = {
833
+ start_byte: OptInt,
834
+ end_byte: OptInt,
835
+ text: OptStr,
836
+ start_line: Int,
837
+ end_line: Int,
838
+ start_hash: Str,
839
+ end_hash: Str,
840
+ hashlines: Hashlines
841
+ };
842
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
843
+ var SearchMatch = Type.Object({
844
+ pattern_index: Type.Integer({ default: 0 }),
845
+ ...SearchSpan,
846
+ captures: Type.Array(SearchCapture)
847
+ });
848
+ var SearchFileOutput = Type.Object({
849
+ file: Str,
850
+ language: Str,
851
+ file_hash: Str,
852
+ matches: Type.Array(SearchMatch)
853
+ });
854
+ var SearchEnvelope = Type.Object({
855
+ results: Type.Array(SearchFileOutput),
856
+ returned_matches: OptInt,
857
+ truncated: Type.Optional(Bool),
858
+ byte_basis: OptStr
859
+ });
860
+ var RefsEnvelope = Type.Object({
861
+ references: Type.Array(Type.Object({
862
+ file: Str,
863
+ line: Int,
864
+ column: Int,
865
+ line_hash: Str,
866
+ text: Str,
867
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
868
+ }))
869
+ });
870
+ var Diagnostic = Type.Object({
871
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
872
+ start_line: Int,
873
+ end_line: Int
874
+ });
875
+ var CheckEnvelope = Type.Object({
876
+ file_hash: OptStr,
877
+ error_count: Int,
878
+ missing_count: Int,
879
+ diagnostics: Type.Array(Diagnostic)
880
+ });
881
+ var DetectedObject = Type.Object({
882
+ label: Str,
883
+ bbox: Type.Tuple([Int, Int, Int, Int])
884
+ });
885
+ var SourceDetection = Type.Object({
886
+ kind: Type.Literal("source"),
887
+ type: Str,
888
+ file: Str,
889
+ language: Str,
890
+ engine: OptStr,
891
+ supported: Bool,
892
+ mime: OptStr,
893
+ syntax: OptStr
894
+ });
895
+ var ImageDetection = Type.Object({
896
+ kind: Type.Literal("image"),
897
+ type: Str,
898
+ file: Str,
899
+ mime: OptStr,
900
+ format: Str,
901
+ width: Int,
902
+ height: Int,
903
+ animated: Bool,
904
+ ocr: OptStr,
905
+ caption: OptStr,
906
+ objects: Type.Optional(Type.Array(DetectedObject))
907
+ });
908
+ var TextDetection = Type.Object({
909
+ kind: Type.Literal("text"),
910
+ type: Str,
911
+ file: Str,
912
+ mime: OptStr
913
+ });
914
+ var PdfDetection = Type.Object({
915
+ kind: Type.Literal("pdf"),
916
+ type: Type.Literal("application/pdf"),
917
+ file: Str,
918
+ mime: OptStr,
919
+ format: Type.Literal("pdf"),
920
+ pages: Int
921
+ });
922
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
923
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
924
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
925
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
926
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
927
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
928
+ var PdfMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
929
+ var DocumentCache = Type.Object({
930
+ content_hash: Str,
931
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
932
+ });
933
+ var Identifier = Type.Object({
934
+ text: Str,
935
+ start_column: Int,
936
+ end_column: Int,
937
+ start_byte: Int,
938
+ end_byte: Int
939
+ });
940
+ var CursorEnvelope = Type.Object({
941
+ file: Str,
942
+ language: Str,
943
+ engine: OptStr,
944
+ line_count: Int,
945
+ file_hash: Str,
946
+ line: Int,
947
+ column: Int,
948
+ line_hash: Str,
949
+ identifier: Type.Optional(Identifier),
950
+ symbol: Type.Optional(Type.Object({
951
+ name: Str,
952
+ kind: Str,
953
+ qualified_name: Str,
954
+ start_line: Int,
955
+ end_line: Int
956
+ }))
957
+ });
958
+ var EditOutputSchema = Type.Object({
959
+ file: Str,
960
+ before_hash: Str,
961
+ file_hash: Str,
962
+ plan_hash: Str,
963
+ applied: Bool,
964
+ changed: Bool,
965
+ edit_count: Int,
966
+ before_content: OptStr,
967
+ content: OptStr
968
+ });
969
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
970
+ var RenameEditSchema = Type.Object({
971
+ line: Int,
972
+ start_column: Int,
973
+ end_column: Int,
974
+ start_byte: Int,
975
+ end_byte: Int,
976
+ occurrence: Str,
977
+ line_hash: Str,
978
+ text: Str
979
+ });
980
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
981
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
982
+ var RenameFileOutputSchema = Type.Object({
983
+ file: Str,
984
+ language: Str,
985
+ engine: OptStr,
986
+ file_hash: Str,
987
+ conflicts: Conflicts,
988
+ edits: Edits
989
+ });
990
+ var RenameOutputSchema = Type.Object({
991
+ file: Str,
992
+ language: Str,
993
+ engine: OptStr,
994
+ file_hash: Str,
995
+ old_name: Str,
996
+ new_name: Str,
997
+ plan_hash: Str,
998
+ applied: Bool,
999
+ conflicts: Conflicts,
1000
+ edits: Edits,
1001
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1002
+ });
1003
+ var DefEnvelope = Type.Object({
1004
+ locations: Type.Array(Type.Object({
1005
+ file: Str,
1006
+ line: Int,
1007
+ column: Int,
1008
+ line_hash: Str,
1009
+ text: Str,
1010
+ kind: OptStr,
1011
+ name: OptStr,
1012
+ qualified_name: OptStr
1013
+ }))
1014
+ });
1015
+ function parseReadOutput(value) {
1016
+ return parse(ReadOutput, value, "read output");
1092
1017
  }
1093
- function resolveToCwd(filePath, cwd, homedirFn = homedir2) {
1094
- const expanded = expandUserPath(filePath, homedirFn);
1095
- return path2.isAbsolute(expanded) ? expanded : path2.resolve(cwd, expanded);
1018
+ function parseStructureOutput(value) {
1019
+ return parse(StructureOutput, value, "structure output");
1096
1020
  }
1097
- function isSubPath(parent, candidate) {
1098
- const relative = path2.relative(parent, candidate);
1099
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
1021
+ function parseSearchOutput(value) {
1022
+ const envelope = parse(SearchEnvelope, value, "search output");
1023
+ const results = envelope.results;
1024
+ if (envelope.returned_matches !== undefined)
1025
+ results.returned_matches = envelope.returned_matches;
1026
+ if (envelope.truncated !== undefined)
1027
+ results.truncated = envelope.truncated;
1028
+ if (envelope.byte_basis !== undefined)
1029
+ results.byte_basis = envelope.byte_basis;
1030
+ return results;
1100
1031
  }
1101
- function resolvePathWithMissingTail(filePath) {
1102
- let ancestor = filePath;
1103
- const missing = [];
1104
- while (true) {
1105
- try {
1106
- lstatSync(ancestor);
1107
- } catch (error) {
1108
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
1109
- throw error;
1110
- const parent = path2.dirname(ancestor);
1111
- if (parent === ancestor)
1112
- throw error;
1113
- missing.push(path2.basename(ancestor));
1114
- ancestor = parent;
1115
- continue;
1116
- }
1117
- return path2.join(realpathSync.native(ancestor), ...missing.reverse());
1118
- }
1032
+ function parseRefsOutput(value) {
1033
+ return parse(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1034
+ ...reference,
1035
+ enclosingSymbol: symbol?.qualified_name
1036
+ }));
1119
1037
  }
1120
- function resolveScopedPath(root, rawPath, fallback = ".") {
1121
- let canonicalRoot;
1122
- let candidate;
1123
- try {
1124
- canonicalRoot = realpathSync.native(path2.resolve(root));
1125
- candidate = resolvePathWithMissingTail(path2.resolve(canonicalRoot, rawPath ?? fallback));
1126
- } catch (error) {
1127
- const message = error instanceof Error ? error.message : String(error);
1128
- return { ok: false, error: `cannot resolve project path (${root}): ${rawPath ?? fallback}: ${message}` };
1038
+ function parseCheckOutput(value) {
1039
+ const output = parse(CheckEnvelope, value, "check output");
1040
+ return {
1041
+ file_hash: output.file_hash,
1042
+ errorCount: output.error_count,
1043
+ missingCount: output.missing_count,
1044
+ diagnostics: output.diagnostics
1045
+ };
1046
+ }
1047
+ function parseDetectOutput(value) {
1048
+ const output = value ?? {};
1049
+ if (output.type === "application/pdf") {
1050
+ return { kind: "pdf", ...parse(PdfDetectionInput, value, "detect output") };
1129
1051
  }
1130
- if (!isSubPath(canonicalRoot, candidate)) {
1131
- return { ok: false, error: `path escapes project root (${canonicalRoot}): ${rawPath ?? fallback}` };
1052
+ if (output.width !== undefined || output.height !== undefined) {
1053
+ return { kind: "image", ...parse(ImageDetectionInput, value, "detect output") };
1132
1054
  }
1133
- return { ok: true, path: candidate };
1134
- }
1135
- // src/error.ts
1136
- var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
1137
- function cleanReadSeekErrorText(stderr) {
1138
- return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
1139
- }
1140
- // src/kdl.ts
1141
- import { parse } from "@bgotink/kdl";
1142
- function convertNode(node) {
1143
- if (node.getTag() !== null)
1144
- throw new Error("KDL node annotations are not supported");
1145
- const properties = new Map;
1146
- for (const entry of node.entries) {
1147
- const name = entry.getName();
1148
- if (name === null)
1149
- throw new Error("KDL positional arguments are not supported");
1150
- if (properties.has(name))
1151
- throw new Error(`KDL duplicate property '${name}'`);
1152
- if (entry.getTag() !== null)
1153
- throw new Error("KDL value annotations are not supported");
1154
- const value = entry.getValue();
1155
- if (value === null)
1156
- throw new Error("KDL null values are not supported");
1157
- if (typeof value === "number" && !Number.isFinite(value)) {
1158
- throw new Error("KDL number out of range");
1159
- }
1160
- properties.set(name, value);
1055
+ if (output.language !== undefined) {
1056
+ return { kind: "source", ...parse(SourceDetectionInput, value, "detect output") };
1161
1057
  }
1058
+ return { kind: "text", ...parse(TextDetectionInput, value, "detect output") };
1059
+ }
1060
+ function parseTextContent(value) {
1061
+ const output = value ?? {};
1062
+ if (output.hashlines === undefined || output.start_line !== undefined)
1063
+ return parseReadOutput(value);
1064
+ const spanned = parse(UnspannedContent, value, "content");
1162
1065
  return {
1163
- name: node.getName(),
1164
- properties,
1165
- children: node.children?.nodes.map(convertNode) ?? []
1066
+ ...spanned,
1067
+ start_line: spanned.hashlines[0]?.line ?? 1,
1068
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1166
1069
  };
1167
1070
  }
1168
- function parseKdlDocument(text) {
1169
- const document = parse(text);
1170
- if (document.nodes.length !== 1)
1171
- throw new Error("KDL document must contain exactly one root node");
1172
- return convertNode(document.nodes[0]);
1071
+ function parseCursorOutput(value) {
1072
+ return parse(CursorEnvelope, value, "cursor output");
1173
1073
  }
1174
-
1175
- // src/output-kdl.ts
1176
- function properties(node) {
1177
- return Object.fromEntries(node.properties);
1074
+ function parsePdfMetadata(value) {
1075
+ if (!value || typeof value !== "object")
1076
+ return;
1077
+ if (value.format !== "pdf")
1078
+ return;
1079
+ const document = parse(PdfMetadata, value, "inspect metadata");
1080
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1178
1081
  }
1179
- function children(node, name) {
1180
- return node.children.filter((child) => child.name === name);
1082
+ function parseEditOutput(value) {
1083
+ return parse(EditOutputSchema, value, "edit output");
1181
1084
  }
1182
- function child(node, name) {
1183
- return node.children.find((candidate) => candidate.name === name);
1085
+ function parseRenameOutput(value) {
1086
+ return parse(RenameOutputSchema, value, "rename output");
1184
1087
  }
1185
- function requireProperty(node, name) {
1186
- const value = node.properties.get(name);
1187
- if (value === undefined)
1188
- throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1189
- return value;
1088
+ function parseDefOutput(value) {
1089
+ return parse(DefEnvelope, value, "def output").locations;
1090
+ }
1091
+ var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
1092
+ var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
1093
+ var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
1094
+ var ImageDescriptor = Type.Object({
1095
+ id: OptStr,
1096
+ kind: Type.Optional(Type.Literal("image")),
1097
+ content_identity: OptStr,
1098
+ path: Str,
1099
+ mime: Str,
1100
+ width: Int,
1101
+ height: Int,
1102
+ caption: OptStr,
1103
+ ocr: OptStr,
1104
+ objects: Type.Optional(Type.Array(DetectedObject)),
1105
+ ...Provenance
1106
+ });
1107
+ var DocumentNode = Type.Object({
1108
+ id: Str,
1109
+ kind: Str,
1110
+ parent_id: OptStr,
1111
+ title: OptStr,
1112
+ level: OptInt,
1113
+ column: OptInt,
1114
+ text: OptStr,
1115
+ ...Position
1116
+ });
1117
+ var DocumentAsset = Type.Object({
1118
+ id: Str,
1119
+ kind: Str,
1120
+ mime: Str,
1121
+ path: OptStr,
1122
+ content_identity: OptStr,
1123
+ bytes: OptInt,
1124
+ ...Position
1125
+ });
1126
+ var DocumentVision = Type.Object({
1127
+ asset_id: Str,
1128
+ mode: OptStr,
1129
+ caption: OptStr,
1130
+ ocr: OptStr,
1131
+ objects: Type.Optional(Type.Array(DetectedObject)),
1132
+ caption_requested: Type.Optional(Bool),
1133
+ objects_requested: Type.Optional(Bool),
1134
+ ocr_requested: Type.Optional(Bool),
1135
+ ...Provenance
1136
+ });
1137
+ var DocumentOutput = Type.Object({
1138
+ id: OptStr,
1139
+ format: Str,
1140
+ source: Str,
1141
+ title: OptStr,
1142
+ pages: Int,
1143
+ nodes: Type.Array(DocumentNode, { default: [] }),
1144
+ assets: Type.Array(DocumentAsset, { default: [] }),
1145
+ vision: Type.Array(DocumentVision, { default: [] })
1146
+ });
1147
+ var TextHit = Type.Object({
1148
+ line: Int,
1149
+ column: Int,
1150
+ text: Str,
1151
+ start_line: Int,
1152
+ end_line: Int,
1153
+ start_byte: Int,
1154
+ end_byte: Int,
1155
+ hashlines: Hashlines
1156
+ });
1157
+ var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
1158
+ var TextSearchOutput = Type.Object({
1159
+ text: Str,
1160
+ mode: Type.Literal("literal"),
1161
+ total_matches: Int,
1162
+ returned_matches: Int,
1163
+ truncated: Bool,
1164
+ results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
1165
+ documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1166
+ });
1167
+ var RuleFinding = Type.Object({
1168
+ name: Str,
1169
+ message: Str,
1170
+ path: Str,
1171
+ language: Str,
1172
+ start_byte: Int,
1173
+ end_byte: Int,
1174
+ line: Int,
1175
+ column: Int,
1176
+ end_line: Int,
1177
+ end_column: Int
1178
+ });
1179
+ var RulesOutput = Type.Object({
1180
+ complete: Bool,
1181
+ check_count: Int,
1182
+ file_count: Int,
1183
+ violation_count: Int,
1184
+ diagnostics: Type.Array(RuleFinding)
1185
+ });
1186
+ function parseDocumentOutput(value) {
1187
+ return parse(DocumentOutput, value, "document output");
1190
1188
  }
1191
- function line(node) {
1192
- const anchor = requireProperty(node, "anchor");
1193
- const content = requireProperty(node, "content");
1194
- if (typeof anchor !== "string" || typeof content !== "string") {
1195
- throw new Error("invalid readseek KDL line");
1196
- }
1197
- const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1198
- if (!match)
1199
- throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1189
+ function parseContentOutput(value) {
1190
+ const output = value;
1191
+ if (output?.format !== undefined)
1192
+ return parseDocumentOutput(value);
1193
+ if (!output || output.metadata === undefined || output.content === undefined)
1194
+ throw new Error("invalid readseek content: missing metadata or content");
1195
+ const metadata = parseDetectOutput(output.metadata);
1200
1196
  return {
1201
- line: Number(match[1]),
1202
- hash: match[2],
1203
- text: content
1197
+ metadata,
1198
+ content: metadata.kind === "image" ? parse(ImageDescriptor, output.content, "image descriptor") : parseTextContent(output.content),
1199
+ ...output.cursor !== undefined ? { cursor: parseCursorOutput(output.cursor) } : {}
1204
1200
  };
1205
1201
  }
1206
- function mapNode(node) {
1207
- return {
1208
- ...properties(node),
1209
- children: children(node, "node").map(mapNode)
1210
- };
1202
+ function parseStructureEnvelope(value) {
1203
+ const output = value;
1204
+ if (output?.format !== undefined)
1205
+ return parseDocumentOutput(value);
1206
+ if (!output || output.structure === undefined)
1207
+ throw new Error("invalid readseek structure: missing structure");
1208
+ return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1209
+ }
1210
+ function parseTextSearchOutput(value) {
1211
+ return parse(TextSearchOutput, value, "text search output");
1212
+ }
1213
+ function parseRulesOutput(value) {
1214
+ return parse(RulesOutput, value, "rules output");
1215
+ }
1216
+ var SourceAssetsOutput = Type.Object({ file: OptStr, metadata: Type.Optional(Detection), assets: Type.Array(DocumentAsset, { default: [] }) });
1217
+ function parseAssetsOutput(value) {
1218
+ const raw = value;
1219
+ if (raw?.format !== undefined)
1220
+ return parseDocumentOutput(value);
1221
+ if (!raw || raw.file === undefined && raw.metadata === undefined)
1222
+ throw new Error("invalid readseek assets output: missing file or metadata");
1223
+ return parse(SourceAssetsOutput, { ...raw, ...raw.metadata !== undefined ? { metadata: parseDetectOutput(raw.metadata) } : {} }, "assets output");
1211
1224
  }
1212
- function sourceContent(node) {
1213
- const result = properties(node);
1214
- result.hashlines = children(node, "line").map(line);
1215
- const objects = children(node, "object");
1216
- if (objects.length > 0) {
1217
- result.objects = objects.map((object) => ({
1218
- label: requireProperty(object, "label"),
1219
- bbox: [
1220
- requireProperty(object, "xmin"),
1221
- requireProperty(object, "ymin"),
1222
- requireProperty(object, "xmax"),
1223
- requireProperty(object, "ymax")
1224
- ]
1225
- }));
1226
- }
1225
+
1226
+ // src/edit-plan.ts
1227
+ import { Document, Node, format } from "@bgotink/kdl";
1228
+ function node(name, properties, children = []) {
1229
+ const result = Node.create(name);
1230
+ for (const [key, value] of Object.entries(properties))
1231
+ result.setProperty(key, value);
1232
+ if (children.length > 0)
1233
+ result.children = new Document(children);
1227
1234
  return result;
1228
1235
  }
1229
- function sourceMap(node) {
1230
- const root = child(node, "node");
1236
+ function editNode(edit) {
1237
+ if ("set_line" in edit) {
1238
+ const value = edit.set_line;
1239
+ return node("set-line", { anchor: value.anchor, text: value.new_text });
1240
+ }
1241
+ if ("replace_lines" in edit) {
1242
+ const value = edit.replace_lines;
1243
+ return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
1244
+ }
1245
+ if ("insert_after" in edit) {
1246
+ const value = edit.insert_after;
1247
+ return node("insert-after", { anchor: value.anchor, text: value.new_text });
1248
+ }
1249
+ if ("replace_symbol" in edit) {
1250
+ const value = edit.replace_symbol;
1251
+ return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
1252
+ }
1253
+ const value = edit.replace;
1254
+ return node("replace", {
1255
+ old: value.old_text,
1256
+ new: value.new_text,
1257
+ ...value.all === undefined ? {} : { all: value.all }
1258
+ });
1259
+ }
1260
+ function serializeEditPlan(edits) {
1261
+ const validation = validateExactlyOneEditVariant(edits);
1262
+ if (!validation.ok)
1263
+ throw new Error(validation.error);
1264
+ return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
1265
+ }
1266
+
1267
+ // src/client.ts
1268
+ function classifyReadSeekFailure(err) {
1269
+ const failure = err;
1270
+ const message = String(failure?.message ?? err);
1271
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1272
+ if (missing) {
1273
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1274
+ }
1275
+ return { code: "readseek-execution-error", message };
1276
+ }
1277
+ function runOptions(options, vision = false) {
1231
1278
  return {
1232
- ...properties(node),
1233
- ...root ? { root: mapNode(root) } : {},
1234
- symbols: children(node, "symbol").map(properties)
1279
+ signal: options.signal,
1280
+ stdin: options.stdin,
1281
+ timeoutMs: options.timeoutMs,
1282
+ cancelable: options.cancelable,
1283
+ ...vision ? { vision: true } : {}
1235
1284
  };
1236
1285
  }
1237
- function diagnostics(node) {
1286
+ function createReadSeekClient(runner) {
1238
1287
  return {
1239
- ...properties(node),
1240
- diagnostics: children(node, "diagnostic").map(properties)
1288
+ async showContent(path, options = {}) {
1289
+ const value = await runner.kdl(buildShowContentArgs({ ...options, path }), runOptions(options));
1290
+ return parseContentOutput(value);
1291
+ },
1292
+ async showStructure(path, options = {}) {
1293
+ const value = await runner.kdl(buildShowStructureArgs({ ...options, path }), runOptions(options));
1294
+ return parseStructureEnvelope(value);
1295
+ },
1296
+ async showMetadata(path, options = {}) {
1297
+ const value = await runner.kdl(buildShowMetadataArgs({ ...options, path }), runOptions(options));
1298
+ return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
1299
+ },
1300
+ async showAssets(path, options = {}) {
1301
+ const value = await runner.kdl(buildShowAssetsArgs({ ...options, path }), runOptions(options));
1302
+ return parseAssetsOutput(value);
1303
+ },
1304
+ async checkSyntax(path, options = {}) {
1305
+ const value = await runner.kdl(buildCheckSyntaxArgs({ ...options, path }), runOptions(options));
1306
+ return parseCheckOutput(value?.diagnostics ?? value);
1307
+ },
1308
+ async checkRules(path, options) {
1309
+ const value = await runner.kdl(buildCheckRulesArgs({ ...options, path }), runOptions(options));
1310
+ return parseRulesOutput(value);
1311
+ },
1312
+ async generateCaption(path, options = {}) {
1313
+ const value = await runner.kdl(buildGenerateCaptionArgs({ ...options, path }), runOptions(options, true));
1314
+ return parseContentOutput(value);
1315
+ },
1316
+ async detectObjects(path, options = {}) {
1317
+ const value = await runner.kdl(buildDetectObjectsArgs({ ...options, path }), runOptions(options, true));
1318
+ return parseContentOutput(value);
1319
+ },
1320
+ async recognizeText(path, options = {}) {
1321
+ const value = await runner.kdl(buildRecognizeTextArgs({ ...options, path }), runOptions(options, true));
1322
+ return parseContentOutput(value);
1323
+ },
1324
+ async findNodes(path, options) {
1325
+ const value = await runner.kdl(buildFindNodesArgs({ ...options, path }), runOptions(options));
1326
+ return parseSearchOutput(value);
1327
+ },
1328
+ async findText(path, options) {
1329
+ const value = await runner.kdl(buildFindTextArgs({ ...options, path }), runOptions(options));
1330
+ return parseTextSearchOutput(value);
1331
+ },
1332
+ async findDefinitions(path, options) {
1333
+ const value = await runner.kdl(buildFindDefinitionsArgs({ ...options, path }), runOptions(options));
1334
+ return parseDefOutput(value);
1335
+ },
1336
+ async findReferences(path, options) {
1337
+ const value = await runner.kdl(buildFindReferencesArgs({ ...options, path }), runOptions(options));
1338
+ return parseRefsOutput(value);
1339
+ },
1340
+ async editText(path, edits, options = {}) {
1341
+ const stdin = serializeEditPlan(edits);
1342
+ return parseEditOutput(await runner.kdl(buildEditTextArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1343
+ },
1344
+ async renameSymbol(path, options) {
1345
+ const value = await runner.kdl(buildRenameSymbolArgs({ ...options, path }), runOptions(options));
1346
+ return parseRenameOutput(value);
1347
+ }
1241
1348
  };
1242
1349
  }
1243
- function identity(node) {
1244
- const result = properties(node);
1245
- const identifier = child(node, "identifier");
1246
- const symbol = child(node, "symbol");
1247
- if (identifier)
1248
- result.identifier = properties(identifier);
1249
- if (symbol)
1250
- result.symbol = properties(symbol);
1251
- return result;
1252
- }
1253
- function sourceDocument(root) {
1254
- const result = {};
1255
- const metadata = child(root, "metadata");
1256
- const cache = child(root, "cache");
1257
- const content = child(root, "content");
1258
- const map = child(root, "map");
1259
- const diagnosticOutput = child(root, "diagnostics");
1260
- const identityOutput = child(root, "identity");
1261
- if (metadata)
1262
- result.metadata = properties(metadata);
1263
- if (cache)
1264
- result.cache = properties(cache);
1265
- if (content)
1266
- result.content = sourceContent(content);
1267
- if (map)
1268
- result.map = sourceMap(map);
1269
- if (diagnosticOutput)
1270
- result.diagnostics = diagnostics(diagnosticOutput);
1271
- if (identityOutput)
1272
- result.identity = identity(identityOutput);
1273
- return result;
1274
- }
1275
- function hit(node) {
1276
- return {
1277
- ...properties(node),
1278
- hashlines: children(node, "line").map(line),
1279
- captures: children(node, "capture").map((capture) => ({
1280
- ...properties(capture),
1281
- hashlines: children(capture, "line").map(line)
1282
- }))
1283
- };
1350
+ // src/platform.ts
1351
+ import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1352
+ import { createRequire } from "node:module";
1353
+ import { homedir } from "node:os";
1354
+ import path2 from "node:path";
1355
+ var READSEEK_BINARY_ENV = "READSEEK_BINARY";
1356
+ var READSEEK_PLATFORM_PACKAGES = {
1357
+ "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
1358
+ "linux-arm64": "@jarkkojs/readseek-linux-arm64",
1359
+ "linux-x64": "@jarkkojs/readseek-linux-x64",
1360
+ "win32-x64": "@jarkkojs/readseek-win32-x64"
1361
+ };
1362
+ var resolveFromPackage = createRequire(import.meta.url).resolve;
1363
+ function readSeekBinaryName(platform = process.platform) {
1364
+ if (platform === "linux")
1365
+ return "readseek.AppImage";
1366
+ return platform === "win32" ? "readseek.exe" : "readseek";
1284
1367
  }
1285
- function search(root) {
1286
- return {
1287
- ...properties(root),
1288
- results: children(root, "match").map((match) => ({
1289
- ...properties(match),
1290
- matches: children(match, "hit").map(hit)
1291
- })),
1292
- documents: children(root, "document").map((documentNode) => ({
1293
- ...properties(documentNode),
1294
- hits: children(documentNode, "hit").map(properties)
1295
- }))
1296
- };
1368
+ function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
1369
+ return `${platform}-${arch}`;
1297
1370
  }
1298
- function references(root) {
1299
- const parsed = children(root, "reference").map((reference) => {
1300
- const sourceLine = child(reference, "line");
1301
- if (!sourceLine)
1302
- throw new Error("invalid readseek KDL reference: missing line");
1303
- const parsedLine = line(sourceLine);
1304
- const symbol = child(reference, "symbol");
1305
- return {
1306
- ...properties(reference),
1307
- line_hash: parsedLine.hash,
1308
- text: parsedLine.text,
1309
- ...symbol ? { symbol: properties(symbol) } : {}
1310
- };
1311
- });
1312
- return {
1313
- references: parsed,
1314
- locations: parsed.map((reference) => {
1315
- const { symbol, ...location } = reference;
1316
- return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1317
- })
1318
- };
1371
+ function resolveReadSeekBinaryOverride(environment = process.env) {
1372
+ const binaryPath = environment[READSEEK_BINARY_ENV];
1373
+ if (binaryPath === undefined)
1374
+ return null;
1375
+ if (!path2.isAbsolute(binaryPath)) {
1376
+ throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
1377
+ }
1378
+ try {
1379
+ if (!statSync(binaryPath).isFile())
1380
+ throw new Error("not a regular file");
1381
+ accessSync(binaryPath, fsConstants.X_OK);
1382
+ } catch (error) {
1383
+ throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
1384
+ cause: error
1385
+ });
1386
+ }
1387
+ return binaryPath;
1319
1388
  }
1320
- function definitions(root) {
1321
- return {
1322
- locations: children(root, "definition").map((definition) => {
1323
- const symbol = child(definition, "symbol");
1324
- const sourceLine = child(definition, "line");
1325
- if (!symbol || !sourceLine) {
1326
- throw new Error("invalid readseek KDL definition: missing symbol or line");
1327
- }
1328
- const symbolProperties = properties(symbol);
1329
- const parsedLine = line(sourceLine);
1330
- const definitionColumn = definition.properties.get("column");
1331
- const nameByte = symbol.properties.get("name_byte");
1332
- const startByte = symbol.properties.get("start_byte");
1333
- let column = 1;
1334
- if (typeof definitionColumn === "number") {
1335
- column = definitionColumn;
1336
- } else if (typeof nameByte === "number" && typeof startByte === "number") {
1337
- column = nameByte - startByte + 1;
1338
- }
1339
- return {
1340
- file: requireProperty(definition, "file"),
1341
- line: parsedLine.line,
1342
- column,
1343
- line_hash: parsedLine.hash,
1344
- text: parsedLine.text,
1345
- kind: symbolProperties.kind,
1346
- name: symbolProperties.name,
1347
- qualified_name: symbolProperties.qualified_name
1348
- };
1349
- })
1350
- };
1389
+ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
1390
+ const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
1391
+ const platform = options.platform ?? process.platform;
1392
+ const arch = options.arch ?? process.arch;
1393
+ const key = readSeekPlatformKey(platform, arch);
1394
+ const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
1395
+ if (!platformPackage) {
1396
+ const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
1397
+ throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
1398
+ }
1399
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
1400
+ let packageJsonPath;
1401
+ try {
1402
+ const metaPackageJson = resolve(`${metaPackage}/package.json`);
1403
+ const metaPackageDir = path2.dirname(metaPackageJson);
1404
+ packageJsonPath = resolve(`${platformPackage}/package.json`, {
1405
+ paths: [metaPackageDir]
1406
+ });
1407
+ } catch {
1408
+ packageJsonPath = resolve(`${platformPackage}/package.json`);
1409
+ }
1410
+ const binaryPath = path2.join(path2.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
1411
+ accessSync(binaryPath, fsConstants.X_OK);
1412
+ return binaryPath;
1351
1413
  }
1352
- function edit(root) {
1353
- const result = properties(root);
1354
- for (const name of ["before_content", "content"]) {
1355
- const contentNode = child(root, name);
1356
- if (contentNode)
1357
- result[name] = requireProperty(contentNode, "content");
1414
+ function resolveReadSeekCacheDir(subPath, options = {}) {
1415
+ const env = options.environment ?? process.env;
1416
+ const platform = options.platform ?? process.platform;
1417
+ const home = options.homedir ? options.homedir() : homedir();
1418
+ const cwd = options.cwd ?? process.cwd();
1419
+ const localDirName = options.localDirName ?? ".readseek";
1420
+ if (localDirName && localDirName.length > 0) {
1421
+ const local = path2.join(cwd, localDirName);
1422
+ if (existsSync(local))
1423
+ return local;
1358
1424
  }
1359
- return result;
1425
+ let base;
1426
+ if (platform === "win32") {
1427
+ base = env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
1428
+ } else {
1429
+ const xdg = env.XDG_CACHE_HOME?.trim();
1430
+ base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(home, ".cache");
1431
+ }
1432
+ const pathModule = platform === "win32" ? path2.win32 : path2;
1433
+ if (subPath === undefined) {
1434
+ return pathModule.join(base, "readseek");
1435
+ }
1436
+ const parts = Array.isArray(subPath) ? subPath : [subPath];
1437
+ return pathModule.join(base, ...parts);
1360
1438
  }
1361
- function renameEdit(node) {
1362
- const sourceLine = child(node, "line");
1363
- if (!sourceLine)
1364
- throw new Error("invalid readseek KDL rename edit: missing line");
1365
- return {
1366
- ...properties(node),
1367
- text: requireProperty(sourceLine, "content")
1368
- };
1439
+ // src/path.ts
1440
+ import { lstatSync, realpathSync } from "node:fs";
1441
+ import { homedir as homedir2 } from "node:os";
1442
+ import path3 from "node:path";
1443
+ var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
1444
+ function canonicalPathKey(cwd, filePath) {
1445
+ const resolved = path3.resolve(cwd, filePath);
1446
+ try {
1447
+ return realpathSync.native(resolved);
1448
+ } catch {
1449
+ return resolved;
1450
+ }
1369
1451
  }
1370
- function renameFile(node) {
1371
- return {
1372
- ...properties(node),
1373
- edits: children(node, "edit").map(renameEdit),
1374
- conflicts: children(node, "conflict").map(properties)
1375
- };
1452
+ function expandUserPath(filePath, homedirFn = homedir2) {
1453
+ const withoutAt = filePath.startsWith("@") ? filePath.slice(1) : filePath;
1454
+ const normalized = withoutAt.replace(UNICODE_SPACES, " ");
1455
+ if (normalized === "~")
1456
+ return homedirFn();
1457
+ if (normalized.startsWith("~/"))
1458
+ return homedirFn() + normalized.slice(1);
1459
+ return normalized;
1376
1460
  }
1377
- function rename(root) {
1378
- return {
1379
- ...renameFile(root),
1380
- others: children(root, "other").map(renameFile)
1381
- };
1461
+ function resolveToCwd(filePath, cwd, homedirFn = homedir2) {
1462
+ const expanded = expandUserPath(filePath, homedirFn);
1463
+ return path3.isAbsolute(expanded) ? expanded : path3.resolve(cwd, expanded);
1382
1464
  }
1383
- function genericNode(node) {
1384
- const result = properties(node);
1385
- const textNode = child(node, "text");
1386
- const bbox = child(node, "bbox");
1387
- if (textNode)
1388
- result.text = requireProperty(textNode, "content");
1389
- if (bbox)
1390
- result.bbox = properties(bbox);
1391
- const objects = children(node, "object");
1392
- if (objects.length > 0)
1393
- result.objects = objects.map(genericNode);
1394
- return result;
1465
+ function isSubPath(parent, candidate) {
1466
+ const relative = path3.relative(parent, candidate);
1467
+ return relative === "" || !relative.startsWith(`..${path3.sep}`) && relative !== ".." && !path3.isAbsolute(relative);
1395
1468
  }
1396
- function pdfDocument(root) {
1397
- return {
1398
- ...properties(root),
1399
- nodes: children(root, "node").map(genericNode),
1400
- assets: children(root, "asset").map(genericNode),
1401
- vision: children(root, "vision").map(genericNode)
1402
- };
1469
+ function resolvePathWithMissingTail(filePath) {
1470
+ let ancestor = filePath;
1471
+ const missing = [];
1472
+ while (true) {
1473
+ try {
1474
+ lstatSync(ancestor);
1475
+ } catch (error) {
1476
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
1477
+ throw error;
1478
+ const parent = path3.dirname(ancestor);
1479
+ if (parent === ancestor)
1480
+ throw error;
1481
+ missing.push(path3.basename(ancestor));
1482
+ ancestor = parent;
1483
+ continue;
1484
+ }
1485
+ return path3.join(realpathSync.native(ancestor), ...missing.reverse());
1486
+ }
1403
1487
  }
1404
- function parseReadSeekKdl(text) {
1405
- const root = parseKdlDocument(text);
1406
- switch (root.name) {
1407
- case "document":
1408
- return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1409
- case "search":
1410
- return search(root);
1411
- case "check":
1412
- return diagnostics(root);
1413
- case "refs":
1414
- return references(root);
1415
- case "def":
1416
- return definitions(root);
1417
- case "edit":
1418
- return edit(root);
1419
- case "edit-batch":
1420
- return { ...properties(root), files: children(root, "file").map(edit) };
1421
- case "rename":
1422
- return rename(root);
1423
- default:
1424
- throw new Error(`unsupported readseek KDL root: ${root.name}`);
1425
- }
1426
- }
1427
- // src/process.ts
1428
- var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1429
- var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1430
- function readSeekProcessEnvironment() {
1431
- return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1432
- }
1433
- function resolveProcessLimits(options = {}) {
1434
- return {
1435
- timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1436
- maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1437
- cancelable: options.cancelable !== false
1438
- };
1439
- }
1440
- function readSeekCancellationError(signal) {
1441
- const reason = signal?.reason;
1442
- return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
1443
- }
1444
- function failureFromProcessResult(result, options = {}) {
1445
- if (options.overflow) {
1446
- return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1488
+ function resolveScopedPath(root, rawPath, fallback = ".") {
1489
+ let canonicalRoot;
1490
+ let candidate;
1491
+ try {
1492
+ canonicalRoot = realpathSync.native(path3.resolve(root));
1493
+ candidate = resolvePathWithMissingTail(path3.resolve(canonicalRoot, rawPath ?? fallback));
1494
+ } catch (error) {
1495
+ const message = error instanceof Error ? error.message : String(error);
1496
+ return { ok: false, error: `cannot resolve project path (${root}): ${rawPath ?? fallback}: ${message}` };
1447
1497
  }
1448
- if (options.timedOut) {
1449
- return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1498
+ if (!isSubPath(canonicalRoot, candidate)) {
1499
+ return { ok: false, error: `path escapes project root (${canonicalRoot}): ${rawPath ?? fallback}` };
1450
1500
  }
1451
- const cleaned = cleanReadSeekErrorText(result.stderr);
1452
- if (cleaned)
1453
- return new Error(cleaned);
1454
- if (result.signal)
1455
- return new Error(`readseek killed by signal ${result.signal}`);
1456
- return new Error(`readseek exited with status ${result.exitCode}`);
1457
- }
1458
- function ensureSuccessfulStdout(result) {
1459
- if (result.exitCode === 0)
1460
- return result.stdout;
1461
- throw failureFromProcessResult(result);
1501
+ return { ok: true, path: candidate };
1462
1502
  }
1463
- function parseSuccessfulKdl(result) {
1464
- return parseReadSeekKdl(ensureSuccessfulStdout(result));
1503
+ // src/error.ts
1504
+ var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
1505
+ function cleanReadSeekErrorText(stderr) {
1506
+ return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
1465
1507
  }
1466
- // src/process-node.ts
1467
- import { spawn } from "node:child_process";
1468
- import { StringDecoder } from "node:string_decoder";
1469
- function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
1470
- const decoder = new StringDecoder("utf8");
1471
- let text = "";
1472
- let bytes = 0;
1473
- let overflowed = false;
1474
- stream.on("data", (chunk) => {
1475
- if (overflowed)
1476
- return;
1477
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1478
- bytes += buffer.length;
1479
- if (bytes > maxOutputBytes) {
1480
- overflowed = true;
1481
- onOverflow();
1482
- return;
1508
+ // src/kdl.ts
1509
+ import { parse as parse2 } from "@bgotink/kdl";
1510
+ function convertNode(node) {
1511
+ if (node.getTag() !== null)
1512
+ throw new Error("KDL node annotations are not supported");
1513
+ const properties = new Map;
1514
+ for (const entry of node.entries) {
1515
+ const name = entry.getName();
1516
+ if (name === null)
1517
+ throw new Error("KDL positional arguments are not supported");
1518
+ if (properties.has(name))
1519
+ throw new Error(`KDL duplicate property '${name}'`);
1520
+ if (entry.getTag() !== null)
1521
+ throw new Error("KDL value annotations are not supported");
1522
+ const value = entry.getValue();
1523
+ if (value === null)
1524
+ throw new Error("KDL null values are not supported");
1525
+ if (typeof value === "number" && !Number.isFinite(value)) {
1526
+ throw new Error("KDL number out of range");
1483
1527
  }
1484
- text += decoder.write(buffer);
1485
- });
1528
+ properties.set(name, value);
1529
+ }
1486
1530
  return {
1487
- text: () => text + decoder.end()
1531
+ name: node.getName(),
1532
+ properties,
1533
+ children: node.children?.nodes.map(convertNode) ?? []
1488
1534
  };
1489
1535
  }
1490
- async function runReadSeekNode(binaryPath, args, options = {}) {
1491
- const limits = resolveProcessLimits(options);
1492
- if (limits.cancelable && options.signal?.aborted) {
1493
- throw readSeekCancellationError(options.signal);
1494
- }
1495
- return await new Promise((resolve, reject) => {
1496
- let settled = false;
1497
- let child;
1498
- let timer;
1499
- const finish = (result) => {
1500
- if (settled)
1501
- return;
1502
- settled = true;
1503
- if (timer !== undefined)
1504
- clearTimeout(timer);
1505
- options.signal?.removeEventListener("abort", onAbort);
1506
- resolve(result);
1507
- };
1508
- const fail = (error) => {
1509
- if (settled)
1510
- return;
1511
- settled = true;
1512
- if (timer !== undefined)
1513
- clearTimeout(timer);
1514
- options.signal?.removeEventListener("abort", onAbort);
1515
- reject(error instanceof Error ? error : new Error(String(error)));
1516
- };
1517
- const terminate = (error) => {
1518
- if (settled)
1519
- return;
1520
- fail(error);
1521
- try {
1522
- child.kill("SIGKILL");
1523
- } catch {}
1524
- };
1525
- const onAbort = () => {
1526
- terminate(readSeekCancellationError(options.signal));
1527
- };
1528
- try {
1529
- child = spawn(binaryPath, [...args], {
1530
- cwd: options.cwd,
1531
- env: readSeekProcessEnvironment(),
1532
- shell: false,
1533
- stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
1534
- windowsHide: true
1535
- });
1536
- } catch (error) {
1537
- fail(error);
1538
- return;
1539
- }
1540
- const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
1541
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
1542
- });
1543
- const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
1544
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
1545
- });
1546
- timer = setTimeout(() => {
1547
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1548
- }, limits.timeoutMs);
1549
- timer.unref?.();
1550
- if (limits.cancelable && options.signal) {
1551
- options.signal.addEventListener("abort", onAbort, { once: true });
1552
- }
1553
- if (options.stdin !== undefined) {
1554
- child.stdin.on("error", (error) => {
1555
- if (error.code !== "EPIPE")
1556
- fail(error);
1557
- });
1558
- child.stdin.end(options.stdin, "utf8");
1559
- }
1560
- child.on("error", fail);
1561
- child.on("close", (code, signal) => {
1562
- finish({
1563
- stdout: stdout.text(),
1564
- stderr: stderr.text(),
1565
- exitCode: code,
1566
- signal
1567
- });
1568
- });
1569
- });
1536
+ function parseKdlDocument(text) {
1537
+ const document = parse2(text);
1538
+ if (document.nodes.length !== 1)
1539
+ throw new Error("KDL document must contain exactly one root node");
1540
+ return convertNode(document.nodes[0]);
1570
1541
  }
1571
- async function runReadSeekNodeText(binaryPath, args, options = {}) {
1572
- return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
1542
+
1543
+ // src/output-kdl.ts
1544
+ function properties(node) {
1545
+ return Object.fromEntries(node.properties);
1573
1546
  }
1574
- async function runReadSeekNodeKdl(binaryPath, args, options = {}) {
1575
- return parseSuccessfulKdl(await runReadSeekNode(binaryPath, args, options));
1547
+ function children(node, name) {
1548
+ return node.children.filter((child) => child.name === name);
1576
1549
  }
1577
- // src/process-bun.ts
1578
- function getBunSpawn() {
1579
- const bunGlobal = globalThis;
1580
- const spawn = bunGlobal.Bun?.spawn;
1581
- if (typeof spawn !== "function") {
1582
- throw new Error("Bun.spawn is unavailable in this runtime");
1583
- }
1584
- return spawn;
1550
+ function child(node, name) {
1551
+ return node.children.find((candidate) => candidate.name === name);
1585
1552
  }
1586
- async function readBoundedText(stream, streamName, maxOutputBytes, onOverflow) {
1587
- const reader = stream.getReader();
1588
- const chunks = [];
1589
- let totalBytes = 0;
1590
- try {
1591
- while (true) {
1592
- const { done, value } = await reader.read();
1593
- if (done)
1594
- break;
1595
- totalBytes += value.byteLength;
1596
- if (totalBytes > maxOutputBytes) {
1597
- onOverflow();
1598
- await reader.cancel().catch(() => {
1599
- return;
1600
- });
1601
- throw failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: streamName, maxOutputBytes });
1602
- }
1603
- chunks.push(value);
1604
- }
1605
- } finally {
1606
- reader.releaseLock();
1607
- }
1608
- const bytes = new Uint8Array(totalBytes);
1609
- let offset = 0;
1610
- for (const chunk of chunks) {
1611
- bytes.set(chunk, offset);
1612
- offset += chunk.byteLength;
1613
- }
1614
- return new TextDecoder().decode(bytes);
1553
+ function requireProperty(node, name) {
1554
+ const value = node.properties.get(name);
1555
+ if (value === undefined)
1556
+ throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1557
+ return value;
1615
1558
  }
1616
- async function runReadSeekBun(binaryPath, args, options = {}) {
1617
- const limits = resolveProcessLimits(options);
1618
- if (limits.cancelable && options.signal?.aborted) {
1619
- throw readSeekCancellationError(options.signal);
1559
+ function line(node) {
1560
+ const anchor = requireProperty(node, "anchor");
1561
+ const content = requireProperty(node, "content");
1562
+ if (typeof anchor !== "string" || typeof content !== "string") {
1563
+ throw new Error("invalid readseek KDL line");
1620
1564
  }
1621
- const spawn = getBunSpawn();
1622
- const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
1623
- const spawnOptions = {
1624
- cwd: options.cwd,
1625
- env: readSeekProcessEnvironment(),
1626
- stderr: "pipe",
1627
- stdin,
1628
- stdout: "pipe"
1629
- };
1630
- const child = spawn([binaryPath, ...args], limits.cancelable && options.signal ? { ...spawnOptions, killSignal: "SIGKILL", signal: options.signal } : spawnOptions);
1631
- const killForOverflow = () => {
1632
- try {
1633
- child.kill("SIGKILL");
1634
- } catch {}
1635
- };
1636
- let timeout;
1637
- const timeoutResult = new Promise((_resolve, reject) => {
1638
- timeout = setTimeout(() => {
1639
- try {
1640
- child.kill("SIGKILL");
1641
- } catch {}
1642
- reject(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1643
- }, limits.timeoutMs);
1644
- timeout.unref?.();
1645
- });
1646
- const pending = Promise.all([
1647
- readBoundedText(child.stdout, "stdout", limits.maxOutputBytes, killForOverflow),
1648
- readBoundedText(child.stderr, "stderr", limits.maxOutputBytes, killForOverflow),
1649
- child.exited
1650
- ]);
1651
- try {
1652
- const [stdout, stderr, exitCode] = await Promise.race([pending, timeoutResult]);
1653
- if (limits.cancelable && options.signal?.aborted)
1654
- throw readSeekCancellationError(options.signal);
1655
- return { stdout, stderr, exitCode, signal: null };
1656
- } finally {
1657
- if (timeout !== undefined)
1658
- clearTimeout(timeout);
1659
- pending.catch(() => {
1660
- return;
1661
- });
1662
- }
1663
- }
1664
- async function runReadSeekBunText(binaryPath, args, options = {}) {
1665
- return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
1666
- }
1667
- async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1668
- return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options));
1669
- }
1670
- // src/client-output.ts
1671
- import { Type } from "@sinclair/typebox";
1672
- import { AssertError, Value } from "@sinclair/typebox/value";
1673
- var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
1674
- function parse2(schema, value, what) {
1675
- try {
1676
- return Value.Parse(OPERATIONS, schema, value);
1677
- } catch (error) {
1678
- if (!(error instanceof AssertError))
1679
- throw error;
1680
- const detail = error.error;
1681
- throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
1682
- }
1683
- }
1684
- var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1685
- var Str = Type.String();
1686
- var Bool = Type.Boolean();
1687
- var OptStr = Type.Optional(Str);
1688
- var OptInt = Type.Optional(Int);
1689
- var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1690
- var Hashlines = Type.Array(Hashline);
1691
- var ReadOutput = Type.Object({
1692
- file: Str,
1693
- language: Str,
1694
- line_count: Int,
1695
- file_hash: Str,
1696
- start_line: Int,
1697
- end_line: Int,
1698
- hashlines: Hashlines
1699
- });
1700
- var SymbolOutput = Type.Object({
1701
- kind: Str,
1702
- name: Str,
1703
- qualified_name: OptStr,
1704
- start_line: Int,
1705
- end_line: Int,
1706
- start_hash: OptStr,
1707
- end_hash: OptStr,
1708
- start_anchor: OptStr,
1709
- end_anchor: OptStr
1710
- });
1711
- function mapNodeShape(child) {
1712
- return Type.Object({
1713
- kind: Str,
1714
- name: Str,
1715
- start_line: Int,
1716
- end_line: Int,
1717
- start_anchor: Str,
1718
- end_anchor: Str,
1719
- children: Type.Array(child)
1720
- });
1721
- }
1722
- function recursiveMapNode() {
1723
- return Type.Recursive((self) => mapNodeShape(self));
1724
- }
1725
- var MapNode = typeof Type.Recursive === "function" ? recursiveMapNode() : Type.Cyclic({ MapNode: mapNodeShape(Type.Ref("MapNode")) }, "MapNode");
1726
- var MapOutput = Type.Object({
1727
- file: Str,
1728
- language: Str,
1729
- line_count: Int,
1730
- file_hash: Str,
1731
- root: Type.Optional(MapNode),
1732
- symbols: Type.Array(SymbolOutput)
1733
- });
1734
- var SearchSpan = {
1735
- start_byte: OptInt,
1736
- end_byte: OptInt,
1737
- text: OptStr,
1738
- start_line: Int,
1739
- end_line: Int,
1740
- start_hash: Str,
1741
- end_hash: Str,
1742
- hashlines: Hashlines
1743
- };
1744
- var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1745
- var SearchMatch = Type.Object({
1746
- pattern_index: Type.Integer({ default: 0 }),
1747
- ...SearchSpan,
1748
- captures: Type.Array(SearchCapture)
1749
- });
1750
- var SearchFileOutput = Type.Object({
1751
- file: Str,
1752
- language: Str,
1753
- file_hash: Str,
1754
- matches: Type.Array(SearchMatch)
1755
- });
1756
- var SearchEnvelope = Type.Object({
1757
- results: Type.Array(SearchFileOutput),
1758
- returned_matches: OptInt,
1759
- truncated: Type.Optional(Bool),
1760
- byte_basis: OptStr
1761
- });
1762
- var RefsEnvelope = Type.Object({
1763
- references: Type.Array(Type.Object({
1764
- file: Str,
1765
- line: Int,
1766
- column: Int,
1767
- line_hash: Str,
1768
- text: Str,
1769
- symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1770
- }))
1771
- });
1772
- var Diagnostic = Type.Object({
1773
- kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1774
- start_line: Int,
1775
- end_line: Int
1776
- });
1777
- var CheckEnvelope = Type.Object({
1778
- file_hash: OptStr,
1779
- error_count: Int,
1780
- missing_count: Int,
1781
- diagnostics: Type.Array(Diagnostic)
1782
- });
1783
- var DetectedObject = Type.Object({
1784
- label: Str,
1785
- bbox: Type.Tuple([Int, Int, Int, Int])
1786
- });
1787
- var PreparedImage = Type.Object({
1788
- mime: Str,
1789
- encoding: Type.Literal("base64"),
1790
- data: Str
1791
- });
1792
- var SourceDetection = Type.Object({
1793
- kind: Type.Literal("source"),
1794
- type: Str,
1795
- file: Str,
1796
- language: Str,
1797
- engine: OptStr,
1798
- supported: Bool,
1799
- mime: OptStr,
1800
- syntax: OptStr
1801
- });
1802
- var ImageDetection = Type.Object({
1803
- kind: Type.Literal("image"),
1804
- type: Str,
1805
- file: Str,
1806
- mime: OptStr,
1807
- format: Str,
1808
- width: Int,
1809
- height: Int,
1810
- animated: Bool,
1811
- encoding: Type.Optional(Type.Literal("base64")),
1812
- data: OptStr,
1813
- ocr: OptStr,
1814
- caption: OptStr,
1815
- objects: Type.Optional(Type.Array(DetectedObject))
1816
- });
1817
- var TextDetection = Type.Object({
1818
- kind: Type.Literal("text"),
1819
- type: Str,
1820
- file: Str,
1821
- mime: OptStr
1822
- });
1823
- var PdfDetection = Type.Object({
1824
- kind: Type.Literal("pdf"),
1825
- type: Type.Literal("application/pdf"),
1826
- file: Str,
1827
- mime: OptStr,
1828
- format: Type.Literal("pdf"),
1829
- pages: Int
1830
- });
1831
- var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1832
- var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1833
- var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1834
- var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1835
- var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1836
- var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1837
- var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1838
- var DocumentCache = Type.Object({
1839
- content_hash: Str,
1840
- state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1841
- });
1842
- var Identifier = Type.Object({
1843
- text: Str,
1844
- start_column: Int,
1845
- end_column: Int,
1846
- start_byte: Int,
1847
- end_byte: Int
1848
- });
1849
- var IdentifyEnvelope = Type.Object({
1850
- file: Str,
1851
- language: Str,
1852
- engine: OptStr,
1853
- line_count: Int,
1854
- file_hash: Str,
1855
- line: Int,
1856
- column: Int,
1857
- line_hash: Str,
1858
- identifier: Type.Optional(Identifier),
1859
- symbol: Type.Optional(Type.Object({
1860
- name: Str,
1861
- kind: Str,
1862
- qualified_name: Str,
1863
- start_line: Int,
1864
- end_line: Int
1865
- }))
1866
- });
1867
- var EditOutputSchema = Type.Object({
1868
- file: Str,
1869
- before_hash: Str,
1870
- file_hash: Str,
1871
- plan_hash: Str,
1872
- applied: Bool,
1873
- changed: Bool,
1874
- edit_count: Int,
1875
- before_content: OptStr,
1876
- content: OptStr
1877
- });
1878
- var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1879
- var RenameEditSchema = Type.Object({
1880
- line: Int,
1881
- start_column: Int,
1882
- end_column: Int,
1883
- start_byte: Int,
1884
- end_byte: Int,
1885
- occurrence: Str,
1886
- line_hash: Str,
1887
- text: Str
1888
- });
1889
- var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1890
- var Edits = Type.Array(RenameEditSchema, { default: [] });
1891
- var RenameFileOutputSchema = Type.Object({
1892
- file: Str,
1893
- language: Str,
1894
- engine: OptStr,
1895
- file_hash: Str,
1896
- conflicts: Conflicts,
1897
- edits: Edits
1898
- });
1899
- var RenameOutputSchema = Type.Object({
1900
- file: Str,
1901
- language: Str,
1902
- engine: OptStr,
1903
- file_hash: Str,
1904
- old_name: Str,
1905
- new_name: Str,
1906
- plan_hash: Str,
1907
- applied: Bool,
1908
- conflicts: Conflicts,
1909
- edits: Edits,
1910
- others: Type.Array(RenameFileOutputSchema, { default: [] })
1911
- });
1912
- var DefEnvelope = Type.Object({
1913
- locations: Type.Array(Type.Object({
1914
- file: Str,
1915
- line: Int,
1916
- column: Int,
1917
- line_hash: Str,
1918
- text: Str,
1919
- kind: OptStr,
1920
- name: OptStr,
1921
- qualified_name: OptStr
1922
- }))
1923
- });
1924
- function parseReadOutput(value) {
1925
- return parse2(ReadOutput, value, "read output");
1565
+ const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1566
+ if (!match)
1567
+ throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1568
+ return {
1569
+ line: Number(match[1]),
1570
+ hash: match[2],
1571
+ text: content
1572
+ };
1926
1573
  }
1927
- function parseMapOutput(value) {
1928
- return parse2(MapOutput, value, "map output");
1574
+ function mapNode(node) {
1575
+ return {
1576
+ ...properties(node),
1577
+ children: children(node, "node").map(mapNode)
1578
+ };
1929
1579
  }
1930
- function parseSearchOutput(value) {
1931
- const envelope = parse2(SearchEnvelope, value, "search output");
1932
- const results = envelope.results;
1933
- if (envelope.returned_matches !== undefined)
1934
- results.returned_matches = envelope.returned_matches;
1935
- if (envelope.truncated !== undefined)
1936
- results.truncated = envelope.truncated;
1937
- if (envelope.byte_basis !== undefined)
1938
- results.byte_basis = envelope.byte_basis;
1939
- return results;
1580
+ function sourceContent(node) {
1581
+ const result = properties(node);
1582
+ result.hashlines = children(node, "line").map(line);
1583
+ const objects = children(node, "object");
1584
+ if (objects.length > 0) {
1585
+ result.objects = objects.map((object) => ({
1586
+ label: requireProperty(object, "label"),
1587
+ bbox: [
1588
+ requireProperty(object, "xmin"),
1589
+ requireProperty(object, "ymin"),
1590
+ requireProperty(object, "xmax"),
1591
+ requireProperty(object, "ymax")
1592
+ ]
1593
+ }));
1594
+ }
1595
+ return result;
1940
1596
  }
1941
- function parseRefsOutput(value) {
1942
- return parse2(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1943
- ...reference,
1944
- enclosingSymbol: symbol?.qualified_name
1945
- }));
1597
+ function sourceMap(node) {
1598
+ const root = child(node, "node");
1599
+ return {
1600
+ ...properties(node),
1601
+ ...root ? { root: mapNode(root) } : {},
1602
+ symbols: children(node, "symbol").map(properties)
1603
+ };
1946
1604
  }
1947
- function parseCheckOutput(value) {
1948
- const output = parse2(CheckEnvelope, value, "check output");
1605
+ function diagnostics(node) {
1949
1606
  return {
1950
- file_hash: output.file_hash,
1951
- errorCount: output.error_count,
1952
- missingCount: output.missing_count,
1953
- diagnostics: output.diagnostics
1607
+ ...properties(node),
1608
+ diagnostics: children(node, "diagnostic").map(properties)
1954
1609
  };
1955
1610
  }
1956
- function parseDetectOutput(value) {
1957
- const output = value ?? {};
1958
- if (output.type === "application/pdf") {
1959
- return { kind: "pdf", ...parse2(PdfDetectionInput, value, "detect output") };
1960
- }
1961
- if (output.width !== undefined || output.height !== undefined) {
1962
- return { kind: "image", ...parse2(ImageDetectionInput, value, "detect output") };
1963
- }
1964
- if (output.language !== undefined) {
1965
- return { kind: "source", ...parse2(SourceDetectionInput, value, "detect output") };
1611
+ function identity(node) {
1612
+ const result = properties(node);
1613
+ const identifier = child(node, "identifier");
1614
+ const symbol = child(node, "symbol");
1615
+ if (identifier)
1616
+ result.identifier = properties(identifier);
1617
+ if (symbol)
1618
+ result.symbol = properties(symbol);
1619
+ return result;
1620
+ }
1621
+ function sourceDocument(root) {
1622
+ const result = {};
1623
+ const metadata = child(root, "metadata");
1624
+ const cache = child(root, "cache");
1625
+ const content = child(root, "content");
1626
+ const structure = child(root, "structure");
1627
+ const diagnosticOutput = child(root, "diagnostics");
1628
+ const identityOutput = child(root, "cursor");
1629
+ if (metadata)
1630
+ result.metadata = properties(metadata);
1631
+ if (!metadata && !content && !structure && !diagnosticOutput && !identityOutput && root.properties.has("file"))
1632
+ result.file = root.properties.get("file");
1633
+ const assets = children(root, "asset");
1634
+ if (assets.length > 0)
1635
+ result.assets = assets.map(genericNode);
1636
+ if (cache)
1637
+ result.cache = properties(cache);
1638
+ if (content)
1639
+ result.content = sourceContent(content);
1640
+ if (structure)
1641
+ result.structure = sourceMap(structure);
1642
+ if (diagnosticOutput)
1643
+ result.diagnostics = diagnostics(diagnosticOutput);
1644
+ if (identityOutput)
1645
+ result.cursor = identity(identityOutput);
1646
+ return result;
1647
+ }
1648
+ function hit(node) {
1649
+ return {
1650
+ ...properties(node),
1651
+ hashlines: children(node, "line").map(line),
1652
+ captures: children(node, "capture").map((capture) => ({
1653
+ ...properties(capture),
1654
+ hashlines: children(capture, "line").map(line)
1655
+ }))
1656
+ };
1657
+ }
1658
+ function search(root) {
1659
+ return {
1660
+ ...properties(root),
1661
+ results: children(root, "match").map((match) => ({
1662
+ ...properties(match),
1663
+ matches: children(match, "hit").map(hit)
1664
+ })),
1665
+ documents: children(root, "document").map((documentNode) => ({
1666
+ ...properties(documentNode),
1667
+ hits: children(documentNode, "hit").map(properties)
1668
+ }))
1669
+ };
1670
+ }
1671
+ function references(root) {
1672
+ const parsed = children(root, "reference").map((reference) => {
1673
+ const sourceLine = child(reference, "line");
1674
+ if (!sourceLine)
1675
+ throw new Error("invalid readseek KDL reference: missing line");
1676
+ const parsedLine = line(sourceLine);
1677
+ const symbol = child(reference, "symbol");
1678
+ return {
1679
+ ...properties(reference),
1680
+ line_hash: parsedLine.hash,
1681
+ text: parsedLine.text,
1682
+ ...symbol ? { symbol: properties(symbol) } : {}
1683
+ };
1684
+ });
1685
+ return {
1686
+ references: parsed,
1687
+ locations: parsed.map((reference) => {
1688
+ const { symbol, ...location } = reference;
1689
+ return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1690
+ })
1691
+ };
1692
+ }
1693
+ function definitions(root) {
1694
+ return {
1695
+ locations: children(root, "definition").map((definition) => {
1696
+ const symbol = child(definition, "symbol");
1697
+ const sourceLine = child(definition, "line");
1698
+ if (!symbol || !sourceLine) {
1699
+ throw new Error("invalid readseek KDL definition: missing symbol or line");
1700
+ }
1701
+ const symbolProperties = properties(symbol);
1702
+ const parsedLine = line(sourceLine);
1703
+ const definitionColumn = definition.properties.get("column");
1704
+ const nameByte = symbol.properties.get("name_byte");
1705
+ const startByte = symbol.properties.get("start_byte");
1706
+ let column = 1;
1707
+ if (typeof definitionColumn === "number") {
1708
+ column = definitionColumn;
1709
+ } else if (typeof nameByte === "number" && typeof startByte === "number") {
1710
+ column = nameByte - startByte + 1;
1711
+ }
1712
+ return {
1713
+ file: requireProperty(definition, "file"),
1714
+ line: parsedLine.line,
1715
+ column,
1716
+ line_hash: parsedLine.hash,
1717
+ text: parsedLine.text,
1718
+ kind: symbolProperties.kind,
1719
+ name: symbolProperties.name,
1720
+ qualified_name: symbolProperties.qualified_name
1721
+ };
1722
+ })
1723
+ };
1724
+ }
1725
+ function edit(root) {
1726
+ const result = properties(root);
1727
+ for (const name of ["before_content", "content"]) {
1728
+ const contentNode = child(root, name);
1729
+ if (contentNode)
1730
+ result[name] = requireProperty(contentNode, "content");
1966
1731
  }
1967
- return { kind: "text", ...parse2(TextDetectionInput, value, "detect output") };
1732
+ return result;
1968
1733
  }
1969
- function parseImageContent(value) {
1970
- const detection = parseDetectOutput(value);
1971
- if (detection.kind !== "image")
1972
- throw new Error("invalid readseek image content: expected an image");
1973
- return detection;
1734
+ function renameEdit(node) {
1735
+ const sourceLine = child(node, "line");
1736
+ if (!sourceLine)
1737
+ throw new Error("invalid readseek KDL rename edit: missing line");
1738
+ return {
1739
+ ...properties(node),
1740
+ text: requireProperty(sourceLine, "content")
1741
+ };
1974
1742
  }
1975
- function parseTextContent(value) {
1976
- const output = value ?? {};
1977
- if (output.hashlines === undefined || output.start_line !== undefined)
1978
- return parseReadOutput(value);
1979
- const spanned = parse2(UnspannedContent, value, "content");
1743
+ function renameFile(node) {
1980
1744
  return {
1981
- ...spanned,
1982
- start_line: spanned.hashlines[0]?.line ?? 1,
1983
- end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1745
+ ...properties(node),
1746
+ edits: children(node, "edit").map(renameEdit),
1747
+ conflicts: children(node, "conflict").map(properties)
1984
1748
  };
1985
1749
  }
1986
- function parseDocumentCache(value) {
1987
- return parse2(DocumentCache, value, "document cache");
1750
+ function rename(root) {
1751
+ return {
1752
+ ...renameFile(root),
1753
+ others: children(root, "other").map(renameFile)
1754
+ };
1988
1755
  }
1989
- function parseIdentifyOutput(value) {
1990
- return parse2(IdentifyEnvelope, value, "identify output");
1756
+ function genericNode(node) {
1757
+ const result = properties(node);
1758
+ const textNode = child(node, "text");
1759
+ const bbox = child(node, "bbox");
1760
+ if (textNode)
1761
+ result.text = requireProperty(textNode, "content");
1762
+ if (bbox)
1763
+ result.bbox = properties(bbox);
1764
+ const objects = children(node, "object");
1765
+ if (objects.length > 0)
1766
+ result.objects = objects.map((object) => ({
1767
+ label: requireProperty(object, "label"),
1768
+ bbox: [requireProperty(object, "x0"), requireProperty(object, "y0"), requireProperty(object, "x1"), requireProperty(object, "y1")]
1769
+ }));
1770
+ return result;
1991
1771
  }
1992
- function parsePdfInspectMetadata(value) {
1993
- if (!value || typeof value !== "object")
1994
- return;
1995
- if (value.format !== "pdf")
1996
- return;
1997
- const document = parse2(PdfInspectMetadata, value, "inspect metadata");
1998
- return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1772
+ function pdfDocument(root) {
1773
+ return {
1774
+ ...properties(root),
1775
+ nodes: children(root, "node").map(genericNode),
1776
+ assets: children(root, "asset").map(genericNode),
1777
+ vision: children(root, "vision").map(genericNode)
1778
+ };
1999
1779
  }
2000
- function parseEditOutput(value) {
2001
- return parse2(EditOutputSchema, value, "edit output");
1780
+ function parseReadSeekKdl(text) {
1781
+ const root = parseKdlDocument(text);
1782
+ switch (root.name) {
1783
+ case "document":
1784
+ return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1785
+ case "search":
1786
+ return search(root);
1787
+ case "check":
1788
+ return diagnostics(root);
1789
+ case "refs":
1790
+ return references(root);
1791
+ case "def":
1792
+ return definitions(root);
1793
+ case "edit":
1794
+ return edit(root);
1795
+ case "edit-batch":
1796
+ return { ...properties(root), files: children(root, "file").map(edit) };
1797
+ case "rename":
1798
+ return rename(root);
1799
+ default:
1800
+ throw new Error(`unsupported readseek KDL root: ${root.name}`);
1801
+ }
1802
+ }
1803
+ // src/process.ts
1804
+ var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1805
+ var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1806
+ function readSeekProcessEnvironment() {
1807
+ return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1808
+ }
1809
+ function resolveProcessLimits(options = {}) {
1810
+ return {
1811
+ timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1812
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1813
+ cancelable: options.cancelable !== false
1814
+ };
2002
1815
  }
2003
- function parseRenameOutput(value) {
2004
- return parse2(RenameOutputSchema, value, "rename output");
1816
+ function readSeekCancellationError(signal) {
1817
+ const reason = signal?.reason;
1818
+ return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
2005
1819
  }
2006
- function parseDefOutput(value) {
2007
- return parse2(DefEnvelope, value, "def output").locations;
1820
+ function failureFromProcessResult(result, options = {}) {
1821
+ if (options.overflow) {
1822
+ return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1823
+ }
1824
+ if (options.timedOut) {
1825
+ return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1826
+ }
1827
+ const cleaned = cleanReadSeekErrorText(result.stderr);
1828
+ if (cleaned)
1829
+ return new Error(cleaned);
1830
+ if (result.signal)
1831
+ return new Error(`readseek killed by signal ${result.signal}`);
1832
+ return new Error(`readseek exited with status ${result.exitCode}`);
2008
1833
  }
2009
-
2010
- // src/client.ts
2011
- function classifyReadSeekFailure(err) {
2012
- const failure = err;
2013
- const message = String(failure?.message ?? err);
2014
- const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
2015
- if (missing) {
2016
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1834
+ function acceptsReadSeekExitCode(args, exitCode) {
1835
+ if (exitCode === 0)
1836
+ return true;
1837
+ if (exitCode !== 1)
1838
+ return false;
1839
+ let index = 0;
1840
+ const valued = new Set(["-C", "--directory", "--output", "--readseek-dir"]);
1841
+ while (index < args.length) {
1842
+ const arg = args[index];
1843
+ if (valued.has(arg)) {
1844
+ index += 2;
1845
+ continue;
1846
+ }
1847
+ if ([...valued].some((flag) => arg.startsWith(`${flag}=`)) || arg === "--kdl") {
1848
+ index++;
1849
+ continue;
1850
+ }
1851
+ return arg === "find" || arg === "check";
2017
1852
  }
2018
- return { code: "readseek-execution-error", message };
1853
+ return false;
2019
1854
  }
2020
- function parseSourceInspectEnvelope(value) {
2021
- if (!value || typeof value !== "object")
2022
- throw new Error("invalid readseek inspect output");
2023
- const output = value;
2024
- const result = {};
2025
- if (output.metadata !== undefined)
2026
- result.metadata = parseDetectOutput(output.metadata);
2027
- if (output.cache !== undefined)
2028
- result.cache = parseDocumentCache(output.cache);
2029
- if (output.content !== undefined)
2030
- result.content = output.content;
2031
- if (output.map !== undefined)
2032
- result.map = parseMapOutput(output.map);
2033
- if (output.diagnostics !== undefined)
2034
- result.diagnostics = parseCheckOutput(output.diagnostics);
2035
- if (output.identity !== undefined)
2036
- result.identity = parseIdentifyOutput(output.identity);
2037
- return result;
1855
+ function ensureSuccessfulStdout(result, args = []) {
1856
+ if (!result.signal && acceptsReadSeekExitCode(args, result.exitCode))
1857
+ return result.stdout;
1858
+ throw failureFromProcessResult(result);
2038
1859
  }
2039
- function createReadSeekClient(runner) {
2040
- function inspectArgs(target, options) {
2041
- return buildInspectArgs({
2042
- path: target,
2043
- selector: options.selector,
2044
- select: options.select ?? "content",
2045
- query: options.query,
2046
- end: options.end,
2047
- limit: options.limit,
2048
- depth: options.depth,
2049
- language: options.language,
2050
- visionMode: options.visionMode,
2051
- visionLevel: options.visionLevel,
2052
- stdinName: options.stdinName
2053
- });
1860
+ function parseSuccessfulKdl(result, args = []) {
1861
+ return parseReadSeekKdl(ensureSuccessfulStdout(result, args));
1862
+ }
1863
+ // src/process-node.ts
1864
+ import { spawn } from "node:child_process";
1865
+ import { StringDecoder } from "node:string_decoder";
1866
+ function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
1867
+ const decoder = new StringDecoder("utf8");
1868
+ let text = "";
1869
+ let bytes = 0;
1870
+ let overflowed = false;
1871
+ stream.on("data", (chunk) => {
1872
+ if (overflowed)
1873
+ return;
1874
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1875
+ bytes += buffer.length;
1876
+ if (bytes > maxOutputBytes) {
1877
+ overflowed = true;
1878
+ onOverflow();
1879
+ return;
1880
+ }
1881
+ text += decoder.write(buffer);
1882
+ });
1883
+ return {
1884
+ text: () => text + decoder.end()
1885
+ };
1886
+ }
1887
+ async function runReadSeekNode(binaryPath, args, options = {}) {
1888
+ const limits = resolveProcessLimits(options);
1889
+ if (limits.cancelable && options.signal?.aborted) {
1890
+ throw readSeekCancellationError(options.signal);
2054
1891
  }
2055
- function inspectRunOptions(options) {
2056
- return {
2057
- signal: options.signal,
2058
- stdin: options.stdin,
2059
- timeoutMs: options.timeoutMs,
2060
- vision: options.vision
1892
+ return await new Promise((resolve, reject) => {
1893
+ let settled = false;
1894
+ let child;
1895
+ let timer;
1896
+ const finish = (result) => {
1897
+ if (settled)
1898
+ return;
1899
+ settled = true;
1900
+ if (timer !== undefined)
1901
+ clearTimeout(timer);
1902
+ options.signal?.removeEventListener("abort", onAbort);
1903
+ resolve(result);
2061
1904
  };
2062
- }
2063
- async function inspect(target, options = {}) {
2064
- return runner.kdl(inspectArgs(target, options), inspectRunOptions(options));
2065
- }
2066
- async function inspectText(target, options = {}) {
2067
- return runner.text(inspectArgs(target, options), inspectRunOptions(options));
2068
- }
2069
- async function inspectSource(target, options = {}) {
2070
- return parseSourceInspectEnvelope(await inspect(target, options));
2071
- }
2072
- function requireInspectMetadata(envelope, field) {
2073
- if (envelope.metadata === undefined)
2074
- throw new Error(`invalid readseek ${field}: missing metadata`);
2075
- return envelope.metadata;
2076
- }
2077
- function requireInspectContent(envelope, field) {
2078
- if (envelope.content === undefined)
2079
- throw new Error(`invalid readseek ${field}: missing content`);
2080
- return envelope.content;
2081
- }
2082
- async function inspectContent(filePath, options = {}) {
2083
- const inspected = await inspect(filePath, {
2084
- select: ["metadata", "content"],
2085
- selector: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
2086
- end: options.endLine,
2087
- visionMode: options.visionMode,
2088
- signal: options.signal,
2089
- vision: options.vision
1905
+ const fail = (error) => {
1906
+ if (settled)
1907
+ return;
1908
+ settled = true;
1909
+ if (timer !== undefined)
1910
+ clearTimeout(timer);
1911
+ options.signal?.removeEventListener("abort", onAbort);
1912
+ reject(error instanceof Error ? error : new Error(String(error)));
1913
+ };
1914
+ const terminate = (error) => {
1915
+ if (settled)
1916
+ return;
1917
+ fail(error);
1918
+ try {
1919
+ child.kill("SIGKILL");
1920
+ } catch {}
1921
+ };
1922
+ const onAbort = () => {
1923
+ terminate(readSeekCancellationError(options.signal));
1924
+ };
1925
+ try {
1926
+ child = spawn(binaryPath, [...args], {
1927
+ cwd: options.cwd,
1928
+ env: readSeekProcessEnvironment(),
1929
+ shell: false,
1930
+ stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
1931
+ windowsHide: true
1932
+ });
1933
+ } catch (error) {
1934
+ fail(error);
1935
+ return;
1936
+ }
1937
+ const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
1938
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
2090
1939
  });
2091
- const pdf = parsePdfInspectMetadata(inspected);
2092
- if (pdf)
2093
- return { metadata: pdf };
2094
- const envelope = parseSourceInspectEnvelope(inspected);
2095
- const metadata = requireInspectMetadata(envelope, "content");
2096
- const content = requireInspectContent(envelope, "content");
2097
- if (metadata.kind === "image") {
2098
- return { metadata, image: parseImageContent(content) };
1940
+ const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
1941
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
1942
+ });
1943
+ timer = setTimeout(() => {
1944
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1945
+ }, limits.timeoutMs);
1946
+ timer.unref?.();
1947
+ if (limits.cancelable && options.signal) {
1948
+ options.signal.addEventListener("abort", onAbort, { once: true });
2099
1949
  }
2100
- return { metadata, text: parseTextContent(content) };
2101
- }
2102
- return {
2103
- inspect,
2104
- inspectText,
2105
- inspectContent,
2106
- async extract(target, selector, output, options = {}) {
2107
- await runner.text(buildExtractArgs({
2108
- path: target,
2109
- selector,
2110
- output,
2111
- stdinName: options.stdinName
2112
- }), {
2113
- signal: options.signal,
2114
- stdin: options.stdin,
2115
- timeoutMs: options.timeoutMs,
2116
- cancelable: options.cancelable
2117
- });
2118
- },
2119
- async image(filePath, modes, options = {}) {
2120
- if (modes.length === 0)
2121
- throw new Error(`readseek returned no image analysis for ${filePath}`);
2122
- const inspected = await inspectContent(filePath, {
2123
- visionMode: modes.length > 1 ? "all" : modes[0],
2124
- signal: options.signal,
2125
- vision: true
1950
+ if (options.stdin !== undefined) {
1951
+ child.stdin.on("error", (error) => {
1952
+ if (error.code !== "EPIPE")
1953
+ fail(error);
2126
1954
  });
2127
- if (!inspected.image)
2128
- throw new Error(`readseek returned no image analysis for ${filePath}`);
2129
- return inspected.image;
2130
- },
2131
- async preparedImage(filePath, options = {}) {
2132
- const inspected = await inspectContent(filePath, {
2133
- visionMode: "none",
2134
- signal: options.signal
1955
+ child.stdin.end(options.stdin, "utf8");
1956
+ }
1957
+ child.on("error", fail);
1958
+ child.on("close", (code, signal) => {
1959
+ finish({
1960
+ stdout: stdout.text(),
1961
+ stderr: stderr.text(),
1962
+ exitCode: code,
1963
+ signal
2135
1964
  });
2136
- const output = inspected.image;
2137
- if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
2138
- throw new Error(`readseek returned no prepared image for ${filePath}`);
1965
+ });
1966
+ });
1967
+ }
1968
+ async function runReadSeekNodeText(binaryPath, args, options = {}) {
1969
+ return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options), args);
1970
+ }
1971
+ async function runReadSeekNodeKdl(binaryPath, args, options = {}) {
1972
+ return parseSuccessfulKdl(await runReadSeekNode(binaryPath, args, options), args);
1973
+ }
1974
+ // src/process-bun.ts
1975
+ function getBunSpawn() {
1976
+ const bunGlobal = globalThis;
1977
+ const spawn = bunGlobal.Bun?.spawn;
1978
+ if (typeof spawn !== "function") {
1979
+ throw new Error("Bun.spawn is unavailable in this runtime");
1980
+ }
1981
+ return spawn;
1982
+ }
1983
+ async function readBoundedText(stream, streamName, maxOutputBytes, onOverflow) {
1984
+ const reader = stream.getReader();
1985
+ const chunks = [];
1986
+ let totalBytes = 0;
1987
+ try {
1988
+ while (true) {
1989
+ const { done, value } = await reader.read();
1990
+ if (done)
1991
+ break;
1992
+ totalBytes += value.byteLength;
1993
+ if (totalBytes > maxOutputBytes) {
1994
+ onOverflow();
1995
+ await reader.cancel().catch(() => {
1996
+ return;
1997
+ });
1998
+ throw failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: streamName, maxOutputBytes });
2139
1999
  }
2140
- return { mime: output.mime, encoding: output.encoding, data: output.data };
2141
- },
2142
- async search(target, pattern, options = {}) {
2143
- const args = buildSearchArgs({
2144
- path: target,
2145
- pattern,
2146
- language: options.language,
2147
- limit: options.limit,
2148
- gitScope: options.gitScope
2149
- });
2150
- return parseSearchOutput(await runner.kdl(args, { signal: options.signal }));
2151
- },
2152
- async refs(target, options = {}) {
2153
- const args = buildRefsArgs({
2154
- path: target,
2155
- name: options.name,
2156
- scope: options.scope,
2157
- line: options.line,
2158
- column: options.column,
2159
- language: options.language,
2160
- gitScope: options.gitScope
2161
- });
2162
- return parseRefsOutput(await runner.kdl(args, { signal: options.signal }));
2163
- },
2164
- async def(target, options) {
2165
- const args = buildDefArgs({
2166
- path: target,
2167
- name: options.name,
2168
- language: options.language,
2169
- gitScope: options.gitScope
2170
- });
2171
- return parseDefOutput(await runner.kdl(args, { signal: options.signal }));
2172
- },
2173
- async check(filePath, content, options = {}) {
2174
- const envelope = await inspectSource("-", {
2175
- select: "diagnostics",
2176
- stdin: content,
2177
- stdinName: filePath,
2178
- signal: options.signal
2179
- });
2180
- if (!envelope.diagnostics)
2181
- throw new Error("invalid readseek diagnostics: missing diagnostics facet");
2182
- return envelope.diagnostics;
2183
- },
2184
- async identify(filePath, content, options) {
2185
- const envelope = await inspectSource("-", {
2186
- select: "identity",
2187
- stdin: content,
2188
- stdinName: filePath,
2189
- selector: buildLineLocation(options.line, options.column),
2190
- language: options.language,
2191
- signal: options.signal
2192
- });
2193
- if (!envelope.identity)
2194
- throw new Error("invalid readseek identity: missing identity facet");
2195
- return envelope.identity;
2196
- },
2197
- async detect(filePath, options = {}) {
2198
- const inspected = await inspect(filePath, { select: "metadata", signal: options.signal });
2199
- const pdf = parsePdfInspectMetadata(inspected);
2200
- if (pdf)
2201
- return pdf;
2202
- return requireInspectMetadata(parseSourceInspectEnvelope(inspected), "metadata");
2203
- },
2204
- async edit(filePath, edits, options = {}) {
2205
- const args = buildEditArgs({
2206
- path: filePath,
2207
- apply: options.apply,
2208
- planHash: options.planHash,
2209
- language: options.language,
2210
- requestFromStdin: true
2211
- });
2212
- return parseEditOutput(await runner.kdl(args, {
2213
- signal: options.signal,
2214
- stdin: serializeEditPlan(edits),
2215
- timeoutMs: options.timeoutMs,
2216
- cancelable: options.cancelable
2217
- }));
2218
- },
2219
- async rename(filePath, options) {
2220
- const args = buildRenameArgs({
2221
- path: filePath,
2222
- to: options.to,
2223
- line: options.line,
2224
- column: options.column,
2225
- workspace: options.workspace,
2226
- apply: options.apply,
2227
- planHash: options.planHash,
2228
- language: options.language,
2229
- gitScope: options.gitScope
2230
- });
2231
- return parseRenameOutput(await runner.kdl(args, { signal: options.signal }));
2000
+ chunks.push(value);
2232
2001
  }
2002
+ } finally {
2003
+ reader.releaseLock();
2004
+ }
2005
+ const bytes = new Uint8Array(totalBytes);
2006
+ let offset = 0;
2007
+ for (const chunk of chunks) {
2008
+ bytes.set(chunk, offset);
2009
+ offset += chunk.byteLength;
2010
+ }
2011
+ return new TextDecoder().decode(bytes);
2012
+ }
2013
+ async function runReadSeekBun(binaryPath, args, options = {}) {
2014
+ const limits = resolveProcessLimits(options);
2015
+ if (limits.cancelable && options.signal?.aborted) {
2016
+ throw readSeekCancellationError(options.signal);
2017
+ }
2018
+ const spawn = getBunSpawn();
2019
+ const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
2020
+ const spawnOptions = {
2021
+ cwd: options.cwd,
2022
+ env: readSeekProcessEnvironment(),
2023
+ stderr: "pipe",
2024
+ stdin,
2025
+ stdout: "pipe"
2026
+ };
2027
+ const child = spawn([binaryPath, ...args], limits.cancelable && options.signal ? { ...spawnOptions, killSignal: "SIGKILL", signal: options.signal } : spawnOptions);
2028
+ const killForOverflow = () => {
2029
+ try {
2030
+ child.kill("SIGKILL");
2031
+ } catch {}
2233
2032
  };
2033
+ let timeout;
2034
+ const timeoutResult = new Promise((_resolve, reject) => {
2035
+ timeout = setTimeout(() => {
2036
+ try {
2037
+ child.kill("SIGKILL");
2038
+ } catch {}
2039
+ reject(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
2040
+ }, limits.timeoutMs);
2041
+ timeout.unref?.();
2042
+ });
2043
+ const pending = Promise.all([
2044
+ readBoundedText(child.stdout, "stdout", limits.maxOutputBytes, killForOverflow),
2045
+ readBoundedText(child.stderr, "stderr", limits.maxOutputBytes, killForOverflow),
2046
+ child.exited
2047
+ ]);
2048
+ try {
2049
+ const [stdout, stderr, exitCode] = await Promise.race([pending, timeoutResult]);
2050
+ if (limits.cancelable && options.signal?.aborted)
2051
+ throw readSeekCancellationError(options.signal);
2052
+ return { stdout, stderr, exitCode, signal: null };
2053
+ } finally {
2054
+ if (timeout !== undefined)
2055
+ clearTimeout(timeout);
2056
+ pending.catch(() => {
2057
+ return;
2058
+ });
2059
+ }
2060
+ }
2061
+ async function runReadSeekBunText(binaryPath, args, options = {}) {
2062
+ return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options), args);
2063
+ }
2064
+ async function runReadSeekBunKdl(binaryPath, args, options = {}) {
2065
+ return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options), args);
2234
2066
  }
2235
2067
  export {
2236
2068
  BUILTIN_FALLBACKS,
2237
- DEFAULT_INSPECT_MAP_DEPTH,
2238
2069
  DEFAULT_MAX_OUTPUT_BYTES,
2239
2070
  DEFAULT_READSEEK_TIMEOUT_MS,
2071
+ DEFAULT_STRUCTURE_DEPTH,
2240
2072
  EDIT_VARIANTS,
2241
2073
  GIT_SCOPES,
2242
2074
  HASH_LEN,
2243
- INSPECT_FACETS,
2244
2075
  PARAM_DESCRIPTIONS,
2245
2076
  READSEEK_BINARY_ENV,
2246
2077
  READSEEK_PLATFORM_PACKAGES,
@@ -2249,19 +2080,26 @@ export {
2249
2080
  TOOL_CONTRACTS,
2250
2081
  TOOL_DESCRIPTIONS,
2251
2082
  TOOL_NAMES,
2252
- VISION_ANALYSIS_MODES,
2253
2083
  VISION_LEVELS,
2254
- VISION_MODES,
2084
+ acceptsReadSeekExitCode,
2255
2085
  appendApplyAndPlanHash,
2256
2086
  appendGitScopeArgs,
2257
- buildDefArgs,
2258
- buildEditArgs,
2259
- buildExtractArgs,
2260
- buildInspectArgs,
2087
+ buildCheckRulesArgs,
2088
+ buildCheckSyntaxArgs,
2089
+ buildDetectObjectsArgs,
2090
+ buildEditTextArgs,
2091
+ buildFindDefinitionsArgs,
2092
+ buildFindNodesArgs,
2093
+ buildFindReferencesArgs,
2094
+ buildFindTextArgs,
2095
+ buildGenerateCaptionArgs,
2261
2096
  buildLineLocation,
2262
- buildRefsArgs,
2263
- buildRenameArgs,
2264
- buildSearchArgs,
2097
+ buildRecognizeTextArgs,
2098
+ buildRenameSymbolArgs,
2099
+ buildShowAssetsArgs,
2100
+ buildShowContentArgs,
2101
+ buildShowMetadataArgs,
2102
+ buildShowStructureArgs,
2265
2103
  canonicalPathKey,
2266
2104
  classifyReadSeekFailure,
2267
2105
  cleanReadSeekErrorText,
@@ -2279,6 +2117,7 @@ export {
2279
2117
  isSubPath,
2280
2118
  parseLineAnchor,
2281
2119
  parseReadSeekKdl,
2120
+ parseSelector,
2282
2121
  parseSuccessfulKdl,
2283
2122
  preferredToolDescriptionMap,
2284
2123
  readSeekBinaryName,
@@ -2306,6 +2145,5 @@ export {
2306
2145
  serializeEditPlan,
2307
2146
  tryParseLineAnchor,
2308
2147
  validateExactlyOneEditVariant,
2309
- validateRefsNameVsScope,
2310
- validateVisionLevelVsMode
2148
+ validateReferencesSelection
2311
2149
  };