pi-tool-discipline 0.1.12 → 0.1.13

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.
@@ -12,6 +12,12 @@ const MAX_VISITED = 5000; // total entries touched, bounds slow/odd trees
12
12
  const MAX_FILE_BYTES = 1024 * 1024; // content search skips files larger than 1 MiB
13
13
  const MAX_OUTPUT_BYTES = 50 * 1024;
14
14
  const MAX_RESULTS = 1000; // hard ceiling for matches/results
15
+
16
+ /** Normalize maxResults: non-finite/non-number falls back to the default. */
17
+ function clampLimit(raw: number | undefined): number {
18
+ const n = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : 100;
19
+ return Math.min(Math.max(1, n), MAX_RESULTS);
20
+ }
15
21
  const TRUNCATE_MARKER = "\n[output truncated]";
16
22
  const CAPPED_MARKER = "\n[search capped — traversal stopped early]";
17
23
 
@@ -107,7 +113,10 @@ function truncate(text: string, maxBytes = MAX_OUTPUT_BYTES, extraMarkerBytes =
107
113
  if (buf.length + extraMarkerBytes <= maxBytes) return text;
108
114
  const budget = maxBytes - Buffer.byteLength(TRUNCATE_MARKER) - extraMarkerBytes;
109
115
  if (budget <= 0) return TRUNCATE_MARKER.trim();
110
- const cut = buf.subarray(0, budget).toString("utf8");
116
+ let cut = buf.subarray(0, budget).toString("utf8");
117
+ // A multibyte char split by subarray becomes U+FFFD (3 bytes) which can
118
+ // exceed the budget — trim back to a valid encoded prefix.
119
+ while (Buffer.byteLength(cut, "utf8") > budget) cut = cut.slice(0, -1);
111
120
  const lastNewline = cut.lastIndexOf("\n");
112
121
  if (lastNewline <= 0) return `${cut}${TRUNCATE_MARKER}`; // no complete line: bounded prefix + marker
113
122
  return `${cut.slice(0, lastNewline)}\n${TRUNCATE_MARKER}`;
@@ -144,8 +153,8 @@ export async function grepFiles(opts: {
144
153
  if (!root) return truncate(`Error: search path not found: ${resolve(opts.cwd, opts.path || ".")}`);
145
154
  const pattern = opts.caseSensitive ? opts.pattern : opts.pattern.toLowerCase();
146
155
  // Hard internal ceiling: bounds memory/work even for direct API calls that
147
- // bypass schema validation.
148
- const limit = Math.min(Math.max(1, opts.maxResults ?? 100), MAX_RESULTS);
156
+ // bypass schema validation (NaN/Infinity included).
157
+ const limit = clampLimit(opts.maxResults);
149
158
  const { files, state } = await walkFiles(root, opts.signal);
150
159
  throwIfAborted(opts.signal);
151
160
  if (state.rootError) return truncate(`Error: cannot read search root ${root}: ${state.rootError}`);
@@ -202,7 +211,7 @@ export async function findFiles(opts: {
202
211
  throwIfAborted(opts.signal);
203
212
  if (state.rootError) return truncate(`Error: cannot read search root ${root}: ${state.rootError}`);
204
213
  const needle = opts.pattern?.toLowerCase();
205
- const resultLimit = Math.min(Math.max(1, opts.maxResults ?? 100), MAX_RESULTS);
214
+ const resultLimit = clampLimit(opts.maxResults);
206
215
  // Match against the RELATIVE path so a pattern matching an ancestor
207
216
  // directory does not hit every file, and rendered output stays relative.
208
217
  const rel: string[] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tool-discipline",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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",