@jarkkojs/readseek-api 0.12.0 → 0.12.1

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/argv.d.ts CHANGED
@@ -69,11 +69,18 @@ export type SearchArgsInput = AccessArgsInput & GitScopeInput & {
69
69
  limit?: number;
70
70
  noAnchors?: boolean;
71
71
  };
72
- export type GrepArgsInput = {
73
- path: string;
74
- text: string;
72
+ export type GrepArgsInput = GitScopeInput & {
73
+ pattern: string;
74
+ path?: string;
75
+ glob?: string;
76
+ ignoreCase?: boolean;
77
+ literal?: boolean;
78
+ context?: number;
75
79
  limit?: number;
76
- } & GitScopeInput;
80
+ summary?: boolean;
81
+ scope?: "symbol";
82
+ scopeContext?: number;
83
+ };
77
84
  export type DefArgsInput = {
78
85
  path: string;
79
86
  name: string;
@@ -541,37 +541,138 @@ declare const DocumentOutput: import("@sinclair/typebox").TObject<{
541
541
  ocr_requested: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
542
542
  }>>;
543
543
  }>;
544
- declare const TextSearchOutput: import("@sinclair/typebox").TObject<{
544
+ declare const GrepLineSchema: import("@sinclair/typebox").TObject<{
545
+ line: import("@sinclair/typebox").TInteger;
546
+ hash: import("@sinclair/typebox").TString;
545
547
  text: import("@sinclair/typebox").TString;
546
- mode: import("@sinclair/typebox").TLiteral<"literal">;
548
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
549
+ }>;
550
+ declare const GrepHitSchema: import("@sinclair/typebox").TObject<{
551
+ line: import("@sinclair/typebox").TInteger;
552
+ column: import("@sinclair/typebox").TInteger;
553
+ start_byte: import("@sinclair/typebox").TInteger;
554
+ end_byte: import("@sinclair/typebox").TInteger;
555
+ text: import("@sinclair/typebox").TString;
556
+ anchor: import("@sinclair/typebox").TString;
557
+ }>;
558
+ declare const GrepSymbolSchema: import("@sinclair/typebox").TObject<{
559
+ name: import("@sinclair/typebox").TString;
560
+ kind: import("@sinclair/typebox").TString;
561
+ start_line: import("@sinclair/typebox").TInteger;
562
+ end_line: import("@sinclair/typebox").TInteger;
563
+ lines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
564
+ line: import("@sinclair/typebox").TInteger;
565
+ hash: import("@sinclair/typebox").TString;
566
+ text: import("@sinclair/typebox").TString;
567
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
568
+ }>>;
569
+ }>;
570
+ declare const GrepSourceResultSchema: import("@sinclair/typebox").TObject<{
571
+ file: import("@sinclair/typebox").TString;
572
+ file_hash: import("@sinclair/typebox").TString;
573
+ language: import("@sinclair/typebox").TString;
574
+ total_matches: import("@sinclair/typebox").TInteger;
575
+ returned_matches: import("@sinclair/typebox").TInteger;
576
+ hits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
577
+ line: import("@sinclair/typebox").TInteger;
578
+ column: import("@sinclair/typebox").TInteger;
579
+ start_byte: import("@sinclair/typebox").TInteger;
580
+ end_byte: import("@sinclair/typebox").TInteger;
581
+ text: import("@sinclair/typebox").TString;
582
+ anchor: import("@sinclair/typebox").TString;
583
+ }>>;
584
+ lines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
585
+ line: import("@sinclair/typebox").TInteger;
586
+ hash: import("@sinclair/typebox").TString;
587
+ text: import("@sinclair/typebox").TString;
588
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
589
+ }>>;
590
+ symbols: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
591
+ name: import("@sinclair/typebox").TString;
592
+ kind: import("@sinclair/typebox").TString;
593
+ start_line: import("@sinclair/typebox").TInteger;
594
+ end_line: import("@sinclair/typebox").TInteger;
595
+ lines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
596
+ line: import("@sinclair/typebox").TInteger;
597
+ hash: import("@sinclair/typebox").TString;
598
+ text: import("@sinclair/typebox").TString;
599
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
600
+ }>>;
601
+ }>>;
602
+ }>;
603
+ declare const GrepDocumentHitSchema: import("@sinclair/typebox").TObject<{
604
+ node_id: import("@sinclair/typebox").TString;
605
+ kind: import("@sinclair/typebox").TString;
606
+ snippet: import("@sinclair/typebox").TString;
607
+ line: import("@sinclair/typebox").TInteger;
608
+ page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
609
+ }>;
610
+ declare const GrepDocumentResultSchema: import("@sinclair/typebox").TObject<{
611
+ file: import("@sinclair/typebox").TString;
612
+ content_hash: import("@sinclair/typebox").TString;
613
+ total_matches: import("@sinclair/typebox").TInteger;
614
+ returned_matches: import("@sinclair/typebox").TInteger;
615
+ hits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
616
+ node_id: import("@sinclair/typebox").TString;
617
+ kind: import("@sinclair/typebox").TString;
618
+ snippet: import("@sinclair/typebox").TString;
619
+ line: import("@sinclair/typebox").TInteger;
620
+ page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
621
+ }>>;
622
+ }>;
623
+ declare const GrepOutputSchema: import("@sinclair/typebox").TObject<{
624
+ pattern: import("@sinclair/typebox").TString;
625
+ mode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"regex">, import("@sinclair/typebox").TLiteral<"literal">]>;
626
+ ignore_case: import("@sinclair/typebox").TBoolean;
627
+ summary: import("@sinclair/typebox").TBoolean;
547
628
  total_matches: import("@sinclair/typebox").TInteger;
548
629
  returned_matches: import("@sinclair/typebox").TInteger;
549
630
  truncated: import("@sinclair/typebox").TBoolean;
631
+ count_unit: import("@sinclair/typebox").TLiteral<"lines">;
632
+ scope: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TLiteral<"symbol">>;
550
633
  results: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
551
634
  file: import("@sinclair/typebox").TString;
552
635
  file_hash: import("@sinclair/typebox").TString;
553
- matches: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
636
+ language: import("@sinclair/typebox").TString;
637
+ total_matches: import("@sinclair/typebox").TInteger;
638
+ returned_matches: import("@sinclair/typebox").TInteger;
639
+ hits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
554
640
  line: import("@sinclair/typebox").TInteger;
555
641
  column: import("@sinclair/typebox").TInteger;
642
+ start_byte: import("@sinclair/typebox").TInteger;
643
+ end_byte: import("@sinclair/typebox").TInteger;
644
+ text: import("@sinclair/typebox").TString;
645
+ anchor: import("@sinclair/typebox").TString;
646
+ }>>;
647
+ lines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
648
+ line: import("@sinclair/typebox").TInteger;
649
+ hash: import("@sinclair/typebox").TString;
556
650
  text: import("@sinclair/typebox").TString;
651
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
652
+ }>>;
653
+ symbols: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
654
+ name: import("@sinclair/typebox").TString;
655
+ kind: import("@sinclair/typebox").TString;
557
656
  start_line: import("@sinclair/typebox").TInteger;
558
657
  end_line: import("@sinclair/typebox").TInteger;
559
- start_byte: import("@sinclair/typebox").TInteger;
560
- end_byte: import("@sinclair/typebox").TInteger;
561
- hashlines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
658
+ lines: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
562
659
  line: import("@sinclair/typebox").TInteger;
563
660
  hash: import("@sinclair/typebox").TString;
564
661
  text: import("@sinclair/typebox").TString;
662
+ kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"match">, import("@sinclair/typebox").TLiteral<"context">]>;
565
663
  }>>;
566
664
  }>>;
567
665
  }>>;
568
666
  documents: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
569
667
  file: import("@sinclair/typebox").TString;
570
668
  content_hash: import("@sinclair/typebox").TString;
669
+ total_matches: import("@sinclair/typebox").TInteger;
670
+ returned_matches: import("@sinclair/typebox").TInteger;
571
671
  hits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
572
672
  node_id: import("@sinclair/typebox").TString;
573
673
  kind: import("@sinclair/typebox").TString;
574
674
  snippet: import("@sinclair/typebox").TString;
675
+ line: import("@sinclair/typebox").TInteger;
575
676
  page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
576
677
  }>>;
577
678
  }>>;
@@ -599,7 +700,13 @@ export type ReadSeekDocumentNode = Static<typeof DocumentNode>;
599
700
  export type ReadSeekDocumentAsset = Static<typeof DocumentAsset>;
600
701
  export type ReadSeekDocumentVision = Static<typeof DocumentVision>;
601
702
  export type ReadSeekDocumentOutput = Static<typeof DocumentOutput>;
602
- export type ReadSeekTextSearchOutput = Static<typeof TextSearchOutput>;
703
+ export type GrepOutput = Static<typeof GrepOutputSchema>;
704
+ export type GrepLine = Static<typeof GrepLineSchema>;
705
+ export type GrepHit = Static<typeof GrepHitSchema>;
706
+ export type GrepSymbol = Static<typeof GrepSymbolSchema>;
707
+ export type GrepSourceResult = Static<typeof GrepSourceResultSchema>;
708
+ export type GrepDocumentHit = Static<typeof GrepDocumentHitSchema>;
709
+ export type GrepDocumentResult = Static<typeof GrepDocumentResultSchema>;
603
710
  export type ReadSeekRulesOutput = Static<typeof RulesOutput>;
604
711
  export interface ReadSeekSourceContentOutput {
605
712
  metadata: ReadSeekDetection;
@@ -614,7 +721,7 @@ export type ReadSeekStructureOutputEnvelope = {
614
721
  export declare function parseDocumentOutput(value: unknown): ReadSeekDocumentOutput;
615
722
  export declare function parseContentOutput(value: unknown): ReadSeekContentOutput;
616
723
  export declare function parseStructureEnvelope(value: unknown): ReadSeekStructureOutputEnvelope;
617
- export declare function parseTextSearchOutput(value: unknown): ReadSeekTextSearchOutput;
724
+ export declare function parseGrepOutput(value: unknown): GrepOutput;
618
725
  export declare function parseRulesOutput(value: unknown): ReadSeekRulesOutput;
619
726
  declare const SourceAssetsOutput: import("@sinclair/typebox").TObject<{
620
727
  file: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
package/dist/client.d.ts CHANGED
@@ -22,7 +22,7 @@ export type ReadSeekCheckOptions = Options<argv.CheckArgsInput>;
22
22
  export type ReadSeekCaptionOptions = Options<argv.CaptionArgsInput>;
23
23
  export type ReadSeekVisionOptions = Options<argv.ObjectsArgsInput>;
24
24
  export type ReadSeekSearchOptions = Options<argv.SearchArgsInput>;
25
- export type ReadSeekGrepOptions = Options<argv.GrepArgsInput>;
25
+ export type ReadSeekGrepParams = argv.GrepArgsInput & Omit<ReadSeekRunOptions, "vision">;
26
26
  export type ReadSeekDefOptions = Options<argv.DefArgsInput>;
27
27
  export type ReadSeekRefsOptions = Options<argv.RefsArgsInput>;
28
28
  export type ReadSeekEditOptions = Options<argv.EditArgsInput>;
@@ -49,7 +49,7 @@ export interface ReadSeekClient {
49
49
  objects(path: string, options?: ReadSeekVisionOptions): Promise<output.ReadSeekContentOutput>;
50
50
  ocr(path: string, options?: ReadSeekVisionOptions): Promise<output.ReadSeekContentOutput>;
51
51
  search(path: string, options: ReadSeekSearchOptions): Promise<output.ReadSeekSearchOutput>;
52
- grep(path: string, options: ReadSeekGrepOptions): Promise<output.ReadSeekTextSearchOutput>;
52
+ grep(params: ReadSeekGrepParams): Promise<output.GrepOutput>;
53
53
  def(path: string, options: ReadSeekDefOptions): Promise<output.DefLocation[]>;
54
54
  refs(path: string, options: ReadSeekRefsOptions): Promise<output.ReadSeekReference[]>;
55
55
  edit(path: string, edits: unknown[], options?: ReadSeekEditOptions): Promise<output.EditOutput>;
@@ -16,7 +16,7 @@ export declare const PARAM_DESCRIPTIONS: {
16
16
  readonly pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)";
17
17
  readonly patternContext: "Surrounding code context for sub-statement patterns";
18
18
  readonly selector: "Target AST node kind to extract from pattern context";
19
- readonly grepPattern: "Regex pattern; set literal for exact text";
19
+ readonly grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.";
20
20
  readonly name: "Qualified or unqualified symbol name";
21
21
  readonly refsName: "Identifier to find references for";
22
22
  readonly gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.";
@@ -33,7 +33,6 @@ export declare const PARAM_DESCRIPTIONS: {
33
33
  readonly kind: "Restrict structure to this node kind";
34
34
  readonly outline: "Show outline of document hierarchy or source symbols";
35
35
  readonly noAnchors: "Omit source line anchors";
36
- readonly text: "Literal native text query; adapter grep also supports regex via pattern";
37
36
  readonly edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace";
38
37
  readonly editVariant: "Exactly one nested edit variant";
39
38
  readonly setLine: "Replace one line: {\"set_line\":{\"anchor\":\"LINE:HASH\",\"new_text\":\"...\"}}";
@@ -65,9 +64,10 @@ export declare const PARAM_DESCRIPTIONS: {
65
64
  readonly literal: "Treat pattern literally";
66
65
  readonly context: "Surrounding lines for each match";
67
66
  readonly matchLimit: "Maximum matches to return";
67
+ readonly grepLimit: "Maximum matching lines to return; positive integer, defaults to 100";
68
68
  readonly summary: "Return per-file counts";
69
69
  readonly grepScope: "Group matches by enclosing symbol";
70
- readonly scopeContext: "Context lines within each symbol";
70
+ readonly scopeContext: "Non-negative context lines within each symbol; requires scope: symbol";
71
71
  };
72
72
  export declare const TOOL_DESCRIPTIONS: {
73
73
  readonly show: "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.";
@@ -80,7 +80,7 @@ export declare const TOOL_DESCRIPTIONS: {
80
80
  readonly objects: "Explicitly infer object labels and bounding boxes using an installed model.";
81
81
  readonly ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.";
82
82
  readonly search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.";
83
- readonly grep: "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.";
83
+ readonly grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.";
84
84
  readonly def: "Find a symbol definition or declaration by qualified or unqualified name.";
85
85
  readonly refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.";
86
86
  readonly edit: "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.";
@@ -0,0 +1,3 @@
1
+ import type { GrepOutput } from "./client-output.ts";
2
+ /** Render native grep results without re-matching or reading source files. */
3
+ export declare function renderGrepOutput(output: GrepOutput): string;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export * from "./client.ts";
6
6
  export { serializeEditPlan } from "./edit-plan.ts";
7
7
  export * from "./hashline.ts";
8
8
  export * from "./show-presentation.ts";
9
+ export * from "./grep-presentation.ts";
10
+ export { parseGrepOutput } from "./client-output.ts";
9
11
  export { READSEEK_BINARY_ENV, READSEEK_PLATFORM_PACKAGES, readSeekBinaryName, readSeekPlatformKey, resolveReadSeekBinaryFromPackages, resolveReadSeekBinaryOverride, resolveReadSeekCacheDir, type ModuleResolver, type ReadSeekCacheOptions, } from "./platform.ts";
10
12
  export { canonicalPathKey, expandUserPath, isSubPath, resolveScopedPath, resolveToCwd, } from "./path.ts";
11
13
  export { cleanReadSeekErrorText } from "./error.ts";
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var PARAM_DESCRIPTIONS = {
36
36
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
37
37
  patternContext: "Surrounding code context for sub-statement patterns",
38
38
  selector: "Target AST node kind to extract from pattern context",
39
- grepPattern: "Regex pattern; set literal for exact text",
39
+ grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
40
40
  name: "Qualified or unqualified symbol name",
41
41
  refsName: "Identifier to find references for",
42
42
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
@@ -53,7 +53,6 @@ var PARAM_DESCRIPTIONS = {
53
53
  kind: "Restrict structure to this node kind",
54
54
  outline: "Show outline of document hierarchy or source symbols",
55
55
  noAnchors: "Omit source line anchors",
56
- text: "Literal native text query; adapter grep also supports regex via pattern",
57
56
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
58
57
  editVariant: "Exactly one nested edit variant",
59
58
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -85,9 +84,10 @@ var PARAM_DESCRIPTIONS = {
85
84
  literal: "Treat pattern literally",
86
85
  context: "Surrounding lines for each match",
87
86
  matchLimit: "Maximum matches to return",
87
+ grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
88
88
  summary: "Return per-file counts",
89
89
  grepScope: "Group matches by enclosing symbol",
90
- scopeContext: "Context lines within each symbol"
90
+ scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
91
91
  };
92
92
  var TOOL_DESCRIPTIONS = {
93
93
  show: "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.",
@@ -100,7 +100,7 @@ var TOOL_DESCRIPTIONS = {
100
100
  objects: "Explicitly infer object labels and bounding boxes using an installed model.",
101
101
  ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
102
102
  search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
103
- grep: "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.",
103
+ grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.",
104
104
  def: "Find a symbol definition or declaration by qualified or unqualified name.",
105
105
  refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
106
106
  edit: "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.",
@@ -208,7 +208,7 @@ var TOOL_CONTRACTS = [
208
208
  tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
209
209
  tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
210
210
  tool("search", "Search AST 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) }, []),
211
- tool("grep", "Search 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) }, []),
211
+ tool("grep", "Search text", { path: string(PARAM_DESCRIPTIONS.searchPath), limit: { ...integer(PARAM_DESCRIPTIONS.grepLimit), default: 100 }, ...gitScopeSchema, 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) }, ["pattern"]),
212
212
  tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
213
213
  tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
214
214
  tool("edit", "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"]),
@@ -236,7 +236,7 @@ var REPLACEABLE_TOOLS = [
236
236
  {
237
237
  tool: "grep",
238
238
  builtIn: "grep",
239
- benefit: "it returns LINE:HASH anchors."
239
+ benefit: "it runs native regex/literal matching and returns source LINE:HASH anchors or document locations."
240
240
  },
241
241
  {
242
242
  tool: "write",
@@ -275,7 +275,7 @@ var PREFERENCE_LEADS = {
275
275
  show: "Preferred for reading source with anchors.",
276
276
  edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
277
277
  write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
278
- grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
278
+ grep: "Preferred native text/regex search; returns edit-ready source LINE:HASH anchors and document node-local locations.",
279
279
  search: "Preferred for AST or structural code search with ast-grep-style patterns.",
280
280
  def: "Preferred for go-to-definition or declaration lookup by name.",
281
281
  refs: "Preferred for finding identifier-name matches or binding-specific references before `rename` or delete.",
@@ -286,7 +286,7 @@ var FALLBACK_TEMPLATES = {
286
286
  read: "Prefer `show` with fresh LINE:HASH anchors.",
287
287
  edit: "Prefer `edit` with fresh LINE:HASH anchors.",
288
288
  write: "Prefer `write` so the result returns LINE:HASH anchors.",
289
- grep: "Prefer `grep` for text/regex search with edit-ready anchors.",
289
+ grep: "Prefer `grep` for native text/regex search with source anchors and document locations.",
290
290
  apply_patch: "Prefer `edit` for existing text files."
291
291
  };
292
292
  function resolveToolName(name, aliases = {}) {
@@ -367,6 +367,59 @@ function validateReferencesSelection(input) {
367
367
  return { ok: false, error: "exactly one of name or at is required" };
368
368
  return { ok: true };
369
369
  }
370
+ function validateGrepParams(input) {
371
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
372
+ return { ok: false, error: "grep params must be an object" };
373
+ }
374
+ const params = input;
375
+ const allowed = [
376
+ "pattern",
377
+ "path",
378
+ "glob",
379
+ "ignoreCase",
380
+ "literal",
381
+ "context",
382
+ "limit",
383
+ "summary",
384
+ "scope",
385
+ "scopeContext",
386
+ "gitScope",
387
+ "signal",
388
+ "stdin",
389
+ "timeoutMs",
390
+ "cancelable"
391
+ ];
392
+ if (Object.keys(params).some((key) => !allowed.includes(key)))
393
+ return { ok: false, error: "unknown grep parameter" };
394
+ if (typeof params.pattern !== "string")
395
+ return { ok: false, error: "grep requires a pattern string" };
396
+ if (params.literal === true && params.pattern.length === 0)
397
+ return { ok: false, error: "literal pattern must not be empty" };
398
+ for (const key of ["path", "glob"]) {
399
+ if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
400
+ return { ok: false, error: `${key} must be a non-empty string` };
401
+ }
402
+ }
403
+ for (const key of ["ignoreCase", "literal", "summary"]) {
404
+ if (params[key] !== undefined && typeof params[key] !== "boolean")
405
+ return { ok: false, error: `${key} must be a boolean` };
406
+ }
407
+ for (const key of ["context", "limit", "scopeContext"]) {
408
+ const value = params[key];
409
+ const minimum = key === "limit" ? 1 : 0;
410
+ if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
411
+ return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
412
+ }
413
+ }
414
+ if (params.scope !== undefined && params.scope !== "symbol")
415
+ return { ok: false, error: "scope must be symbol" };
416
+ if (params.scopeContext !== undefined && params.scope !== "symbol")
417
+ return { ok: false, error: "scopeContext requires scope" };
418
+ if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
419
+ return { ok: false, error: "invalid gitScope" };
420
+ }
421
+ return { ok: true };
422
+ }
370
423
  var VARIANT_FIELDS = {
371
424
  set_line: ["anchor", "new_text"],
372
425
  replace_lines: ["start_anchor", "end_anchor", "new_text"],
@@ -755,11 +808,29 @@ function buildSearchArgs(input) {
755
808
  return finish(args);
756
809
  }
757
810
  function buildGrepArgs(input) {
758
- const args = start("grep", input);
759
- args.push("--text", input.text);
760
- limit2(args, input.limit);
811
+ const validated = validateGrepParams(input);
812
+ if (!validated.ok)
813
+ throw new Error(validated.error);
814
+ const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
815
+ const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
816
+ if (input.path !== undefined && !needsPathTerminator)
817
+ args.push(input.path);
818
+ if (input.literal)
819
+ args.push("--literal");
820
+ if (input.ignoreCase)
821
+ args.push("--ignore-case");
822
+ if (input.glob !== undefined)
823
+ args.push(`--glob=${input.glob}`);
824
+ pushOptionalNumber(args, "--context", input.context);
825
+ limit2(args, input.limit ?? 100);
826
+ if (input.summary)
827
+ args.push("--summary");
828
+ pushOptionalString(args, "--scope", input.scope);
829
+ pushOptionalNumber(args, "--scope-context", input.scopeContext);
761
830
  appendGitScopeArgs(args, input);
762
- return finish(args);
831
+ if (input.path !== undefined && needsPathTerminator)
832
+ args.push("--", input.path);
833
+ return args;
763
834
  }
764
835
  function buildDefArgs(input) {
765
836
  const args = start("def", input);
@@ -1191,25 +1262,41 @@ var DocumentOutput = Type.Object({
1191
1262
  assets: Type.Array(DocumentAsset, { default: [] }),
1192
1263
  vision: Type.Array(DocumentVision, { default: [] })
1193
1264
  });
1194
- var TextHit = Type.Object({
1195
- line: Int,
1196
- column: Int,
1265
+ var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
1266
+ var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
1267
+ var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
1268
+ var GrepLineSchema = Type.Object({
1269
+ line: GrepPosition,
1270
+ hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
1197
1271
  text: Str,
1198
- start_line: Int,
1199
- end_line: Int,
1200
- start_byte: Int,
1201
- end_byte: Int,
1202
- hashlines: Hashlines
1272
+ kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
1203
1273
  });
1204
- var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
1205
- var TextSearchOutput = Type.Object({
1206
- text: Str,
1207
- mode: Type.Literal("literal"),
1208
- total_matches: Int,
1209
- returned_matches: Int,
1274
+ var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
1275
+ var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
1276
+ var GrepSourceResultSchema = Type.Object({
1277
+ file: Str,
1278
+ file_hash: Str,
1279
+ language: Str,
1280
+ total_matches: GrepCount,
1281
+ returned_matches: GrepCount,
1282
+ hits: Type.Array(GrepHitSchema),
1283
+ lines: Type.Array(GrepLineSchema),
1284
+ symbols: Type.Array(GrepSymbolSchema)
1285
+ });
1286
+ var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
1287
+ var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
1288
+ var GrepOutputSchema = Type.Object({
1289
+ pattern: Str,
1290
+ mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
1291
+ ignore_case: Bool,
1292
+ summary: Bool,
1293
+ total_matches: GrepCount,
1294
+ returned_matches: GrepCount,
1210
1295
  truncated: Bool,
1211
- results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
1212
- documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1296
+ count_unit: Type.Literal("lines"),
1297
+ scope: Type.Optional(Type.Literal("symbol")),
1298
+ results: Type.Array(GrepSourceResultSchema),
1299
+ documents: Type.Array(GrepDocumentResultSchema)
1213
1300
  });
1214
1301
  var RuleFinding = Type.Object({
1215
1302
  name: Str,
@@ -1254,8 +1341,35 @@ function parseStructureEnvelope(value) {
1254
1341
  throw new Error("invalid readseek structure: missing structure");
1255
1342
  return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1256
1343
  }
1257
- function parseTextSearchOutput(value) {
1258
- return parse(TextSearchOutput, value, "text search output");
1344
+ function parseGrepOutput(value) {
1345
+ const output = parse(GrepOutputSchema, value, "grep output");
1346
+ const invalid = (message) => {
1347
+ throw new Error(`invalid readseek grep output: ${message}`);
1348
+ };
1349
+ const groups = [...output.results, ...output.documents];
1350
+ if (output.returned_matches > output.total_matches || groups.reduce((sum, group) => sum + group.total_matches, 0) !== output.total_matches || groups.reduce((sum, group) => sum + group.returned_matches, 0) !== output.returned_matches)
1351
+ invalid("inconsistent counts");
1352
+ for (const group of groups) {
1353
+ if (group.total_matches === 0 || group.returned_matches > group.total_matches)
1354
+ invalid("inconsistent file counts");
1355
+ if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
1356
+ invalid("inconsistent hit count");
1357
+ }
1358
+ for (const result of output.results) {
1359
+ if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
1360
+ invalid("summary contains display entries");
1361
+ if (output.scope !== "symbol" && result.symbols.length > 0)
1362
+ invalid("symbols require symbol scope");
1363
+ for (const hit of result.hits) {
1364
+ if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
1365
+ invalid("invalid hit span or anchor");
1366
+ }
1367
+ for (const symbol of result.symbols) {
1368
+ if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
1369
+ invalid("invalid symbol range");
1370
+ }
1371
+ }
1372
+ return output;
1259
1373
  }
1260
1374
  function parseRulesOutput(value) {
1261
1375
  return parse(RulesOutput, value, "rules output");
@@ -1372,9 +1486,9 @@ function createReadSeekClient(runner) {
1372
1486
  const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
1373
1487
  return parseSearchOutput(value);
1374
1488
  },
1375
- async grep(path, options) {
1376
- const value = await runner.kdl(buildGrepArgs({ ...options, path }), runOptions(options));
1377
- return parseTextSearchOutput(value);
1489
+ async grep(params) {
1490
+ const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
1491
+ return parseGrepOutput(value);
1378
1492
  },
1379
1493
  async def(path, options) {
1380
1494
  const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
@@ -1609,6 +1723,31 @@ function formatShowResult(presentation) {
1609
1723
 
1610
1724
  `);
1611
1725
  }
1726
+ // src/grep-presentation.ts
1727
+ function renderGrepOutput(output) {
1728
+ const rendered = [`${output.returned_matches} of ${output.total_matches} matching lines${output.truncated ? " (truncated)" : ""}`];
1729
+ const displayLine = (line) => `${line.line}:${line.hash}|${line.kind === "match" ? ">>" : " "}${line.text}`;
1730
+ for (const result of output.results) {
1731
+ rendered.push(`${result.file}: ${result.total_matches} matching lines (${result.returned_matches} returned)`);
1732
+ if (output.summary)
1733
+ continue;
1734
+ rendered.push(...result.lines.map(displayLine));
1735
+ for (const symbol of result.symbols) {
1736
+ rendered.push(` ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line})`);
1737
+ rendered.push(...symbol.lines.map(displayLine));
1738
+ }
1739
+ }
1740
+ for (const document of output.documents) {
1741
+ rendered.push(`${document.file}: ${document.total_matches} matching lines (${document.returned_matches} returned)`);
1742
+ if (output.summary)
1743
+ continue;
1744
+ for (const hit of document.hits) {
1745
+ rendered.push(` node:${hit.node_id}${hit.page === undefined ? "" : ` page:${hit.page}`} line:${hit.line} (${hit.kind}): ${hit.snippet}`);
1746
+ }
1747
+ }
1748
+ return rendered.join(`
1749
+ `);
1750
+ }
1612
1751
  // src/platform.ts
1613
1752
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1614
1753
  import { createRequire } from "node:module";
@@ -1930,6 +2069,46 @@ function search(root) {
1930
2069
  }))
1931
2070
  };
1932
2071
  }
2072
+ function grepChildren(node, allowed) {
2073
+ if (node.children.some((child) => !allowed.includes(child.name))) {
2074
+ throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
2075
+ }
2076
+ }
2077
+ function grepLine(node) {
2078
+ grepChildren(node, []);
2079
+ const parsed = line(node);
2080
+ if (requireProperty(node, "number") !== parsed.line)
2081
+ throw new Error("invalid readseek KDL grep line number");
2082
+ return { ...parsed, kind: requireProperty(node, "kind") };
2083
+ }
2084
+ function grep(root) {
2085
+ grepChildren(root, ["match", "document"]);
2086
+ return parseGrepOutput({
2087
+ ...properties(root),
2088
+ results: children(root, "match").map((match) => {
2089
+ grepChildren(match, ["hit", "line", "symbol"]);
2090
+ return {
2091
+ ...properties(match),
2092
+ hits: children(match, "hit").map((hit) => {
2093
+ grepChildren(hit, []);
2094
+ return properties(hit);
2095
+ }),
2096
+ lines: children(match, "line").map(grepLine),
2097
+ symbols: children(match, "symbol").map((symbol) => {
2098
+ grepChildren(symbol, ["line"]);
2099
+ return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
2100
+ })
2101
+ };
2102
+ }),
2103
+ documents: children(root, "document").map((document) => {
2104
+ grepChildren(document, ["hit"]);
2105
+ return { ...properties(document), hits: children(document, "hit").map((hit) => {
2106
+ grepChildren(hit, []);
2107
+ return properties(hit);
2108
+ }) };
2109
+ })
2110
+ });
2111
+ }
1933
2112
  function references(root) {
1934
2113
  const parsed = children(root, "reference").map((reference) => {
1935
2114
  const sourceLine = child(reference, "line");
@@ -2045,7 +2224,7 @@ function parseReadSeekKdl(text) {
2045
2224
  case "document":
2046
2225
  return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
2047
2226
  case "search":
2048
- return search(root);
2227
+ return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
2049
2228
  case "check":
2050
2229
  return diagnostics(root);
2051
2230
  case "refs":
@@ -2379,6 +2558,7 @@ export {
2379
2558
  getToolContract,
2380
2559
  isShowTool,
2381
2560
  isSubPath,
2561
+ parseGrepOutput,
2382
2562
  parseLineAnchor,
2383
2563
  parseReadSeekKdl,
2384
2564
  parseSelector,
@@ -2390,6 +2570,7 @@ export {
2390
2570
  readSeekProcessEnvironment,
2391
2571
  renderCompactGuidelines,
2392
2572
  renderFallbackToolDescription,
2573
+ renderGrepOutput,
2393
2574
  renderPreferredToolDescription,
2394
2575
  renderToolDescription,
2395
2576
  renderToolRoutingPolicy,
@@ -2410,5 +2591,6 @@ export {
2410
2591
  serializeEditPlan,
2411
2592
  tryParseLineAnchor,
2412
2593
  validateExactlyOneEditVariant,
2594
+ validateGrepParams,
2413
2595
  validateReferencesSelection
2414
2596
  };
@@ -9,6 +9,7 @@ export type ReferencesSelectionInput = {
9
9
  at?: string;
10
10
  };
11
11
  export declare function validateReferencesSelection(input: ReferencesSelectionInput): ValidationResult;
12
+ export declare function validateGrepParams(input: unknown): ValidationResult;
12
13
  export type SetLineEdit = {
13
14
  set_line: {
14
15
  anchor: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jarkkojs/readseek-api",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Shared ReadSeek CLI contract, validation, argv builders, and bounded process runners",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -51,7 +51,7 @@
51
51
  "vitest": "^4.1.0"
52
52
  },
53
53
  "dependencies": {
54
- "@jarkkojs/readseek": "^0.12.0",
54
+ "@jarkkojs/readseek": "^0.12.1",
55
55
  "@bgotink/kdl": "0.4.0",
56
56
  "@sinclair/typebox": "0.34.52"
57
57
  }