pi-readseek 0.2.0 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
5
5
  "type": "module",
6
6
  "exports": {
@@ -39,7 +39,7 @@
39
39
  "node": ">=20.0.0"
40
40
  },
41
41
  "dependencies": {
42
- "@jarkkojs/readseek": "0.2.2",
42
+ "@jarkkojs/readseek": "0.2.6",
43
43
  "diff": "^8.0.3",
44
44
  "ignore": "^7.0.5",
45
45
  "picomatch": "^4.0.4",
package/prompts/edit.md CHANGED
@@ -1,4 +1,4 @@
1
- Surgically edit existing text files. Prefer hash-verified anchored edits from fresh `read`, `grep`, `search`, or `write` output; copy `LINE:HASH` anchors exactly.
1
+ Surgically edit existing text files. Prefer hash-verified anchors from fresh `read`, `grep`, `search`, or `write` output; copy `LINE:HASH` anchors exactly.
2
2
 
3
3
  `edit` requires the target file to have been anchored earlier in the current session. If you get `file-not-read`, run `read`, `grep`, `search`, or `write` first.
4
4
 
@@ -6,16 +6,15 @@ Surgically edit existing text files. Prefer hash-verified anchored edits from fr
6
6
 
7
7
  | Variant | Use | Anchors |
8
8
  |---|---|---|
9
- | `set_line` | Replace/delete one line | 1 |
10
- | `replace_lines` | Replace/delete a contiguous range | 2 |
9
+ | `set_line` | Replace or delete one line | 1 |
10
+ | `replace_lines` | Replace or delete one contiguous range | 2 |
11
11
  | `insert_after` | Insert after an existing line | 1 |
12
- | `replace_symbol` | Replace one function/class/method/etc. | 0 (`symbol`) |
13
- | `replace` | String replacement escape hatch; one match by default, all with `all: true` | 0 |
12
+ | `replace_symbol` | Replace one function/class/method/interface/type/enum/etc. | 0 (`symbol`) |
13
+ | `replace` | Exact string replacement escape hatch; one match by default, all with `all: true` | 0 |
14
14
 
15
- Set `new_text` (or `replace_lines` `new_text`) to `""` to delete the anchored line(s). For an intentionally blank line, use `"\n"` or whitespace content, not `""`.
16
- Prefer `set_line`, `replace_lines`, and `insert_after`: they verify the file still matches the anchored content. Use `replace` only when anchors are impractical, such as repeated text across many unrelated lines.
15
+ Set `new_text` (or `replace_lines.new_text`) to `""` to delete anchored line(s). For an intentionally blank line, use `"\n"` or whitespace content, not `""`.
17
16
 
18
- `replace` is exact-only by default: missing `old_text` fails with `text-not-found`. Wrap old_text/new_text in {replace: ...} a bare top-level `{old_text, new_text}` inside `edits[]` is rejected with guidance. `fuzzy: true` is a narrow fallback that only normalizes whitespace and confusable Unicode (e.g. smart hyphens) after exact matching fails; it is **not approximate or Levenshtein/semantic matching** and will not find renamed or reworded text. When fuzzy matching is used, the response warns that exact text was not found.
17
+ Prefer `set_line`, `replace_lines`, and `insert_after`: they verify that the file still matches the anchored content. Use `replace` only when anchors are impractical, such as repeated text across many unrelated lines.
19
18
 
20
19
  ## Input shape
21
20
 
@@ -32,29 +31,32 @@ Prefer `set_line`, `replace_lines`, and `insert_after`: they verify the file sti
32
31
  }
33
32
  ```
34
33
 
35
- Use only the variant(s) needed for the task; the example shows all shapes together for reference. Each `edits[]` entry must contain exactly one variant key. `new_text` / `new_body` is plain file content — no hash prefixes or diff markers.
34
+ Use only the needed variant(s); the example shows all shapes for reference. Each `edits[]` entry must contain exactly one variant key. `new_text` / `new_body` is plain file content — no hash prefixes or diff markers.
36
35
 
37
- ## Optional post-edit verification
36
+ ## Exact and fuzzy replacement
37
+
38
+ `replace` is exact-only by default. Missing `old_text` fails with `text-not-found`.
38
39
 
39
- `postEditVerify: true` opts into post-write persisted-content verification for this one call. It is default off: when omitted or false, successful edits use the normal fast path and do not perform an extra read-back check.
40
+ Wrap string replacements as `{ "replace": { "old_text": "...", "new_text": "..." } }`; a bare top-level `{ old_text, new_text }` inside `edits[]` is rejected with guidance.
40
41
 
41
- When enabled, `edit` first runs the normal validation and write path. Only after the write succeeds, it reads the file back from disk and compares the persisted content to the exact intended content, including BOM restoration and original line-ending restoration. This is not syntax validation; syntax validation is the separate pre-write `syntaxValidate` / `PI_HASHLINE_SYNTAX_VALIDATE` guard described below.
42
+ `fuzzy: true` is a narrow fallback after exact matching fails. It normalizes whitespace and confusable Unicode such as smart hyphens; it is **not** approximate, Levenshtein, or semantic matching. Fuzzy successes return a warning.
42
43
 
43
44
  ## `replace_symbol`
44
45
 
45
- Use `replace_symbol` to replace one function, class, method, interface, type, enum, or similar symbol. Query symbols like `read symbol:`: `Name`, `Class.method`, or `Name@<line>`.
46
+ Use `replace_symbol` when you want to replace one whole mapped symbol without line anchors. Query symbols like `read({ symbol })`: `Name`, `Class.method`, or `Name@<line>`.
46
47
 
47
48
  Rules:
48
- - Use an exact name, dotted path, or `@<line>`. If `read({symbol})` returned a fuzzy match, confirm the exact symbol before editing.
49
+
50
+ - Use an exact name, dotted path, or `@<line>`. If `read({ symbol })` returned a fuzzy match, confirm the exact symbol first.
49
51
  - Supported for TypeScript, JavaScript, Rust, and Java. For other languages, use anchored edits.
50
52
  - `new_body` must not be empty or whitespace-only.
51
53
  - Write `new_body` without extra leading indentation; `edit` re-indents it to match the original symbol.
52
54
  - If `new_body` appears to declare a different symbol name, the edit still applies but returns a `name-mismatch` warning.
53
- - Do not combine `replace_symbol` with anchored edits that touch the same lines. Duplicate/overlapping `replace_symbol` ranges are rejected.
55
+ - Do not combine `replace_symbol` with anchored edits that touch the same lines. Duplicate or overlapping `replace_symbol` ranges are rejected.
54
56
 
55
57
  ## Stale anchors
56
58
 
57
- If anchors no longer match, `edit` fails with a hash mismatch (`hash-mismatch`) and shows nearby current lines. Lines marked `>>>` include updated anchors:
59
+ If anchors no longer match, `edit` fails with `hash-mismatch` and shows nearby current lines. Lines marked `>>>` include updated anchors:
58
60
 
59
61
  ```text
60
62
  >>> 41:b34| const renamed = 3;
@@ -62,32 +64,39 @@ If anchors no longer match, `edit` fails with a hash mismatch (`hash-mismatch`)
62
64
 
63
65
  Copy the updated `LINE:HASH` and retry. If the target moved farther away, re-run `read`, `grep`, `search`, or `write` for fresh anchors.
64
66
 
65
- If `edit` auto-relocates an anchor, check the warning and verify the edit landed in the intended place.
67
+ If `edit` auto-relocates an anchor, read the warning and verify that the edit landed in the intended place.
66
68
 
67
69
  ## Validation and warnings
68
70
 
69
71
  - All edits are checked before writing; if a hard validation fails, nothing is written.
70
72
  - Anchored edits are applied bottom-up so line numbers stay stable.
71
73
  - `no-op` means the requested edit matched the current file already or produced identical content.
72
- - A whitespace-only warning means formatting changed but behavior probably did not.
73
- - A `replace`-only success may include a reminder to prefer anchored edits next time.
74
+ - Whitespace-only warnings mean formatting changed but behavior probably did not.
75
+ - A `replace`-only success may remind you to prefer anchored edits next time.
74
76
 
75
77
  Syntax validation runs before writing when supported:
78
+
76
79
  - Supported: Rust, C++, C headers, Java.
77
80
  - Default `warn`: write succeeds, but warnings include `syntax-regression: lines X-Y`.
78
81
  - `block`: aborts without writing.
79
82
  - `off`: skips validation.
80
83
  - `PI_HASHLINE_SYNTAX_VALIDATE` can set the default mode.
81
84
 
82
- Existing syntax errors are tolerated; the warning is for newly introduced parser errors.
85
+ Existing syntax errors are tolerated; warnings are for newly introduced parser errors.
86
+
87
+ ## Optional post-edit verification
88
+
89
+ `postEditVerify: true` opts into read-back verification for this call. It is off by default. When enabled, `edit` writes normally, then reads the file back and compares persisted content to the intended content, including BOM and original line endings. This is not syntax validation.
83
90
 
84
91
  ## Diff data contract
85
92
 
86
- Successful `edit` results include `details.diffData` and `details.readseekValue.diffData` in addition to the existing `details.diff` / `readseekValue.diff` string fields. The string fields remain the backward-compatible human-readable fallback.
93
+ Successful `edit` results include:
87
94
 
88
- Successful `edit` results also include `details.patch`: a standard unified diff (`---`/`+++` file headers and `@@` hunk headers) generated from the pre-/post-edit file contents. Use `details.patch` when you need a portable, tool-agnostic patch (e.g. to apply elsewhere); use the compact `details.diff` hashline string for human-readable in-session display. `details.patch` is additive and does not change `details.diff`.
95
+ - `details.diff` and `details.readseekValue.diff`: compact human-readable hashline diff strings.
96
+ - `details.patch`: standard unified diff with file and hunk headers.
97
+ - `details.diffData` and `details.readseekValue.diffData`: stable structured diff data.
89
98
 
90
- `diffData` is a stable versioned contract:
99
+ `diffData` shape:
91
100
 
92
101
  ```ts
93
102
  type DiffData = {
@@ -110,4 +119,4 @@ type DiffData = {
110
119
  };
111
120
  ```
112
121
 
113
- For compact one-line hashline diffs, `details.diff` remains compact, while `diffData.entries` uses expanded remove/add rows so renderers can show inline word changes without breaking hashline output.
122
+ For compact one-line hashline diffs, `details.diff` remains compact while `diffData.entries` uses expanded remove/add rows so renderers can show inline word changes without breaking hashline output.
package/prompts/find.md CHANGED
@@ -1,4 +1,4 @@
1
- Find files recursively by name. Uses glob patterns by default, respects nested `.gitignore`, includes hidden files, and returns relative paths.
1
+ Find files or directories recursively by basename. Uses glob patterns by default, respects nested `.gitignore`, includes hidden entries, and returns relative paths.
2
2
 
3
3
  ## Parameters
4
4
 
@@ -9,11 +9,10 @@ Find files recursively by name. Uses glob patterns by default, respects nested `
9
9
  - `maxDepth` — non-negative directory depth limit.
10
10
  - `sortBy` — `"name"` default, `"mtime"`, or `"size"`; use `reverse: true` for descending/newest/largest first.
11
11
  - `modifiedSince` — keep entries modified strictly after an ISO date/time or relative age like `30m`, `1h`, `24h`, `7d`.
12
- - `minSize` / `maxSize` — file-size filters, inclusive; numbers are bytes, strings accept 1024-based `KB`, `MB`, `GB`, etc. Directories are not removed by size filters.
12
+ - `minSize` / `maxSize` — inclusive file-size filters; numbers are bytes, strings accept 1024-based `KB`, `MB`, `GB`, etc. Directories are not removed by size filters.
13
13
 
14
14
  ## Output and usage
15
15
 
16
- One relative path per line. Directories end with `/`. If results exceed `limit` or 50 KB, output says it was truncated.
17
- Filtering and sorting happen before `limit`, so queries like largest/newest files work as expected.
16
+ Output is one relative path per line. Directories end with `/`. Filtering and sorting happen before `limit`, so newest/largest queries work as expected.
18
17
 
19
- Use `find` for recursive file-name discovery, `ls` for one directory, and `grep` for file contents. Remember: `pattern` matches basenames, not full paths.
18
+ Use `find` for recursive name discovery, `ls` for one directory, `grep` or `search` for contents, and `read` for file content. Remember: `pattern` matches basenames, not full paths.
package/prompts/grep.md CHANGED
@@ -1,26 +1,28 @@
1
- Search file contents. Non-summary results return `LINE:HASH` anchors usable directly by `edit`; no follow-up `read` is needed.
1
+ Search file contents. Non-summary results include edit-ready `LINE:HASH` anchors, so you usually do not need a follow-up `read` before `edit`.
2
2
 
3
3
  ## Modes
4
4
 
5
- - Default: matching lines only. Output lines are `path:>>LINE:HASH|content`; `>>` marks matches.
6
- - `context: N`: include N lines before/after each match. Context lines use `path: LINE:HASH|content`; nearby ranges are merged/deduped.
7
- - `summary: true`: return per-file match counts only — no line content or anchors. Use first for broad searches, then narrow with `path`/`glob`.
8
- - `scope: "symbol"`: group matches by enclosing symbol. By default returns the full symbol block. `scopeContext: N` windows to ±N lines around each match, clipped to the symbol; `0` returns only match lines. Ignored when `summary: true`.
5
+ - Default: matching lines only. Match rows look like `path:>>LINE:HASH|content`.
6
+ - `context: N`: include N lines before and after each match. Context rows use `path: LINE:HASH|content`; nearby ranges are merged and deduped.
7
+ - `summary: true`: return per-file match counts only. Use this first for broad searches, then narrow with `path`, `glob`, or a stricter pattern.
8
+ - `scope: "symbol"`: group matches by enclosing symbol. By default returns full symbol blocks. `scopeContext: N` clips each match to ±N lines inside the symbol; `0` returns only match lines. Ignored with `summary: true`.
9
9
 
10
10
  ## Parameters
11
11
 
12
- - `pattern` — regex by default; use `literal: true` for exact strings or regex metacharacters.
12
+ - `pattern` — regular expression by default; set `literal: true` for exact text or regex metacharacters.
13
13
  - `path` — file or directory, default cwd.
14
- - `glob` — file filter, e.g. `'*.ts'` or `'**/*.test.ts'`.
14
+ - `glob` — file-name filter such as `*.ts` or `**/*.test.ts`.
15
15
  - `ignoreCase` — case-insensitive search.
16
16
  - `context` — surrounding lines for normal grep.
17
- - `limit` — max matches, default 100.
17
+ - `limit` — maximum matches, default 100.
18
18
  - `summary` — counts only, no anchors.
19
19
  - `scope` — only `"symbol"` is supported.
20
20
  - `scopeContext` — non-negative context within symbol scope; requires `scope: "symbol"`.
21
21
 
22
- ## Truncation and guidance
22
+ ## Use well
23
23
 
24
- If matches hit `limit`, output appends `[Results truncated at N matches refine pattern or increase limit]`. Large non-summary results may cap displayed matches per file and/or head-truncate by output budget; narrow with `summary`, `path`, `glob`, or a more specific pattern.
24
+ Use `grep` for text: identifiers, strings, config keys, error messages, comments, or docs. Use `literal: true` unless you want regex behavior.
25
25
 
26
- Use `grep` for text search. For structural code patterns such as calls, imports, or JSX, prefer `search`.
26
+ For code shape calls, imports, declarations, JSX, object literals, control flow — prefer `search`, which parses AST patterns.
27
+
28
+ If output says results were truncated at `limit` or by display budget, narrow before editing. Good narrowing order: `summary` → `path`/`glob` → stricter pattern → `scope: "symbol"` or `context`.
package/prompts/ls.md CHANGED
@@ -1,11 +1,11 @@
1
- List one directory. Shows directories first with `/`, then files, sorted alphabetically; dotfiles are included.
1
+ List one directory. Output is directories first (with `/`), then files, sorted alphabetically; dotfiles are included.
2
2
 
3
3
  ## Parameters
4
4
 
5
5
  - `path` — directory to list, default cwd.
6
6
  - `limit` — max entries, default 500; must be positive.
7
- - `glob` — optional entry-name filter such as `*.ts` or `.env*`.
7
+ - `glob` — optional entry-name filter such as `*.ts`, `.env*`, or `test-*`.
8
8
 
9
9
  ## Usage
10
10
 
11
- Output is one entry per line. Use `ls` to inspect a single directory, `find` for recursive discovery, and `read` for file contents. If output exceeds `limit` or 50 KB, it says so.
11
+ Use `ls` to inspect one known directory. Use `find` for recursive discovery, `grep` or `search` for contents, and `read` for file content. If output exceeds `limit` or 50 KB, narrow with `glob` or switch to `find`.
package/prompts/read.md CHANGED
@@ -1,13 +1,21 @@
1
- Read text files with `LINE:HASH|content` anchors usable by `edit`. Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}. Images return attachments, not edit anchors.
1
+ Read files through readseek. Text output uses `LINE:HASH|content` anchors that can be copied directly into `edit`; supported images return attachments instead of anchors. Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}.
2
+
3
+ ## Choose the right read
4
+
5
+ - Normal read: inspect a whole small file or a targeted `offset` / `limit` range.
6
+ - `map: true`: append a structural map for navigation before reading more code.
7
+ - `symbol: "Name"`: read one function, class, method, interface, type, enum, or similar symbol.
8
+ - `bundle: "local"`: with `symbol`, include direct same-file local support when readseek can identify it.
2
9
 
3
10
  ## Parameters
4
11
 
5
- - `offset` / `limit` positive line numbers for targeted reads; `offset` is 1-indexed.
6
- - `map: true` — append a full-file structural map even for small files. May combine with `offset` / `limit`; cannot combine with `symbol` or `bundle`.
7
- - `symbol: "Name"` — read one symbol range by name, with hash anchors. Supports `ClassName.method`, Java package-relative names, and `Name@<line>` disambiguation. Cannot combine with `offset` / `limit`.
8
- - `bundle: "local"` — with `symbol`, also include direct same-file local support when available. Cannot combine with `map`.
12
+ - `path` — file path.
13
+ - `offset` / `limit` positive line numbers; `offset` is 1-indexed.
14
+ - `map` — append the full-file structural map; cannot combine with `symbol` or `bundle`.
15
+ - `symbol` — symbol query; supports `Class.method`, package-relative Java names, and `Name@<line>` disambiguation; cannot combine with `offset` / `limit`.
16
+ - `bundle` — only `"local"`; requires `symbol` and cannot combine with `map`.
9
17
 
10
- When a full-file read is truncated, a readseek structural map is appended automatically when available. Use that map's line ranges for follow-up `read({ offset, limit })`. Map and symbol coverage depends on readseek language support.
18
+ When a full-file read is truncated, readseek appends a structural map automatically when available. Use map line ranges for follow-up `read({ offset, limit })` calls.
11
19
 
12
20
  ## Symbol examples
13
21
 
@@ -16,18 +24,18 @@ When a full-file read is truncated, a readseek structural map is appended automa
16
24
  | `{ "symbol": "processEvent" }` | function or top-level symbol |
17
25
  | `{ "symbol": "EventEmitter" }` | class/interface/type/enum/etc. |
18
26
  | `{ "symbol": "EventEmitter.emit" }` | child method/member |
19
- | `{ "symbol": "Foo.bar@42" }` | specific overload/definition near line 42 |
20
- | `{ "symbol": "handleRequest", "bundle": "local" }` | symbol plus direct local support |
27
+ | `{ "symbol": "Foo.bar@42" }` | overload/definition near line 42 |
28
+ | `{ "symbol": "handleRequest", "bundle": "local" }` | symbol plus direct same-file support |
21
29
 
22
30
  ## Symbol resolution
23
31
 
24
- `@<line>` only applies as a trailing suffix like `Foo.bar@42`; names such as `foo@bar` are ordinary queries. Resolution order: containing range → nearest symbol starting at/after the requested line → nearest symbol above it. If unresolved but same-name candidates exist, the response lists retry hints like `name@<startLine>`.
32
+ `@<line>` only applies as a trailing suffix like `Foo.bar@42`; names such as `foo@bar` are ordinary queries. Resolution order: containing range → nearest symbol starting at/after the requested line → nearest symbol above it.
25
33
 
26
34
  Result behavior:
35
+
27
36
  - **Found**: returns only the symbol range with `[Symbol: name (kind), lines X-Y of Z]`.
28
- - **Ambiguous**: returns candidate names/kinds/ranges; retry with dot notation or `@<line>`.
29
- - **Fuzzy**: returns the best camelCase/substring match with a warning banner and confirmation hint. Verify before editing from fuzzy-match anchors.
30
- - **Not found**: falls back to normal read with a warning listing available symbols.
31
- - **Unmappable**: falls back to normal read with a warning.
37
+ - **Ambiguous**: lists candidates and retry hints such as `name@<startLine>`.
38
+ - **Fuzzy**: returns the best camelCase/substring match with a warning; verify before editing from those anchors.
39
+ - **Not found** or **unmappable**: falls back to normal read with a warning and, when available, symbol suggestions.
32
40
 
33
- Hash anchors from symbol and bundled reads are valid for `edit`.
41
+ Hash anchors from normal, symbol, and bundled reads are valid for `edit` until the file changes.
package/prompts/sg.md CHANGED
@@ -1,9 +1,9 @@
1
- AST-aware structural code search. Use when text search is too broad or brittle and you need code shape, such as calls, imports, declarations, or JSX. Returns matches grouped by file with edit-ready hashline anchors.
1
+ Search code with readseek AST patterns. Use it when text search is too broad or brittle and the query depends on syntax: calls, imports, declarations, JSX, object fields, control flow, and similar code shapes. Results are grouped by file with edit-ready hashline anchors.
2
2
 
3
3
  ## Parameters
4
4
 
5
5
  - `pattern` — ast-grep-style pattern to match.
6
- - `lang` — language hint such as `typescript`, `tsx`, `javascript`, `jsx`, `rust`, `python`, `dockerfile`, `lua`, `nix`, `perl`, or `zig`; set it when syntax is ambiguous.
6
+ - `lang` — language hint; set it when syntax is ambiguous, extensionless, generated, or TSX/JSX-like.
7
7
  - `path` — file or directory, default cwd.
8
8
  - `cached` — in a Git repository, search tracked/indexed files.
9
9
  - `others` — in a Git repository, search untracked files.
@@ -12,9 +12,11 @@ AST-aware structural code search. Use when text search is too broad or brittle a
12
12
  ## Pattern syntax
13
13
 
14
14
  - `$NAME` matches one AST node.
15
- - `$_` matches any one node.
16
- - `$$$ARGS` matches zero or more nodes; use `$$$` for variable-length args, body statements, object fields, JSX children, etc.
17
- - Reusing the same metavariable name requires each occurrence to match the same source text.
15
+ - `$_` matches any one AST node when you do not need to reuse it.
16
+ - `$$$ARGS` matches zero or more sibling nodes. Use it for function args, body statements, object fields, JSX children, etc.
17
+ - Reusing a metavariable name requires every occurrence to match the same source text.
18
+
19
+ Patterns are parsed as code, not text. Formatting is mostly ignored, but syntax must be valid for the selected language. Include punctuation that the language grammar requires.
18
20
 
19
21
  ## Examples
20
22
 
@@ -23,9 +25,16 @@ AST-aware structural code search. Use when text search is too broad or brittle a
23
25
  - `export function $NAME($$$PARAMS) { $$$BODY }` — exported functions.
24
26
  - `$OBJ.$METHOD($$$ARGS)` — method calls.
25
27
  - `<$TAG $$$ATTRS>$$$CHILDREN</$TAG>` — JSX/TSX elements.
28
+ - `if ($COND) { $$$BODY }` — control-flow blocks.
29
+
30
+ ## Languages
31
+
32
+ Useful `lang` values include `assembly`, `bash`, `c`, `cpp`, `csharp`, `css`, `dockerfile`, `gdscript`, `go`, `html`, `java`, `javascript`, `json`, `jsx`, `just`, `kconfig`, `latex`, `lua`, `make`, `markdown`, `meson`, `nix`, `perl`, `php`, `puppet`, `python`, `riscv`, `ruby`, `rust`, `sql`, `swift`, `toml`, `tsx`, `typescript`, `typst`, `xml`, `yaml`, `zig`, and `unknown`.
33
+
34
+ `unknown` forces text-only handling and is not useful for parser-backed search.
26
35
 
27
- ## Tips
36
+ ## Git selection
28
37
 
29
- Patterns are parsed as code, not text: formatting is mostly ignored, but syntax must be valid for `lang`. Include semicolons in languages that require them. Use `grep` for plain text and `search` for structure.
38
+ When searching a directory inside a Git repository, readseek defaults to tracked/indexed files plus untracked non-ignored files. Use `cached`, `others`, and `ignored` to narrow or expand that selection. `ignored` requires `others`.
30
39
 
31
- When searching a directory inside a Git repository, readseek 0.2.x defaults to tracked/indexed files plus untracked non-ignored files. Use `cached`, `others`, and `ignored` to narrow or expand that Git selection.
40
+ Use `grep` for plain text and `search` for structure.
package/prompts/write.md CHANGED
@@ -1,24 +1,24 @@
1
- Write full file content. Creates new files and parent directories, overwrites existing files, and returns `LINE:HASH` anchors for immediate `edit` use.
1
+ Create or overwrite a whole file and return `LINE:HASH` anchors for immediate follow-up `edit` calls.
2
2
 
3
3
  ## Use / avoid
4
4
 
5
- Use `write` to create a file or intentionally replace a whole file. For small changes or appends, `read` first and use `edit` (`insert_after` for appends).
5
+ Use `write` for new files, generated files, or intentional full-file replacement. For small changes or appends to an existing file, read or search first and use `edit` (`insert_after` for appends).
6
6
 
7
- Existing files are overwritten without confirmation. Binary-looking content is written, but hashlines are not generated, so there are no anchors to feed into `edit`.
7
+ Existing files are overwritten without confirmation. Binary-looking content can be written, but hashlines are not generated, so there are no anchors for `edit`.
8
8
 
9
9
  ## Parameters
10
10
 
11
11
  - `path` — relative or absolute file path.
12
12
  - `content` — complete file contents.
13
- - `map` — optional; append a structural map when possible. Map append is best-effort and write still succeeds if map generation fails.
13
+ - `map` — optional; append a structural map when possible. Map generation is best-effort and does not make the write fail.
14
14
 
15
15
  ## Output
16
16
 
17
- Successful text writes return `LINE:HASH|content`; display hashlines escape control characters for safe rendering. Visible output is capped at 2000 lines or 50 KB, but full anchors remain available in `readseekValue`.
17
+ Successful text writes return `LINE:HASH|content`. Display hashlines escape control characters for safe rendering. Visible output is capped at 2000 lines or 50 KB, but full anchors remain available in `readseekValue`.
18
18
 
19
19
  ## Diff data contract
20
20
 
21
- Successful text `write` results include additive final `details.diff`, `details.readseekValue.diff`, `details.diffData`, and `details.readseekValue.diffData` fields. The string fields remain the backward-compatible human-readable fallback.
21
+ Successful text `write` results include additive final `details.diff`, `details.readseekValue.diff`, `details.diffData`, and `details.readseekValue.diffData` fields. String diff fields remain the backward-compatible human-readable fallback.
22
22
 
23
23
  `diffData` is a stable versioned contract:
24
24
 
@@ -43,4 +43,4 @@ type DiffData = {
43
43
  };
44
44
  ```
45
45
 
46
- For compact one-line hashline diffs, `details.diff` remains compact, while `diffData.entries` uses expanded remove/add rows so renderers can show inline word changes without breaking hashline output.
46
+ For compact one-line hashline diffs, `details.diff` remains compact while `diffData.entries` uses expanded remove/add rows so renderers can show inline word changes without breaking hashline output.
@@ -5,7 +5,7 @@ import { THRESHOLDS } from "./constants.js";
5
5
  import type { FileMap, MapOptions } from "./types.js";
6
6
 
7
7
  export const READSEEK_MAPPER_NAME = "readseek";
8
- export const READSEEK_MAPPER_VERSION = 1;
8
+ export const READSEEK_MAPPER_VERSION = 2;
9
9
 
10
10
  export interface MapperIdentity {
11
11
  mapperName: string;
@@ -38,7 +38,7 @@ export async function generateMapWithIdentity(
38
38
  throwIfAborted(options.signal);
39
39
  const fileStat = await stat(filePath);
40
40
  throwIfAborted(options.signal);
41
- const map = await readseekMap(filePath, fileStat.size);
41
+ const map = await readseekMap(filePath, fileStat.size, { signal: options.signal });
42
42
  throwIfAborted(options.signal);
43
43
  return { map, ...READSEEK_MAPPER_IDENTITY };
44
44
  }
@@ -56,7 +56,7 @@ export async function generateMapFromContent(
56
56
  options: MapOptions = {},
57
57
  ): Promise<FileMap | null> {
58
58
  throwIfAborted(options.signal);
59
- const map = await readseekMapContent(filePath, content);
59
+ const map = await readseekMapContent(filePath, content, { signal: options.signal });
60
60
  throwIfAborted(options.signal);
61
61
  return map;
62
62
  }
@@ -1,7 +1,5 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, type StdioOptions } from "node:child_process";
2
2
  import { createRequire } from "node:module";
3
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
3
  import path from "node:path";
6
4
 
7
5
  import { DetailLevel } from "./readseek/enums.js";
@@ -27,7 +25,7 @@ export interface ReadseekReadOutput {
27
25
  interface ReadseekSymbol {
28
26
  kind: string;
29
27
  name: string;
30
- address: string;
28
+ qualified_name: string;
31
29
  start_line: number;
32
30
  end_line: number;
33
31
  start_hash: string;
@@ -90,34 +88,41 @@ function normalizeKind(kind: string): FileSymbol["kind"] {
90
88
  return SymbolKind.Unknown;
91
89
  }
92
90
 
91
+ function parentQualifiedNameFor(qualifiedName: string): string {
92
+ const lastDot = qualifiedName.lastIndexOf(".");
93
+ return lastDot === -1 ? "" : qualifiedName.slice(0, lastDot);
94
+ }
95
+
93
96
  function symbolsFromReadseek(symbols: ReadseekSymbol[]): FileSymbol[] {
94
- const byAddress = new Map<string, FileSymbol[]>();
95
- const entries: Array<{ address: string; parentAddress: string; symbol: FileSymbol }> = [];
97
+ const symbolsByQualifiedName = new Map<string, FileSymbol[]>();
98
+ const entries: Array<{ parentQualifiedName: string; symbol: FileSymbol }> = [];
96
99
 
97
100
  for (const symbol of symbols) {
98
- const address = symbol.address || symbol.name;
99
- const parentAddress = address.includes(".") ? address.slice(0, address.lastIndexOf(".")) : "";
101
+ const parentQualifiedName = parentQualifiedNameFor(symbol.qualified_name);
100
102
  const fileSymbol: FileSymbol = {
101
103
  name: symbol.name,
102
104
  kind: normalizeKind(symbol.kind),
103
105
  startLine: symbol.start_line,
104
106
  endLine: symbol.end_line,
105
107
  };
106
- const bucket = byAddress.get(address);
108
+ const bucket = symbolsByQualifiedName.get(symbol.qualified_name);
107
109
  if (bucket) bucket.push(fileSymbol);
108
- else byAddress.set(address, [fileSymbol]);
109
- entries.push({ address, parentAddress, symbol: fileSymbol });
110
+ else symbolsByQualifiedName.set(symbol.qualified_name, [fileSymbol]);
111
+ entries.push({ parentQualifiedName, symbol: fileSymbol });
110
112
  }
111
113
 
112
114
  const roots: FileSymbol[] = [];
113
115
  for (const entry of entries) {
114
- const parent = entry.parentAddress ? byAddress.get(entry.parentAddress)?.[0] : undefined;
115
- if (parent) {
116
- parent.children ??= [];
117
- parent.children.push(entry.symbol);
118
- } else {
116
+ const parent = entry.parentQualifiedName
117
+ ? symbolsByQualifiedName.get(entry.parentQualifiedName)?.[0]
118
+ : undefined;
119
+ if (!parent) {
119
120
  roots.push(entry.symbol);
121
+ continue;
120
122
  }
123
+
124
+ parent.children ??= [];
125
+ parent.children.push(entry.symbol);
121
126
  }
122
127
 
123
128
  return roots;
@@ -156,14 +161,29 @@ export function isReadseekAvailable(): boolean {
156
161
  }
157
162
  }
158
163
 
159
- async function runReadseek(args: string[], options: { signal?: AbortSignal } = {}): Promise<unknown> {
160
- const { stdout, stderr } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
161
- const child = spawn(readseekBinaryPath(), args, { stdio: ["ignore", "pipe", "pipe"], signal: options.signal });
164
+ interface RunReadseekOptions {
165
+ signal?: AbortSignal;
166
+ stdin?: string;
167
+ }
168
+
169
+ async function runReadseek(args: string[], options: RunReadseekOptions = {}): Promise<unknown> {
170
+ const stdout = await new Promise<string>((resolve, reject) => {
171
+ const stdin = options.stdin;
172
+ const stdio: StdioOptions = [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"];
173
+ const child = spawn(readseekBinaryPath(), args, { stdio, signal: options.signal });
174
+ const childStdout = child.stdout;
175
+ const childStderr = child.stderr;
176
+ const childStdin = child.stdin;
177
+ if (!childStdout || !childStderr) {
178
+ child.kill();
179
+ reject(new Error("readseek stdio streams are unavailable"));
180
+ return;
181
+ }
162
182
  const stdoutChunks: Buffer[] = [];
163
183
  const stderrChunks: Buffer[] = [];
164
184
  let stdoutBytes = 0;
165
185
 
166
- child.stdout.on("data", (chunk: Buffer) => {
186
+ childStdout.on("data", (chunk: Buffer) => {
167
187
  stdoutBytes += chunk.length;
168
188
  if (stdoutBytes > 32 * 1024 * 1024) {
169
189
  child.kill();
@@ -172,16 +192,26 @@ async function runReadseek(args: string[], options: { signal?: AbortSignal } = {
172
192
  }
173
193
  stdoutChunks.push(chunk);
174
194
  });
175
- child.stderr.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
195
+ childStderr.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
176
196
  child.on("error", (error: any) => reject(error));
197
+ if (stdin !== undefined) {
198
+ if (!childStdin) {
199
+ child.kill();
200
+ reject(new Error("readseek stdin stream is unavailable"));
201
+ return;
202
+ }
203
+ childStdin.on("error", (error: any) => {
204
+ if (error?.code !== "EPIPE") reject(error);
205
+ });
206
+ childStdin.end(stdin, "utf-8");
207
+ }
177
208
  child.on("close", (code) => {
178
209
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
179
210
  const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
180
- if (code === 0) resolve({ stdout, stderr });
211
+ if (code === 0) resolve(stdout);
181
212
  else reject(new Error((stderr || `readseek exited with status ${code}`).replace(/^error:\s*/i, "")));
182
213
  });
183
214
  });
184
- void stderr;
185
215
  return JSON.parse(stdout) as unknown;
186
216
  }
187
217
 
@@ -235,7 +265,7 @@ function parseMapOutput(value: unknown): ReadseekMapOutput {
235
265
  return {
236
266
  kind: requireString(item.kind, "symbol.kind"),
237
267
  name: requireString(item.name, "symbol.name"),
238
- address: requireString(item.address, "symbol.address"),
268
+ qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
239
269
  start_line: requireNumber(item.start_line, "symbol.start_line"),
240
270
  end_line: requireNumber(item.end_line, "symbol.end_line"),
241
271
  start_hash: requireString(item.start_hash, "symbol.start_hash"),
@@ -308,8 +338,7 @@ export async function readseekRead(filePath: string, startLine?: number, endLine
308
338
  return parseReadOutput(await runReadseek(args));
309
339
  }
310
340
 
311
- export async function readseekMap(filePath: string, totalBytes: number): Promise<FileMap | null> {
312
- const output = parseMapOutput(await runReadseek(["map", filePath]));
341
+ function fileMapFromReadseekOutput(output: ReadseekMapOutput, filePath: string, totalBytes: number): FileMap | null {
313
342
  if (output.language === "unknown" && output.symbols.length === 0) return null;
314
343
  return {
315
344
  path: filePath,
@@ -322,6 +351,15 @@ export async function readseekMap(filePath: string, totalBytes: number): Promise
322
351
  };
323
352
  }
324
353
 
354
+ export async function readseekMap(
355
+ filePath: string,
356
+ totalBytes: number,
357
+ options: { signal?: AbortSignal } = {},
358
+ ): Promise<FileMap | null> {
359
+ const output = parseMapOutput(await runReadseek(["map", filePath], { signal: options.signal }));
360
+ return fileMapFromReadseekOutput(output, filePath, totalBytes);
361
+ }
362
+
325
363
  export async function readseekSearch(
326
364
  target: string,
327
365
  pattern: string,
@@ -335,15 +373,13 @@ export async function readseekSearch(
335
373
  return parseSearchOutput(await runReadseek(args, { signal: options.signal })).results;
336
374
  }
337
375
 
338
- export async function readseekMapContent(filePath: string, content: string): Promise<FileMap | null> {
339
- const tempDir = await mkdtemp(path.join(tmpdir(), "readseek-map-"));
340
- const tempPath = path.join(tempDir, path.basename(filePath) || `content${path.extname(filePath)}` || "content");
341
- try {
342
- await writeFile(tempPath, content, "utf8");
343
- const map = await readseekMap(tempPath, Buffer.byteLength(content, "utf8"));
344
- if (!map) return null;
345
- return { ...map, path: filePath };
346
- } finally {
347
- await rm(tempDir, { recursive: true, force: true });
348
- }
376
+ export async function readseekMapContent(
377
+ filePath: string,
378
+ content: string,
379
+ options: { signal?: AbortSignal } = {},
380
+ ): Promise<FileMap | null> {
381
+ const output = parseMapOutput(
382
+ await runReadseek(["map", "--stdin", "--path", filePath], { signal: options.signal, stdin: content }),
383
+ );
384
+ return fileMapFromReadseekOutput(output, filePath, Buffer.byteLength(content, "utf8"));
349
385
  }
@@ -5,37 +5,39 @@ const COMPACT_DESCRIPTIONS: Record<string, string> = {
5
5
  "read.md": "Read text files/images by path; text has LINE:HASH anchors, images return attachments.",
6
6
  "edit.md": "Edit existing text files using fresh LINE:HASH anchors from read, grep, search, or write.",
7
7
  "grep.md": "Search file contents; non-summary results include LINE:HASH anchors for edits.",
8
- "find.md": "Find files by glob, respecting .gitignore.",
9
- "ls.md": "List one directory.",
10
- "write.md": "Create or overwrite a file and return anchors.",
8
+ "find.md": "Find files recursively by basename glob, respecting .gitignore.",
9
+ "ls.md": "List one directory with directories first and dotfiles included.",
10
+ "write.md": "Create or overwrite a complete file and return anchors.",
11
11
  "sg.md": "Search code by AST pattern and return anchored matches.",
12
12
  };
13
13
 
14
14
  const COMPACT_GUIDELINES: Record<string, string[]> = {
15
15
  "read.md": [
16
16
  "Use read for file contents, images/screenshots, ranges, symbols, and edit anchors.",
17
+ "Use map or symbol mode before pulling large code files into context.",
17
18
  "Use read for images; it returns attachments, so avoid OCR tools unless explicitly needed.",
18
19
  ],
19
20
  "edit.md": [
20
21
  "Use edit with fresh LINE:HASH anchors for existing files.",
21
- "Use edit replace only when anchored edits are impractical.",
22
+ "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical.",
22
23
  ],
23
24
  "grep.md": [
24
25
  "Use grep for text search and edit-ready matching anchors.",
25
- "Use grep summary mode when only file counts are needed.",
26
+ "Use grep summary mode for broad count/file discovery before narrowing.",
26
27
  ],
27
28
  "find.md": [
28
- "Use find for recursive file discovery by glob.",
29
+ "Use find for recursive file discovery by basename glob.",
29
30
  ],
30
31
  "ls.md": [
31
- "Use ls to list one directory, optionally with a glob filter.",
32
+ "Use ls to inspect one directory; use find for recursion.",
32
33
  ],
33
34
  "write.md": [
34
35
  "Use write to create files or intentionally overwrite whole files.",
35
- "Use edit rather than write for small changes to existing files.",
36
+ "Use edit rather than write for small changes or appends to existing files.",
36
37
  ],
37
38
  "sg.md": [
38
39
  "Use search for AST-shaped code patterns.",
40
+ "Use grep instead of search for plain text.",
39
41
  ],
40
42
  };
41
43