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.
- package/extensions/search.ts +13 -4
- package/package.json +1 -1
package/extensions/search.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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