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 +2 -2
- package/prompts/edit.md +33 -24
- package/prompts/find.md +4 -5
- package/prompts/grep.md +13 -11
- package/prompts/ls.md +3 -3
- package/prompts/read.md +22 -14
- package/prompts/sg.md +17 -8
- package/prompts/write.md +7 -7
- package/src/readseek/mapper.ts +3 -3
- package/src/readseek-client.ts +73 -37
- package/src/tool-prompt-metadata.ts +10 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
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
|
|
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
|
|
10
|
-
| `replace_lines` | Replace
|
|
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` |
|
|
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
|
|
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
|
-
`
|
|
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)
|
|
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
|
-
##
|
|
36
|
+
## Exact and fuzzy replacement
|
|
37
|
+
|
|
38
|
+
`replace` is exact-only by default. Missing `old_text` fails with `text-not-found`.
|
|
38
39
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
-
|
|
73
|
-
- A `replace`-only success may
|
|
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;
|
|
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
|
|
93
|
+
Successful `edit` results include:
|
|
87
94
|
|
|
88
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
6
|
-
- `context: N`: include N lines before
|
|
7
|
-
- `summary: true`: return per-file match counts only
|
|
8
|
-
- `scope: "symbol"`: group matches by enclosing symbol. By default returns
|
|
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` —
|
|
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
|
|
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` —
|
|
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
|
-
##
|
|
22
|
+
## Use well
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
7
|
+
- `glob` — optional entry-name filter such as `*.ts`, `.env*`, or `test-*`.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
|
|
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
|
|
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
|
-
- `
|
|
6
|
-
- `
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
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
|
|
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" }` |
|
|
20
|
-
| `{ "symbol": "handleRequest", "bundle": "local" }` | symbol plus direct
|
|
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.
|
|
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**:
|
|
29
|
-
- **Fuzzy**: returns the best camelCase/substring match with a warning
|
|
30
|
-
- **Not found**: falls back to normal read with a warning
|
|
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
|
-
|
|
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
|
|
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
|
|
17
|
-
- Reusing
|
|
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
|
-
##
|
|
36
|
+
## Git selection
|
|
28
37
|
|
|
29
|
-
|
|
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
|
-
|
|
40
|
+
Use `grep` for plain text and `search` for structure.
|
package/prompts/write.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
package/src/readseek/mapper.ts
CHANGED
|
@@ -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 =
|
|
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
|
}
|
package/src/readseek-client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
95
|
-
const entries: Array<{
|
|
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
|
|
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 =
|
|
108
|
+
const bucket = symbolsByQualifiedName.get(symbol.qualified_name);
|
|
107
109
|
if (bucket) bucket.push(fileSymbol);
|
|
108
|
-
else
|
|
109
|
-
entries.push({
|
|
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.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|