pi-readseek 0.1.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.1.1",
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.1.6",
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 `python`; set it when syntax is ambiguous.
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.
@@ -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.split("\n");
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 = await readseekRead(absolutePath, startLine, endIdx);
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}`;
@@ -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: number, endLine: number): Promise<ReadseekReadOutput> {
297
- return parseReadOutput(await runReadseek([
298
- "read",
299
- filePath,
300
- "--start",
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
- language?: string,
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
- return parseSearchOutput(await runReadseek(args, { signal })).results;
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, effectiveLang, signal);
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[]) {