pi-tool-discipline 0.1.9 → 0.1.11

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.
@@ -53,6 +53,7 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
53
53
  try {
54
54
  entries = await readdir(dir);
55
55
  } catch (error: any) {
56
+ throwIfAborted(signal); // cancellation wins over the fs error
56
57
  if (depth === 0) state.rootError = error?.message ?? String(error);
57
58
  return;
58
59
  }
@@ -69,13 +70,16 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
69
70
  const lst = await lstat(p);
70
71
  throwIfAborted(signal);
71
72
  if (lst.isSymbolicLink()) continue; // never follow symlinks
72
- if (lst.isDirectory()) await walk(p, out, state, signal, depth + 1);
73
- else if (lst.isFile()) out.push(p); // regular files only
73
+ if (lst.isDirectory()) {
74
+ await walk(p, out, state, signal, depth + 1);
75
+ throwIfAborted(signal);
76
+ } else if (lst.isFile()) out.push(p); // regular files only
74
77
  } catch (error: any) {
75
- if (error?.message === "Operation aborted") throw error;
78
+ throwIfAborted(signal); // cancellation wins over the fs error
76
79
  // unreadable entries are skipped
77
80
  }
78
81
  }
82
+ throwIfAborted(signal); // post-recursion check
79
83
  }
80
84
 
81
85
  /**
@@ -84,7 +88,7 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
84
88
  */
85
89
  function truncate(text: string, maxBytes = MAX_OUTPUT_BYTES, extraMarkerBytes = 0): string {
86
90
  const buf = Buffer.from(text, "utf8");
87
- if (buf.length <= maxBytes) return text;
91
+ if (buf.length + extraMarkerBytes <= maxBytes) return text;
88
92
  const budget = maxBytes - Buffer.byteLength(TRUNCATE_MARKER) - extraMarkerBytes;
89
93
  if (budget <= 0) return TRUNCATE_MARKER.trim();
90
94
  const cut = buf.subarray(0, budget).toString("utf8");
@@ -93,12 +97,14 @@ function truncate(text: string, maxBytes = MAX_OUTPUT_BYTES, extraMarkerBytes =
93
97
  return `${cut.slice(0, lastNewline)}\n${TRUNCATE_MARKER}`;
94
98
  }
95
99
 
96
- async function resolveRoot(cwd: string, sub?: string): Promise<string | null> {
100
+ async function resolveRoot(cwd: string, sub?: string, signal?: AbortSignal): Promise<string | null> {
97
101
  const root = resolve(cwd, sub || ".");
98
102
  try {
99
103
  const st = await stat(root);
104
+ throwIfAborted(signal);
100
105
  return st.isDirectory() ? root : null;
101
- } catch {
106
+ } catch (error: any) {
107
+ throwIfAborted(signal);
102
108
  return null;
103
109
  }
104
110
  }
@@ -118,26 +124,31 @@ export async function grepFiles(opts: {
118
124
  signal?: AbortSignal;
119
125
  }): Promise<string> {
120
126
  throwIfAborted(opts.signal);
121
- const root = await resolveRoot(opts.cwd, opts.path);
127
+ const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
122
128
  if (!root) return `Error: search path not found: ${resolve(opts.cwd, opts.path || ".")}`;
123
129
  const pattern = opts.caseSensitive ? opts.pattern : opts.pattern.toLowerCase();
124
130
  const limit = opts.maxResults ?? 100;
125
131
  const { files, state } = await walkFiles(root, opts.signal);
132
+ throwIfAborted(opts.signal);
126
133
  if (state.rootError) return `Error: cannot read search root ${root}: ${state.rootError}`;
127
134
  const matches: FileMatch[] = [];
128
135
  for (const file of files) {
129
136
  throwIfAborted(opts.signal);
130
137
  if (matches.length >= limit) break;
131
138
  try {
132
- if ((await stat(file)).size > MAX_FILE_BYTES) continue; // cap only before reading content
133
- } catch {
139
+ const size = (await stat(file)).size;
140
+ throwIfAborted(opts.signal);
141
+ if (size > MAX_FILE_BYTES) continue; // cap only before reading content
142
+ } catch (error: any) {
143
+ throwIfAborted(opts.signal);
134
144
  continue;
135
145
  }
136
146
  throwIfAborted(opts.signal);
137
147
  let content: string;
138
148
  try {
139
149
  content = await readFile(file, "utf8");
140
- } catch {
150
+ } catch (error: any) {
151
+ throwIfAborted(opts.signal);
141
152
  continue;
142
153
  }
143
154
  throwIfAborted(opts.signal);
@@ -167,9 +178,10 @@ export async function findFiles(opts: {
167
178
  signal?: AbortSignal;
168
179
  }): Promise<string> {
169
180
  throwIfAborted(opts.signal);
170
- const root = await resolveRoot(opts.cwd, opts.path);
181
+ const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
171
182
  if (!root) return `Error: search path not found: ${resolve(opts.cwd, opts.path || ".")}`;
172
183
  const { files, state } = await walkFiles(root, opts.signal);
184
+ throwIfAborted(opts.signal);
173
185
  if (state.rootError) return `Error: cannot read search root ${root}: ${state.rootError}`;
174
186
  const needle = opts.pattern?.toLowerCase();
175
187
  // Match against the RELATIVE path so a pattern matching an ancestor
@@ -193,7 +205,7 @@ export async function findFiles(opts: {
193
205
 
194
206
  export async function listDir(opts: { path?: string; cwd: string; signal?: AbortSignal }): Promise<string> {
195
207
  throwIfAborted(opts.signal);
196
- const root = await resolveRoot(opts.cwd, opts.path);
208
+ const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
197
209
  if (!root) return `Error: directory not found: ${resolve(opts.cwd, opts.path || ".")}`;
198
210
  try {
199
211
  const entries = await readdir(root);
@@ -201,7 +213,7 @@ export async function listDir(opts: { path?: string; cwd: string; signal?: Abort
201
213
  if (entries.length === 0) return "(empty directory)";
202
214
  return truncate(entries.join("\n"));
203
215
  } catch (error: any) {
204
- if (error?.message === "Operation aborted") throw error;
216
+ throwIfAborted(opts.signal);
205
217
  return `Error listing ${root}: ${error.message}`;
206
218
  }
207
219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tool-discipline",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "pi extension: enforce ffgrep/fffind-first search discipline and neutralize the default bash file-operation guideline",
5
5
  "type": "module",
6
6
  "license": "MIT",