pi-readseek 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +2 -2
- package/prompts/sg.md +7 -1
- package/src/context-hygiene.ts +9 -0
- package/src/read.ts +10 -2
- package/src/readseek-client.ts +19 -13
- package/src/sg.ts +30 -2
package/README.md
CHANGED
|
@@ -36,6 +36,9 @@ pi install npm:pi-readseek
|
|
|
36
36
|
`pi-readseek` is licensed under `MIT`. See [LICENSE](LICENSE) for more
|
|
37
37
|
information.
|
|
38
38
|
|
|
39
|
+
The upstream `@jarkkojs/readseek` packages are licensed separately as
|
|
40
|
+
`Apache-2.0 AND LGPL-2.1-or-later`.
|
|
41
|
+
|
|
39
42
|
`readseek` is originally derived from the source code of
|
|
40
43
|
[`pi-hashline-readmap`](https://github.com/coctostan/pi-hashline-readmap).
|
|
41
44
|
The relevant copyrights have been retained in [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
42
|
+
"@jarkkojs/readseek": "0.2.2",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"ignore": "^7.0.5",
|
|
45
45
|
"picomatch": "^4.0.4",
|
package/prompts/sg.md
CHANGED
|
@@ -3,14 +3,18 @@ AST-aware structural code search. Use when text search is too broad or brittle a
|
|
|
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`, or `
|
|
6
|
+
- `lang` — language hint such as `typescript`, `tsx`, `javascript`, `jsx`, `rust`, `python`, `dockerfile`, `lua`, `nix`, `perl`, or `zig`; set it when syntax is ambiguous.
|
|
7
7
|
- `path` — file or directory, default cwd.
|
|
8
|
+
- `cached` — in a Git repository, search tracked/indexed files.
|
|
9
|
+
- `others` — in a Git repository, search untracked files.
|
|
10
|
+
- `ignored` — with `others`, include ignored untracked files.
|
|
8
11
|
|
|
9
12
|
## Pattern syntax
|
|
10
13
|
|
|
11
14
|
- `$NAME` matches one AST node.
|
|
12
15
|
- `$_` matches any one node.
|
|
13
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.
|
|
14
18
|
|
|
15
19
|
## Examples
|
|
16
20
|
|
|
@@ -23,3 +27,5 @@ AST-aware structural code search. Use when text search is too broad or brittle a
|
|
|
23
27
|
## Tips
|
|
24
28
|
|
|
25
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.
|
|
30
|
+
|
|
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.
|
package/src/context-hygiene.ts
CHANGED
|
@@ -48,6 +48,9 @@ export interface ContextHygieneSearchRehydrateInput {
|
|
|
48
48
|
pattern: string;
|
|
49
49
|
lang?: string;
|
|
50
50
|
path?: string;
|
|
51
|
+
cached?: true;
|
|
52
|
+
others?: true;
|
|
53
|
+
ignored?: true;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export interface ContextHygieneReadRehydrateDescriptor {
|
|
@@ -200,6 +203,9 @@ export interface BuildSearchRehydrateDescriptorInput {
|
|
|
200
203
|
pattern: string;
|
|
201
204
|
lang?: string;
|
|
202
205
|
path?: string;
|
|
206
|
+
cached?: boolean;
|
|
207
|
+
others?: boolean;
|
|
208
|
+
ignored?: boolean;
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
export function buildSearchRehydrateDescriptor(
|
|
@@ -208,6 +214,9 @@ export function buildSearchRehydrateDescriptor(
|
|
|
208
214
|
const descriptorInput: ContextHygieneSearchRehydrateInput = { pattern: input.pattern };
|
|
209
215
|
if (input.lang !== undefined) descriptorInput.lang = input.lang;
|
|
210
216
|
if (input.path !== undefined) descriptorInput.path = input.path;
|
|
217
|
+
if (input.cached === true) descriptorInput.cached = true;
|
|
218
|
+
if (input.others === true) descriptorInput.others = true;
|
|
219
|
+
if (input.ignored === true) descriptorInput.ignored = true;
|
|
211
220
|
return { tool: "search", input: descriptorInput };
|
|
212
221
|
}
|
|
213
222
|
|
package/src/read.ts
CHANGED
|
@@ -52,6 +52,12 @@ interface ReadToolOptions {
|
|
|
52
52
|
onSuccessfulRead?: (absolutePath: string) => void;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function splitReadseekLines(text: string): string[] {
|
|
56
|
+
if (text.length === 0) return [];
|
|
57
|
+
const withoutTrailingTerminator = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
58
|
+
return withoutTrailingTerminator.split("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
56
62
|
|
|
57
63
|
function startsWithBytes(buffer: Buffer, bytes: number[]): boolean {
|
|
@@ -386,7 +392,7 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
386
392
|
const hasBinaryContent = looksLikeBinary(rawBuffer);
|
|
387
393
|
throwIfAborted(signal);
|
|
388
394
|
const normalized = normalizeToLF(stripBom(rawBuffer.toString("utf-8")).text);
|
|
389
|
-
const allLines = normalized
|
|
395
|
+
const allLines = splitReadseekLines(normalized);
|
|
390
396
|
const total = allLines.length;
|
|
391
397
|
const structuredWarnings: ReadseekWarning[] = [];
|
|
392
398
|
let startLine = p.offset !== undefined ? p.offset : 1;
|
|
@@ -538,7 +544,9 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
538
544
|
throwIfAborted(signal);
|
|
539
545
|
let readseekOutput: Awaited<ReturnType<typeof readseekRead>>;
|
|
540
546
|
try {
|
|
541
|
-
readseekOutput =
|
|
547
|
+
readseekOutput = total === 0
|
|
548
|
+
? await readseekRead(absolutePath)
|
|
549
|
+
: await readseekRead(absolutePath, startLine, endIdx);
|
|
542
550
|
} catch (err: any) {
|
|
543
551
|
const detail = err?.message ? ` — ${err.message}` : "";
|
|
544
552
|
const message = `readseek failed while reading ${rawPath}${detail}`;
|
package/src/readseek-client.ts
CHANGED
|
@@ -72,6 +72,14 @@ interface ReadseekSearchOutput {
|
|
|
72
72
|
results: ReadseekSearchFileOutput[];
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export interface ReadseekSearchOptions {
|
|
76
|
+
language?: string;
|
|
77
|
+
cached?: boolean;
|
|
78
|
+
others?: boolean;
|
|
79
|
+
ignored?: boolean;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
function normalizeLanguage(language: string): string {
|
|
76
84
|
return language === "java" ? "Java" : language;
|
|
77
85
|
}
|
|
@@ -293,15 +301,11 @@ function parseSearchOutput(value: unknown): ReadseekSearchOutput {
|
|
|
293
301
|
};
|
|
294
302
|
}
|
|
295
303
|
|
|
296
|
-
export async function readseekRead(filePath: string, startLine
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
String(startLine),
|
|
302
|
-
"--end",
|
|
303
|
-
String(endLine),
|
|
304
|
-
]));
|
|
304
|
+
export async function readseekRead(filePath: string, startLine?: number, endLine?: number): Promise<ReadseekReadOutput> {
|
|
305
|
+
const args = ["read", filePath];
|
|
306
|
+
if (startLine !== undefined) args.push("--start", String(startLine));
|
|
307
|
+
if (endLine !== undefined) args.push("--end", String(endLine));
|
|
308
|
+
return parseReadOutput(await runReadseek(args));
|
|
305
309
|
}
|
|
306
310
|
|
|
307
311
|
export async function readseekMap(filePath: string, totalBytes: number): Promise<FileMap | null> {
|
|
@@ -321,12 +325,14 @@ export async function readseekMap(filePath: string, totalBytes: number): Promise
|
|
|
321
325
|
export async function readseekSearch(
|
|
322
326
|
target: string,
|
|
323
327
|
pattern: string,
|
|
324
|
-
|
|
325
|
-
signal?: AbortSignal,
|
|
328
|
+
options: ReadseekSearchOptions = {},
|
|
326
329
|
): Promise<ReadseekSearchFileOutput[]> {
|
|
327
330
|
const args = ["search", target, pattern];
|
|
328
|
-
if (language) args.push("--language", language);
|
|
329
|
-
|
|
331
|
+
if (options.language) args.push("--language", options.language);
|
|
332
|
+
if (options.cached) args.push("--cached");
|
|
333
|
+
if (options.others) args.push("--others");
|
|
334
|
+
if (options.ignored) args.push("--ignored");
|
|
335
|
+
return parseSearchOutput(await runReadseek(args, { signal: options.signal })).results;
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
export async function readseekMapContent(filePath: string, content: string): Promise<FileMap | null> {
|
package/src/sg.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { buildSgOutput } from "./sg-output.js";
|
|
|
12
12
|
import { buildSearchRehydrateDescriptor } from "./context-hygiene.js";
|
|
13
13
|
import { clampLineToWidth, clampLinesToWidth, isRendererExpanded, renderToolLabel, summaryLine } from "./tui-render-utils.js";
|
|
14
14
|
|
|
15
|
-
type SgParams = { pattern: string; lang?: string; path?: string };
|
|
15
|
+
type SgParams = { pattern: string; lang?: string; path?: string; cached?: boolean; others?: boolean; ignored?: boolean };
|
|
16
16
|
|
|
17
17
|
export interface SgRange {
|
|
18
18
|
startLine: number;
|
|
@@ -113,15 +113,35 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
113
113
|
pattern: Type.String({ description: "AST pattern" }),
|
|
114
114
|
lang: Type.Optional(Type.String({ description: "Language hint" })),
|
|
115
115
|
path: Type.Optional(Type.String({ description: "Search path" })),
|
|
116
|
+
cached: Type.Optional(Type.Boolean({ description: "In a Git repository, search tracked/indexed files" })),
|
|
117
|
+
others: Type.Optional(Type.Boolean({ description: "In a Git repository, search untracked files" })),
|
|
118
|
+
ignored: Type.Optional(Type.Boolean({ description: "With others=true, include ignored untracked files" })),
|
|
116
119
|
}),
|
|
117
120
|
ptc: toolConfig,
|
|
118
121
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
119
122
|
await ensureHashInit();
|
|
120
123
|
const p = params as SgParams;
|
|
124
|
+
if (p.ignored && !p.others) {
|
|
125
|
+
const message = "Error: search parameter 'ignored' requires 'others'";
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: message }],
|
|
128
|
+
isError: true,
|
|
129
|
+
details: {
|
|
130
|
+
readseekValue: {
|
|
131
|
+
tool: "search",
|
|
132
|
+
ok: false,
|
|
133
|
+
error: buildReadseekError("invalid-parameter", message),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
121
138
|
const rehydrate = buildSearchRehydrateDescriptor({
|
|
122
139
|
pattern: p.pattern,
|
|
123
140
|
lang: p.lang,
|
|
124
141
|
path: p.path,
|
|
142
|
+
cached: p.cached,
|
|
143
|
+
others: p.others,
|
|
144
|
+
ignored: p.ignored,
|
|
125
145
|
});
|
|
126
146
|
|
|
127
147
|
const searchPath = resolveToCwd(p.path ?? ".", ctx.cwd);
|
|
@@ -178,7 +198,13 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
178
198
|
|
|
179
199
|
try {
|
|
180
200
|
const effectiveLang = readseekLanguageForPath(p.lang, searchPath, searchPathIsFile);
|
|
181
|
-
const results = await readseekSearch(searchPath, p.pattern,
|
|
201
|
+
const results = await readseekSearch(searchPath, p.pattern, {
|
|
202
|
+
language: effectiveLang,
|
|
203
|
+
cached: p.cached,
|
|
204
|
+
others: p.others,
|
|
205
|
+
ignored: p.ignored,
|
|
206
|
+
signal,
|
|
207
|
+
});
|
|
182
208
|
if (results.length === 0) {
|
|
183
209
|
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [], rehydrate });
|
|
184
210
|
return {
|
|
@@ -262,6 +288,8 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
262
288
|
let text = `${renderToolLabel(theme, "search")} ${theme.fg("accent", `/${args.pattern}/`)}`;
|
|
263
289
|
text += theme.fg("dim", ` in ${args.path ?? "."}`);
|
|
264
290
|
if (args.lang) text += theme.fg("dim", ` (${args.lang})`);
|
|
291
|
+
const flags = [args.cached && "cached", args.others && "others", args.ignored && "ignored"].filter(Boolean);
|
|
292
|
+
if (flags.length > 0) text += theme.fg("dim", ` [${flags.join(",")}]`);
|
|
265
293
|
return new Text(clampLineToWidth(text, context.width), 0, 0);
|
|
266
294
|
},
|
|
267
295
|
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|