harnesstrim 0.2.1 → 0.3.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/dist/chunks/chunk-KU5ELU4C.mjs +35 -0
- package/dist/chunks/chunk-XLAPDMOY.mjs +1004 -0
- package/dist/chunks/server-GCF7KBO7.mjs +21155 -0
- package/dist/chunks/tokens-D6XSJUG5.mjs +280 -0
- package/dist/cli.mjs +113 -23265
- package/package.json +1 -1
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
// ../core/src/reducers/tap-output-slim.ts
|
|
2
|
+
var MARKER = "[harnesstrim:tap-output-slim]";
|
|
3
|
+
var SIGNAL = /\b(fail(ed|ure)?|error|warn(ing)?|exception|traceback|assertionerror)\b/i;
|
|
4
|
+
var tapOutputSlim = {
|
|
5
|
+
name: "tap-output-slim",
|
|
6
|
+
reduce(input) {
|
|
7
|
+
if (input.includes(MARKER) || !/^TAP version 13\r?$/m.test(input)) {
|
|
8
|
+
return { output: input, changed: false };
|
|
9
|
+
}
|
|
10
|
+
const eol = input.includes("\r\n") ? "\r\n" : "\n";
|
|
11
|
+
const lines = input.split(eol);
|
|
12
|
+
const out = [];
|
|
13
|
+
let omitted = 0;
|
|
14
|
+
let pending = [];
|
|
15
|
+
let count = 0;
|
|
16
|
+
const flush = () => {
|
|
17
|
+
if (count >= 2) {
|
|
18
|
+
out.push(`${MARKER} omitted ${count} passing subtests (duration/type only)`);
|
|
19
|
+
omitted += count;
|
|
20
|
+
} else out.push(...pending);
|
|
21
|
+
pending = [];
|
|
22
|
+
count = 0;
|
|
23
|
+
};
|
|
24
|
+
let diagnosticTail = false;
|
|
25
|
+
for (let i = 0; i < lines.length; ) {
|
|
26
|
+
if (/^not ok\b|^Bail out!/i.test(lines[i])) diagnosticTail = true;
|
|
27
|
+
const header = /^# Subtest: (.+)$/.exec(lines[i]);
|
|
28
|
+
const success = /^ok \d+ - (.+)$/.exec(lines[i + 1] ?? "");
|
|
29
|
+
let end = i + 2;
|
|
30
|
+
let known = !diagnosticTail && !!header && !!success && header[1] === success[1] && !SIGNAL.test(header[1]) && !/#\s*(?:SKIP|TODO)/i.test(success[1]);
|
|
31
|
+
if (known && lines[end] === " ---") {
|
|
32
|
+
end++;
|
|
33
|
+
const start = end;
|
|
34
|
+
while (/^ (?:duration_ms: [0-9]+(?:\.[0-9]+)?|type: ['"]?test['"]?)$/.test(lines[end] ?? "")) end++;
|
|
35
|
+
known = end > start && lines[end] === " ...";
|
|
36
|
+
if (known) end++;
|
|
37
|
+
}
|
|
38
|
+
known = known && (end === lines.length || /^(?:# Subtest: |1\.\.|# (?:tests|suites|pass|fail|cancelled|skipped|todo|duration_ms)\b)/.test(lines[end] ?? ""));
|
|
39
|
+
if (known) {
|
|
40
|
+
pending.push(...lines.slice(i, end));
|
|
41
|
+
count++;
|
|
42
|
+
i = end;
|
|
43
|
+
} else {
|
|
44
|
+
flush();
|
|
45
|
+
out.push(lines[i]);
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
flush();
|
|
50
|
+
const output = out.join(eol);
|
|
51
|
+
return omitted > 0 && output.length < input.length ? { output, changed: true, note: `omitted ${omitted} passing subtests` } : { output: input, changed: false };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ../core/src/reducers/test-output-slim.ts
|
|
56
|
+
var MARKER2 = "[harnesstrim:test-output-slim]";
|
|
57
|
+
var SIGNAL2 = /\b(fail(ed|ure)?|error|warn(ing)?|exception|traceback|assert(ion)?(error)?|expected|received)\b/i;
|
|
58
|
+
var PASS = /^(?:PASS\s+|\s*[\u2713\u2714]\s+|\S+::\S+\s+PASSED(?:\s|$))/;
|
|
59
|
+
var ANSI = /\x1b\[[0-?]*[ -/]*[@-~]/g;
|
|
60
|
+
var testOutputSlim = {
|
|
61
|
+
name: "test-output-slim",
|
|
62
|
+
reduce(input) {
|
|
63
|
+
if (input.includes(MARKER2)) return { output: input, changed: false };
|
|
64
|
+
const eol = input.includes("\r\n") ? "\r\n" : "\n";
|
|
65
|
+
const lines = input.split(eol);
|
|
66
|
+
const out = [];
|
|
67
|
+
let run = [];
|
|
68
|
+
let dropped = 0;
|
|
69
|
+
let diagnosticTail = false;
|
|
70
|
+
const flush = () => {
|
|
71
|
+
if (run.length >= 2) {
|
|
72
|
+
out.push(`${MARKER2} omitted ${run.length} passing/noise line(s)`);
|
|
73
|
+
dropped += run.length;
|
|
74
|
+
} else out.push(...run);
|
|
75
|
+
run = [];
|
|
76
|
+
};
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const clean = line.replace(ANSI, "");
|
|
79
|
+
if (SIGNAL2.test(clean) || /^\s*[A-Za-z]*Error:|^\s*[\u2715\u2717\u00d7\u25cf]\s/.test(clean)) diagnosticTail = true;
|
|
80
|
+
if (!diagnosticTail && PASS.test(clean)) run.push(line);
|
|
81
|
+
else {
|
|
82
|
+
flush();
|
|
83
|
+
out.push(line);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
flush();
|
|
87
|
+
const output = out.join(eol);
|
|
88
|
+
return dropped > 0 && output.length < input.length ? { output, changed: true, note: `dropped ${dropped} confirmed passing-test lines` } : { output: input, changed: false };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ../core/src/reducers/git-diff-slim.ts
|
|
93
|
+
var MARKER_PREFIX = "[harnesstrim:git-diff-slim]";
|
|
94
|
+
var GENERATED_PATH_RE = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum)$|\.min\.(js|css)$|\.map$|(^|\/)(dist|build)\//i;
|
|
95
|
+
var DIFF_HEADER_RE = /^diff --git a\/(.+) b\/(.+)$/;
|
|
96
|
+
var HUNK_HEADER_RE = /^@@ .*@@/;
|
|
97
|
+
function isBlockHeaderLine(line) {
|
|
98
|
+
return DIFF_HEADER_RE.test(line) || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("new file mode") || line.startsWith("deleted file mode") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to");
|
|
99
|
+
}
|
|
100
|
+
var gitDiffSlim = {
|
|
101
|
+
name: "git-diff-slim",
|
|
102
|
+
reduce(input) {
|
|
103
|
+
const lines = input.split(/\r?\n/);
|
|
104
|
+
const out = [];
|
|
105
|
+
let droppedTotal = 0;
|
|
106
|
+
let i = 0;
|
|
107
|
+
while (i < lines.length) {
|
|
108
|
+
const headerMatch = DIFF_HEADER_RE.exec(lines[i]);
|
|
109
|
+
if (!headerMatch) {
|
|
110
|
+
out.push(lines[i]);
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const filePath = headerMatch[2];
|
|
115
|
+
const blockStart = i;
|
|
116
|
+
let j = i + 1;
|
|
117
|
+
while (j < lines.length && !DIFF_HEADER_RE.test(lines[j])) j++;
|
|
118
|
+
const blockEnd = j;
|
|
119
|
+
if (!GENERATED_PATH_RE.test(filePath)) {
|
|
120
|
+
for (let k = blockStart; k < blockEnd; k++) out.push(lines[k]);
|
|
121
|
+
i = blockEnd;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
out.push(lines[blockStart]);
|
|
125
|
+
let added = 0;
|
|
126
|
+
let removed = 0;
|
|
127
|
+
let hunkLineCount = 0;
|
|
128
|
+
let inHunk = false;
|
|
129
|
+
for (let k = blockStart + 1; k < blockEnd; k++) {
|
|
130
|
+
const line = lines[k];
|
|
131
|
+
if (isBlockHeaderLine(line)) {
|
|
132
|
+
out.push(line);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (HUNK_HEADER_RE.test(line)) {
|
|
136
|
+
inHunk = true;
|
|
137
|
+
hunkLineCount++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (inHunk) {
|
|
141
|
+
hunkLineCount++;
|
|
142
|
+
if (line.startsWith("+") && !line.startsWith("+++")) added++;
|
|
143
|
+
else if (line.startsWith("-") && !line.startsWith("---")) removed++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
out.push(line);
|
|
147
|
+
}
|
|
148
|
+
if (hunkLineCount > 0) {
|
|
149
|
+
out.push(`${MARKER_PREFIX} ${filePath}: +${added} -${removed} (generated/lockfile, hunk omitted)`);
|
|
150
|
+
droppedTotal += hunkLineCount;
|
|
151
|
+
}
|
|
152
|
+
i = blockEnd;
|
|
153
|
+
}
|
|
154
|
+
const output = out.join("\n");
|
|
155
|
+
return {
|
|
156
|
+
output,
|
|
157
|
+
changed: droppedTotal > 0,
|
|
158
|
+
note: droppedTotal > 0 ? `collapsed ${droppedTotal} generated-file diff line(s)` : void 0
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ../core/src/reducers/generic-text-slim.ts
|
|
164
|
+
var MARKER_PREFIX2 = "[harnesstrim:generic-text-slim]";
|
|
165
|
+
var HEADER_RE = /^#{1,6}\s/m;
|
|
166
|
+
var EMOJI_BULLET_RE = /^[\s]*[✅❌⚠️🔍📡🔧🚀🎯💡📝🔄⚡🎨🔒📊📍🏗️🧹⬆️⬇️🔀⏪📋📂🔐📖👀🛠️🔥♻️🧪🏁]/;
|
|
167
|
+
var BULLET_RE = /^\s*[-*+]\s/;
|
|
168
|
+
var NUMBERED_RE = /^\s*\d+[\.\)]\s/;
|
|
169
|
+
var BOLD_RE = /\*\*.+\*\*/;
|
|
170
|
+
var TABLE_RE = /^\s*\|.+\|\s*$/;
|
|
171
|
+
var HR_RE = /^---+$/;
|
|
172
|
+
var CODE_FENCE_RE = /^```/;
|
|
173
|
+
var KEY_VALUE_RE = /^\s*\*{0,2}\w[\w\s]+\*{0,2}:/;
|
|
174
|
+
var CONTEXT_AFTER = 2;
|
|
175
|
+
var MIN_COLLAPSE_RUN = 3;
|
|
176
|
+
function isSignalLine(line) {
|
|
177
|
+
return line.startsWith(MARKER_PREFIX2) || HEADER_RE.test(line) || EMOJI_BULLET_RE.test(line) || BULLET_RE.test(line) || NUMBERED_RE.test(line) || BOLD_RE.test(line) || KEY_VALUE_RE.test(line) || TABLE_RE.test(line) || HR_RE.test(line) || CODE_FENCE_RE.test(line);
|
|
178
|
+
}
|
|
179
|
+
var genericTextSlim = {
|
|
180
|
+
name: "generic-text-slim",
|
|
181
|
+
reduce(input) {
|
|
182
|
+
const lines = input.split(/\r?\n/);
|
|
183
|
+
const n = lines.length;
|
|
184
|
+
const keep = new Array(n).fill(false);
|
|
185
|
+
for (let i2 = 0; i2 < Math.min(CONTEXT_AFTER, n); i2++) {
|
|
186
|
+
keep[i2] = true;
|
|
187
|
+
}
|
|
188
|
+
for (let i2 = Math.max(0, n - CONTEXT_AFTER); i2 < n; i2++) {
|
|
189
|
+
keep[i2] = true;
|
|
190
|
+
}
|
|
191
|
+
let insideCodeFence = false;
|
|
192
|
+
for (let i2 = 0; i2 < n; i2++) {
|
|
193
|
+
if (CODE_FENCE_RE.test(lines[i2])) {
|
|
194
|
+
keep[i2] = true;
|
|
195
|
+
insideCodeFence = !insideCodeFence;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (insideCodeFence) {
|
|
199
|
+
keep[i2] = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (isSignalLine(lines[i2])) {
|
|
203
|
+
keep[i2] = true;
|
|
204
|
+
for (let j = i2 + 1; j <= Math.min(i2 + CONTEXT_AFTER, n - 1); j++) {
|
|
205
|
+
keep[j] = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const out = [];
|
|
210
|
+
let i = 0;
|
|
211
|
+
let droppedTotal = 0;
|
|
212
|
+
while (i < n) {
|
|
213
|
+
if (keep[i]) {
|
|
214
|
+
out.push(lines[i]);
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
let runEnd = i;
|
|
219
|
+
while (runEnd < n && !keep[runEnd]) runEnd++;
|
|
220
|
+
const runLength = runEnd - i;
|
|
221
|
+
if (runLength >= MIN_COLLAPSE_RUN) {
|
|
222
|
+
out.push(`${MARKER_PREFIX2} omitted ${runLength} line(s) of prose/narrative`);
|
|
223
|
+
droppedTotal += runLength;
|
|
224
|
+
} else {
|
|
225
|
+
for (let j = i; j < runEnd; j++) out.push(lines[j]);
|
|
226
|
+
}
|
|
227
|
+
i = runEnd;
|
|
228
|
+
}
|
|
229
|
+
const output = out.join("\n");
|
|
230
|
+
return {
|
|
231
|
+
output,
|
|
232
|
+
changed: droppedTotal > 0,
|
|
233
|
+
note: droppedTotal > 0 ? `dropped ${droppedTotal} prose/narrative line(s)` : void 0
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// ../core/src/reducers/json-output-slim.ts
|
|
239
|
+
var MARKER_PREFIX3 = "[harnesstrim:json-output-slim]";
|
|
240
|
+
var LEADING_ITEMS = 3;
|
|
241
|
+
var TRAILING_ITEMS = 3;
|
|
242
|
+
var MIN_ARRAY_LENGTH = 20;
|
|
243
|
+
var MAX_OBJECT_KEYS = 15;
|
|
244
|
+
var jsonOutputSlim = {
|
|
245
|
+
name: "json-output-slim",
|
|
246
|
+
reduce(input) {
|
|
247
|
+
let parsed;
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.parse(input);
|
|
250
|
+
} catch {
|
|
251
|
+
return tryExtractJsonBlocks(input);
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(parsed)) {
|
|
254
|
+
return reduceArray(parsed, input);
|
|
255
|
+
}
|
|
256
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
257
|
+
return reduceObject(parsed, input);
|
|
258
|
+
}
|
|
259
|
+
return { output: input, changed: false };
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
function reduceArray(arr, originalInput) {
|
|
263
|
+
if (arr.length < MIN_ARRAY_LENGTH) {
|
|
264
|
+
return { output: originalInput, changed: false };
|
|
265
|
+
}
|
|
266
|
+
const n = arr.length;
|
|
267
|
+
const keptCount = LEADING_ITEMS + TRAILING_ITEMS;
|
|
268
|
+
if (n <= keptCount) {
|
|
269
|
+
return { output: originalInput, changed: false };
|
|
270
|
+
}
|
|
271
|
+
const leading = arr.slice(0, LEADING_ITEMS);
|
|
272
|
+
const trailing = arr.slice(n - TRAILING_ITEMS);
|
|
273
|
+
const out = [
|
|
274
|
+
`${MARKER_PREFIX3} array with ${n} total items, showing ${LEADING_ITEMS} first + ${TRAILING_ITEMS} last`
|
|
275
|
+
];
|
|
276
|
+
for (const item of leading) {
|
|
277
|
+
out.push(jsonPreview(item));
|
|
278
|
+
}
|
|
279
|
+
out.push(`${MARKER_PREFIX3} ... omitted ${n - keptCount} items ...`);
|
|
280
|
+
for (const item of trailing) {
|
|
281
|
+
out.push(jsonPreview(item));
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
output: out.join("\n"),
|
|
285
|
+
changed: true,
|
|
286
|
+
note: `collapsed JSON array from ${n} to ${keptCount} visible items`
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function reduceObject(obj, originalInput) {
|
|
290
|
+
const keys = Object.keys(obj);
|
|
291
|
+
let hasLongArray = false;
|
|
292
|
+
for (const key of keys) {
|
|
293
|
+
if (Array.isArray(obj[key]) && obj[key].length >= MIN_ARRAY_LENGTH) {
|
|
294
|
+
hasLongArray = true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (keys.length < MAX_OBJECT_KEYS && !hasLongArray) {
|
|
298
|
+
return { output: originalInput, changed: false };
|
|
299
|
+
}
|
|
300
|
+
const keyCount = keys.length;
|
|
301
|
+
const out = [];
|
|
302
|
+
if (keyCount >= MAX_OBJECT_KEYS) {
|
|
303
|
+
out.push(
|
|
304
|
+
`${MARKER_PREFIX3} object with ${keyCount} keys, first ${MAX_OBJECT_KEYS} shown`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
const shownKeys = keys.slice(0, Math.min(keyCount, MAX_OBJECT_KEYS));
|
|
308
|
+
let changed = false;
|
|
309
|
+
for (const key of shownKeys) {
|
|
310
|
+
const val = obj[key];
|
|
311
|
+
if (Array.isArray(val) && val.length >= MIN_ARRAY_LENGTH) {
|
|
312
|
+
const arrResult = reduceArray(val, JSON.stringify(val));
|
|
313
|
+
if (arrResult.changed) {
|
|
314
|
+
changed = true;
|
|
315
|
+
out.push(` "${key}": [reduced: ${val.length} items]`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
out.push(` "${key}": ${jsonPreview(val, true)}`);
|
|
320
|
+
}
|
|
321
|
+
if (keyCount >= MAX_OBJECT_KEYS) {
|
|
322
|
+
const omitted = keyCount - MAX_OBJECT_KEYS;
|
|
323
|
+
out.push(` ${MARKER_PREFIX3} ... omitted ${omitted} keys ...`);
|
|
324
|
+
changed = true;
|
|
325
|
+
}
|
|
326
|
+
if (!changed) {
|
|
327
|
+
return { output: originalInput, changed: false };
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
output: out.join("\n"),
|
|
331
|
+
changed: true,
|
|
332
|
+
note: hasLongArray ? `collapsed nested array(s) inside object` : `collapsed JSON object from ${keyCount} to ${MAX_OBJECT_KEYS} keys`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function jsonPreview(value, inline = false) {
|
|
336
|
+
if (value === null) return "null";
|
|
337
|
+
if (typeof value === "string") {
|
|
338
|
+
const short = value.length > 80 ? value.slice(0, 77) + "..." : value;
|
|
339
|
+
return JSON.stringify(short);
|
|
340
|
+
}
|
|
341
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
342
|
+
return String(value);
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
if (value.length === 0) return "[]";
|
|
346
|
+
if (inline) return `[${value.length} items]`;
|
|
347
|
+
if (value.length <= 5) {
|
|
348
|
+
return value.map((v) => jsonPreview(v, true)).join("\n");
|
|
349
|
+
}
|
|
350
|
+
return `[${value.length} items]`;
|
|
351
|
+
}
|
|
352
|
+
if (typeof value === "object") {
|
|
353
|
+
const keys = Object.keys(value);
|
|
354
|
+
if (keys.length === 0) return "{}";
|
|
355
|
+
if (inline) return `{${keys.length} keys}`;
|
|
356
|
+
if (keys.length <= 5) {
|
|
357
|
+
return JSON.stringify(value).slice(0, 200) + "...";
|
|
358
|
+
}
|
|
359
|
+
return `{${keys.length} keys}`;
|
|
360
|
+
}
|
|
361
|
+
return String(value);
|
|
362
|
+
}
|
|
363
|
+
function tryExtractJsonBlocks(input) {
|
|
364
|
+
let cursor = 0;
|
|
365
|
+
let output = "";
|
|
366
|
+
let changed = false;
|
|
367
|
+
while (cursor < input.length) {
|
|
368
|
+
const match = input.slice(cursor).match(/^[\t ]*[\[{]/m);
|
|
369
|
+
if (!match || match.index === void 0) {
|
|
370
|
+
output += input.slice(cursor);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
const start = cursor + match.index + match[0].search(/[\[{]/);
|
|
374
|
+
const end = findJsonBlockEnd(input, start);
|
|
375
|
+
if (end === -1) {
|
|
376
|
+
output += input.slice(cursor);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
output += input.slice(cursor, start);
|
|
380
|
+
const jsonBlock = input.slice(start, end);
|
|
381
|
+
let parsed;
|
|
382
|
+
try {
|
|
383
|
+
parsed = JSON.parse(jsonBlock);
|
|
384
|
+
} catch {
|
|
385
|
+
output += input.slice(start, start + 1);
|
|
386
|
+
cursor = start + 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const result = Array.isArray(parsed) ? reduceArray(parsed, jsonBlock) : typeof parsed === "object" && parsed !== null ? reduceObject(parsed, jsonBlock) : { output: jsonBlock, changed: false };
|
|
390
|
+
output += result.output;
|
|
391
|
+
changed ||= result.changed;
|
|
392
|
+
cursor = end;
|
|
393
|
+
}
|
|
394
|
+
return changed ? { output, changed: true, note: "reduced embedded JSON block(s)" } : { output: input, changed: false };
|
|
395
|
+
}
|
|
396
|
+
function findJsonBlockEnd(input, start) {
|
|
397
|
+
const stack = [];
|
|
398
|
+
let inString = false;
|
|
399
|
+
let escape = false;
|
|
400
|
+
for (let i = start; i < input.length; i++) {
|
|
401
|
+
const ch = input[i];
|
|
402
|
+
if (escape) {
|
|
403
|
+
escape = false;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (ch === "\\") {
|
|
407
|
+
escape = true;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (ch === '"') {
|
|
411
|
+
inString = !inString;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (inString) continue;
|
|
415
|
+
if (ch === "{" || ch === "[") stack.push(ch);
|
|
416
|
+
if (ch === "}" || ch === "]") {
|
|
417
|
+
stack.pop();
|
|
418
|
+
if (stack.length === 0) return i + 1;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return -1;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ../core/src/reducers/file-listing-slim.ts
|
|
425
|
+
var MARKER_PREFIX4 = "[harnesstrim:file-listing-slim]";
|
|
426
|
+
var HEADER_KEEP = 5;
|
|
427
|
+
var FOOTER_KEEP = 3;
|
|
428
|
+
var MIN_LINES = 20;
|
|
429
|
+
var FILE_LIKE_THRESHOLD = 0.5;
|
|
430
|
+
var LEADING_ENTRIES = 4;
|
|
431
|
+
var TRAILING_ENTRIES = 3;
|
|
432
|
+
var MIN_FILE_RUN = 10;
|
|
433
|
+
function isFileEntry(line) {
|
|
434
|
+
const trimmed = line.trim();
|
|
435
|
+
if (!trimmed) return false;
|
|
436
|
+
if (/^[\-bcdlsp][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-]/.test(trimmed)) return true;
|
|
437
|
+
if (/^\.\/(?:\.|[^.\s])/.test(trimmed)) return true;
|
|
438
|
+
if (/^\s*(?:├──|└──|│\s+)/.test(trimmed)) return true;
|
|
439
|
+
if (/^[\w.\/\-]+\.[a-zA-Z]{1,4}:\d+\|/.test(trimmed)) return true;
|
|
440
|
+
if (/^total\s+\d+$/.test(trimmed)) return true;
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
var fileListingSlim = {
|
|
444
|
+
name: "file-listing-slim",
|
|
445
|
+
reduce(input) {
|
|
446
|
+
const lines = input.split(/\r?\n/);
|
|
447
|
+
const n = lines.length;
|
|
448
|
+
if (n < MIN_LINES) return { output: input, changed: false };
|
|
449
|
+
let nonBlank = 0;
|
|
450
|
+
let fileLike = 0;
|
|
451
|
+
for (const line of lines) {
|
|
452
|
+
if (line.trim()) {
|
|
453
|
+
nonBlank++;
|
|
454
|
+
if (isFileEntry(line)) fileLike++;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (nonBlank === 0 || fileLike / nonBlank < FILE_LIKE_THRESHOLD) {
|
|
458
|
+
return { output: input, changed: false };
|
|
459
|
+
}
|
|
460
|
+
const keep = new Array(n).fill(false);
|
|
461
|
+
for (let i2 = 0; i2 < Math.min(HEADER_KEEP, n); i2++) keep[i2] = true;
|
|
462
|
+
for (let i2 = Math.max(0, n - FOOTER_KEEP); i2 < n; i2++) keep[i2] = true;
|
|
463
|
+
let i = 0;
|
|
464
|
+
while (i < n) {
|
|
465
|
+
if (!isFileEntry(lines[i])) {
|
|
466
|
+
i++;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const runStart = i;
|
|
470
|
+
while (i < n && isFileEntry(lines[i])) i++;
|
|
471
|
+
const runEnd = i;
|
|
472
|
+
const runLength = runEnd - runStart;
|
|
473
|
+
if (runLength >= MIN_FILE_RUN) {
|
|
474
|
+
for (let j = runStart; j < runStart + Math.min(LEADING_ENTRIES, runLength); j++) {
|
|
475
|
+
keep[j] = true;
|
|
476
|
+
}
|
|
477
|
+
for (let j = Math.max(runStart + LEADING_ENTRIES, runEnd - TRAILING_ENTRIES); j < runEnd; j++) {
|
|
478
|
+
keep[j] = true;
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
for (let j = runStart; j < runEnd; j++) keep[j] = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const out = [];
|
|
485
|
+
let pos = 0;
|
|
486
|
+
let droppedTotal = 0;
|
|
487
|
+
while (pos < n) {
|
|
488
|
+
if (keep[pos]) {
|
|
489
|
+
out.push(lines[pos]);
|
|
490
|
+
pos++;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const dropStart = pos;
|
|
494
|
+
while (pos < n && !keep[pos]) pos++;
|
|
495
|
+
const dropLength = pos - dropStart;
|
|
496
|
+
if (dropLength >= 3) {
|
|
497
|
+
out.push(`${MARKER_PREFIX4} omitted ${dropLength} line(s)`);
|
|
498
|
+
droppedTotal += dropLength;
|
|
499
|
+
} else {
|
|
500
|
+
for (let j = dropStart; j < pos; j++) {
|
|
501
|
+
out.push(lines[j]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const changed = droppedTotal > 0;
|
|
506
|
+
if (!changed) return { output: input, changed: false };
|
|
507
|
+
return {
|
|
508
|
+
output: out.join("\n"),
|
|
509
|
+
changed: true,
|
|
510
|
+
note: `collapsed ${droppedTotal} lines of file listing`
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// ../core/src/reducers/cron-output-slim.ts
|
|
516
|
+
var MARKER_PREFIX5 = "[harnesstrim:cron-output-slim]";
|
|
517
|
+
var CRON_HEADER_RE = /^# Cron Job:/m;
|
|
518
|
+
var PROMPT_RE = /^## Prompt\s*$/m;
|
|
519
|
+
var RESPONSE_RE = /^## Response\s*$/m;
|
|
520
|
+
var cronOutputSlim = {
|
|
521
|
+
name: "cron-output-slim",
|
|
522
|
+
reduce(input) {
|
|
523
|
+
if (input.includes(MARKER_PREFIX5) || !CRON_HEADER_RE.test(input)) {
|
|
524
|
+
return { output: input, changed: false };
|
|
525
|
+
}
|
|
526
|
+
const prompt = PROMPT_RE.exec(input);
|
|
527
|
+
const response = RESPONSE_RE.exec(input);
|
|
528
|
+
if (!prompt || !response || response.index <= prompt.index) {
|
|
529
|
+
return { output: input, changed: false };
|
|
530
|
+
}
|
|
531
|
+
const promptEnd = input.indexOf("\n", prompt.index) + 1;
|
|
532
|
+
const omitted = input.slice(promptEnd, response.index);
|
|
533
|
+
const omittedLines = omitted.split(/\r?\n/).filter(Boolean).length;
|
|
534
|
+
if (omittedLines === 0) return { output: input, changed: false };
|
|
535
|
+
const output = `${input.slice(0, promptEnd)}${MARKER_PREFIX5} omitted ${omittedLines} archived prompt/skill line(s)
|
|
536
|
+
|
|
537
|
+
${input.slice(response.index)}`;
|
|
538
|
+
return {
|
|
539
|
+
output,
|
|
540
|
+
changed: true,
|
|
541
|
+
note: `omitted ${omittedLines} archived prompt/skill line(s)`
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// ../core/src/reducers/lint-output-slim.ts
|
|
547
|
+
var MARKER_PREFIX6 = "[harnesstrim:lint-output-slim]";
|
|
548
|
+
var LINT_LINE_RE = /^[\w.\/\\-]+:\d+:\d+\s+(warning|error)\s+([\w@.\/-]+)/;
|
|
549
|
+
var MAX_RULES_IN_MARKER = 8;
|
|
550
|
+
function isLintLine(line) {
|
|
551
|
+
return LINT_LINE_RE.test(line);
|
|
552
|
+
}
|
|
553
|
+
var lintOutputSlim = {
|
|
554
|
+
name: "lint-output-slim",
|
|
555
|
+
reduce(input) {
|
|
556
|
+
const lines = input.split(/\r?\n/);
|
|
557
|
+
const out = [];
|
|
558
|
+
let droppedTotal = 0;
|
|
559
|
+
let i = 0;
|
|
560
|
+
while (i < lines.length) {
|
|
561
|
+
const line = lines[i];
|
|
562
|
+
if (!isLintLine(line)) {
|
|
563
|
+
out.push(line);
|
|
564
|
+
i++;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const counts = [];
|
|
568
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
569
|
+
let runEnd = i;
|
|
570
|
+
while (runEnd < lines.length && isLintLine(lines[runEnd])) {
|
|
571
|
+
const m = LINT_LINE_RE.exec(lines[runEnd]);
|
|
572
|
+
const severity = m[1] === "error" ? "error" : "warning";
|
|
573
|
+
const rule = m[2];
|
|
574
|
+
const key = `${severity}:${rule}`;
|
|
575
|
+
const existing = byKey.get(key);
|
|
576
|
+
if (existing) {
|
|
577
|
+
existing.count++;
|
|
578
|
+
} else {
|
|
579
|
+
const entry = { severity, rule, count: 1 };
|
|
580
|
+
byKey.set(key, entry);
|
|
581
|
+
counts.push(entry);
|
|
582
|
+
}
|
|
583
|
+
runEnd++;
|
|
584
|
+
}
|
|
585
|
+
const runLength = runEnd - i;
|
|
586
|
+
if (runLength >= 2) {
|
|
587
|
+
const parts = counts.slice(0, MAX_RULES_IN_MARKER).map(
|
|
588
|
+
(c) => `${c.rule} \xD7${c.count}`
|
|
589
|
+
);
|
|
590
|
+
const truncated = counts.length > MAX_RULES_IN_MARKER;
|
|
591
|
+
const suffix = truncated ? `, +${counts.length - MAX_RULES_IN_MARKER} more rule(s)` : "";
|
|
592
|
+
const severities = counts.some((c) => c.severity === "error") && counts.some((c) => c.severity === "warning") ? "error(s) and warning(s)" : counts.some((c) => c.severity === "error") ? "error(s)" : "warning(s)";
|
|
593
|
+
out.push(`${MARKER_PREFIX6} omitted ${runLength} lint line(s) (${severities}: ${parts.join(", ")}${suffix})`);
|
|
594
|
+
droppedTotal += runLength;
|
|
595
|
+
} else {
|
|
596
|
+
for (let j = i; j < runEnd; j++) out.push(lines[j]);
|
|
597
|
+
}
|
|
598
|
+
i = runEnd;
|
|
599
|
+
}
|
|
600
|
+
const output = out.join("\n");
|
|
601
|
+
return {
|
|
602
|
+
output,
|
|
603
|
+
changed: droppedTotal > 0,
|
|
604
|
+
note: droppedTotal > 0 ? `dropped ${droppedTotal} lint noise line(s)` : void 0
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// ../core/src/reducers/ci-log-slim.ts
|
|
610
|
+
var MARKER_PREFIX7 = "[harnesstrim:ci-log-slim]";
|
|
611
|
+
var MIN_NOISE_RUN = 3;
|
|
612
|
+
var SIGNAL_RE = /\b(?:error|warning|warn|fail(?:ed|ure)?|fatal|exception|traceback)\b|exit code/i;
|
|
613
|
+
var CI_NOISE_PATTERNS = [
|
|
614
|
+
/##\[debug\]/,
|
|
615
|
+
/^Syncing repository:/,
|
|
616
|
+
/^Getting Git version info$/,
|
|
617
|
+
/^Temporarily overriding HOME=/,
|
|
618
|
+
/^Adding repository directory to the temporary git global config as a safe directory$/,
|
|
619
|
+
/^Disabling automatic garbage collection$/,
|
|
620
|
+
/^Setting up auth$/,
|
|
621
|
+
/^Persisting credentials$/,
|
|
622
|
+
/^Fetching the repository$/,
|
|
623
|
+
/^Determining the checkout info$/,
|
|
624
|
+
/^Checking out the ref$/,
|
|
625
|
+
/^Post job cleanup\.$/,
|
|
626
|
+
/^Cleaning up orphan processes$/,
|
|
627
|
+
/^git version \d/i,
|
|
628
|
+
/^\/usr\/bin\/git config --global --add safe\.directory /,
|
|
629
|
+
/^\/usr\/bin\/git config --local --name-only --get-regexp /
|
|
630
|
+
];
|
|
631
|
+
function stripGhRunPrefix(line) {
|
|
632
|
+
const parts = line.split(" ");
|
|
633
|
+
return parts.length >= 4 ? parts.slice(3).join(" ") : line;
|
|
634
|
+
}
|
|
635
|
+
function isNoiseLine(line) {
|
|
636
|
+
if (line.startsWith(MARKER_PREFIX7)) return false;
|
|
637
|
+
const payload = stripGhRunPrefix(line).trim();
|
|
638
|
+
if (!payload || SIGNAL_RE.test(payload)) return false;
|
|
639
|
+
return CI_NOISE_PATTERNS.some((pattern) => pattern.test(payload));
|
|
640
|
+
}
|
|
641
|
+
var ciLogSlim = {
|
|
642
|
+
name: "ci-log-slim",
|
|
643
|
+
reduce(input) {
|
|
644
|
+
const lines = input.split(/\r?\n/);
|
|
645
|
+
const out = [];
|
|
646
|
+
let dropped = 0;
|
|
647
|
+
let index = 0;
|
|
648
|
+
while (index < lines.length) {
|
|
649
|
+
if (!isNoiseLine(lines[index])) {
|
|
650
|
+
out.push(lines[index]);
|
|
651
|
+
index += 1;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
let end = index;
|
|
655
|
+
while (end < lines.length && isNoiseLine(lines[end])) end += 1;
|
|
656
|
+
const runLength = end - index;
|
|
657
|
+
if (runLength >= MIN_NOISE_RUN) {
|
|
658
|
+
out.push(`${MARKER_PREFIX7} omitted ${runLength} CI setup/debug line(s)`);
|
|
659
|
+
dropped += runLength;
|
|
660
|
+
} else {
|
|
661
|
+
for (let cursor = index; cursor < end; cursor += 1) out.push(lines[cursor]);
|
|
662
|
+
}
|
|
663
|
+
index = end;
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
output: out.join("\n"),
|
|
667
|
+
changed: dropped > 0,
|
|
668
|
+
note: dropped > 0 ? `dropped ${dropped} CI setup/debug line(s)` : void 0
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// ../core/src/reducers/package-manager-output-slim.ts
|
|
674
|
+
var MARKER_PREFIX8 = "[harnesstrim:package-manager-output-slim]";
|
|
675
|
+
var PNPM_PROGRESS_RE = /^Progress:\s+resolved\s+\d+,\s+reused\s+\d+,\s+downloaded\s+\d+,\s+added\s+\d+(?:,\s+done)?\s*$/;
|
|
676
|
+
var DECORATION_RE = /^[+\-]{20,}\s*$/;
|
|
677
|
+
function isProgress(line) {
|
|
678
|
+
return PNPM_PROGRESS_RE.test(line);
|
|
679
|
+
}
|
|
680
|
+
function isDecoration(line) {
|
|
681
|
+
return DECORATION_RE.test(line);
|
|
682
|
+
}
|
|
683
|
+
var packageManagerOutputSlim = {
|
|
684
|
+
name: "package-manager-output-slim",
|
|
685
|
+
reduce(input) {
|
|
686
|
+
if (input.includes(MARKER_PREFIX8)) {
|
|
687
|
+
return { output: input, changed: false };
|
|
688
|
+
}
|
|
689
|
+
const lines = input.split(/\r?\n/);
|
|
690
|
+
const progressIndexes = lines.map((line, index) => isProgress(line) ? index : -1).filter((index) => index >= 0);
|
|
691
|
+
if (progressIndexes.length < 3) {
|
|
692
|
+
return { output: input, changed: false };
|
|
693
|
+
}
|
|
694
|
+
const finalProgressIndex = progressIndexes[progressIndexes.length - 1];
|
|
695
|
+
const out = [];
|
|
696
|
+
let omittedProgress = 0;
|
|
697
|
+
let omittedDecoration = 0;
|
|
698
|
+
let markerWritten = false;
|
|
699
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
700
|
+
const line = lines[index];
|
|
701
|
+
if (isProgress(line) && index !== finalProgressIndex) {
|
|
702
|
+
omittedProgress += 1;
|
|
703
|
+
if (!markerWritten) {
|
|
704
|
+
out.push(MARKER_PREFIX8);
|
|
705
|
+
markerWritten = true;
|
|
706
|
+
}
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (isDecoration(line)) {
|
|
710
|
+
omittedDecoration += 1;
|
|
711
|
+
if (!markerWritten) {
|
|
712
|
+
out.push(MARKER_PREFIX8);
|
|
713
|
+
markerWritten = true;
|
|
714
|
+
}
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
out.push(line);
|
|
718
|
+
}
|
|
719
|
+
if (!markerWritten) return { output: input, changed: false };
|
|
720
|
+
const markerIndex = out.indexOf(MARKER_PREFIX8);
|
|
721
|
+
out[markerIndex] = `${MARKER_PREFIX8} omitted ${omittedProgress} intermediate progress snapshot(s)` + (omittedDecoration > 0 ? ` and ${omittedDecoration} decorative line(s)` : "");
|
|
722
|
+
return {
|
|
723
|
+
output: out.join("\n"),
|
|
724
|
+
changed: true,
|
|
725
|
+
note: `dropped ${omittedProgress} pnpm progress snapshot(s)` + (omittedDecoration > 0 ? ` and ${omittedDecoration} decorative line(s)` : "")
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// ../core/src/dispatch.ts
|
|
731
|
+
var DEFAULT_MIN_LENGTH = 400;
|
|
732
|
+
var GIT_DIFF_RE = /^diff --git /m;
|
|
733
|
+
var TEST_OUTPUT_RE = /\b\d+\s+(passed|failed)\b|^(PASS|FAIL)\s|::\w.*\b(PASSED|FAILED)\b|=+\s*(FAILURES|short test summary)/im;
|
|
734
|
+
var JSON_RE = /^\s*[\[{]/m;
|
|
735
|
+
var FILE_LISTING_RE = /(?:^total\s+\d+|^[\-bcdlsp][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-]|^\.\/(?:\.|[^.\s])|^\s*(?:├──|└──|│\s+)|^[\w.\/\-]+\.[a-zA-Z]{1,4}:\d+\|)/m;
|
|
736
|
+
var CRON_OUTPUT_RE = /^# Cron Job:.*\n[\s\S]*^## Prompt\s*$[\s\S]*^## Response\s*$/m;
|
|
737
|
+
var CI_LOG_RE = /##\[(?:debug|group|endgroup)\]|(?:^|\t)(?:Syncing repository:|Post job cleanup\.)/m;
|
|
738
|
+
var LINT_OUTPUT_RE = /^[\w.\/\\-]+:\d+:\d+\s+(?:warning|error)\s+[\w@.\/-]+\s/m;
|
|
739
|
+
var PACKAGE_MANAGER_OUTPUT_RE = /^Progress:\s+resolved\s+\d+,\s+reused\s+\d+,\s+downloaded\s+\d+,\s+added\s+\d+(?:,\s+done)?\s*$/m;
|
|
740
|
+
var LONG_TEXT_RE = /^#{1,4}\s.*\n(?:(?!^#{1,4}\s|^diff --git |^```).*\n){5,}/m;
|
|
741
|
+
function pickReducer(text) {
|
|
742
|
+
if (GIT_DIFF_RE.test(text)) return gitDiffSlim;
|
|
743
|
+
if (CI_LOG_RE.test(text) && text.length >= 400) return ciLogSlim;
|
|
744
|
+
if (/^TAP version 13\r?$/m.test(text)) return tapOutputSlim;
|
|
745
|
+
if (TEST_OUTPUT_RE.test(text)) return testOutputSlim;
|
|
746
|
+
if (CRON_OUTPUT_RE.test(text) && text.length >= 400) return cronOutputSlim;
|
|
747
|
+
if (LINT_OUTPUT_RE.test(text) && text.length >= 400) return lintOutputSlim;
|
|
748
|
+
if (PACKAGE_MANAGER_OUTPUT_RE.test(text) && text.length >= 400) return packageManagerOutputSlim;
|
|
749
|
+
if (JSON_RE.test(text) && text.length >= 400) return jsonOutputSlim;
|
|
750
|
+
if (FILE_LISTING_RE.test(text) && text.length >= 400) return fileListingSlim;
|
|
751
|
+
if (LONG_TEXT_RE.test(text) && text.length >= 1e3) return genericTextSlim;
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
function runReducerSafely(reducer, text) {
|
|
755
|
+
try {
|
|
756
|
+
const result = reducer.reduce(text);
|
|
757
|
+
if (!result.changed || result.output.length >= text.length) {
|
|
758
|
+
return { output: text, changed: false, reducer: null };
|
|
759
|
+
}
|
|
760
|
+
return { ...result, reducer: reducer.name };
|
|
761
|
+
} catch (error) {
|
|
762
|
+
return {
|
|
763
|
+
output: text,
|
|
764
|
+
changed: false,
|
|
765
|
+
reducer: null,
|
|
766
|
+
reductionError: {
|
|
767
|
+
reducer: reducer.name,
|
|
768
|
+
message: error instanceof Error ? error.message : String(error)
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function reduceAuto(text, minLength = DEFAULT_MIN_LENGTH) {
|
|
774
|
+
if (text.length < minLength) {
|
|
775
|
+
return { output: text, changed: false, reducer: null };
|
|
776
|
+
}
|
|
777
|
+
const reducer = pickReducer(text);
|
|
778
|
+
if (!reducer) {
|
|
779
|
+
return { output: text, changed: false, reducer: null };
|
|
780
|
+
}
|
|
781
|
+
return runReducerSafely(reducer, text);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ../core/src/metrics/trim-event.ts
|
|
785
|
+
import { randomUUID } from "node:crypto";
|
|
786
|
+
var TRIM_EVENT_SCHEMA_VERSION = 1;
|
|
787
|
+
function makeTrimEvent(partial) {
|
|
788
|
+
return {
|
|
789
|
+
schemaVersion: TRIM_EVENT_SCHEMA_VERSION,
|
|
790
|
+
eventId: randomUUID(),
|
|
791
|
+
ts: partial.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
792
|
+
harness: partial.harness,
|
|
793
|
+
tool: partial.tool,
|
|
794
|
+
reducer: partial.reducer,
|
|
795
|
+
beforeChars: partial.beforeChars,
|
|
796
|
+
afterChars: partial.afterChars,
|
|
797
|
+
changed: partial.changed ?? true,
|
|
798
|
+
reductionFailed: partial.reductionFailed ?? false,
|
|
799
|
+
beforeTokens: partial.beforeTokens ?? null,
|
|
800
|
+
afterTokens: partial.afterTokens ?? null
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function pct(before, after) {
|
|
804
|
+
if (before === 0) return 0;
|
|
805
|
+
return Math.round((1 - after / before) * 1e3) / 10;
|
|
806
|
+
}
|
|
807
|
+
function summarize(events) {
|
|
808
|
+
let beforeChars = 0;
|
|
809
|
+
let afterChars = 0;
|
|
810
|
+
let reduced = 0;
|
|
811
|
+
let passThrough = 0;
|
|
812
|
+
let reducerFailures = 0;
|
|
813
|
+
let reductionErrors = 0;
|
|
814
|
+
let grewChars = 0;
|
|
815
|
+
const byReducerMap = /* @__PURE__ */ new Map();
|
|
816
|
+
const byHarnessMap = /* @__PURE__ */ new Map();
|
|
817
|
+
for (const e of events) {
|
|
818
|
+
beforeChars += e.beforeChars;
|
|
819
|
+
afterChars += e.afterChars;
|
|
820
|
+
if (e.reductionFailed) {
|
|
821
|
+
reducerFailures++;
|
|
822
|
+
} else if (e.changed === false) {
|
|
823
|
+
passThrough++;
|
|
824
|
+
} else if (e.afterChars > e.beforeChars) {
|
|
825
|
+
reductionErrors++;
|
|
826
|
+
grewChars += e.afterChars - e.beforeChars;
|
|
827
|
+
} else {
|
|
828
|
+
reduced++;
|
|
829
|
+
}
|
|
830
|
+
const harness = e.harness ?? "unknown";
|
|
831
|
+
const h = byHarnessMap.get(harness) ?? {
|
|
832
|
+
harness,
|
|
833
|
+
count: 0,
|
|
834
|
+
failures: 0,
|
|
835
|
+
beforeChars: 0,
|
|
836
|
+
afterChars: 0,
|
|
837
|
+
savedChars: 0,
|
|
838
|
+
reductionPct: 0
|
|
839
|
+
};
|
|
840
|
+
h.count += 1;
|
|
841
|
+
if (e.reductionFailed) h.failures += 1;
|
|
842
|
+
h.beforeChars += e.beforeChars;
|
|
843
|
+
h.afterChars += e.afterChars;
|
|
844
|
+
h.savedChars += e.beforeChars - e.afterChars;
|
|
845
|
+
byHarnessMap.set(harness, h);
|
|
846
|
+
if (e.reducer === null) continue;
|
|
847
|
+
const b = byReducerMap.get(e.reducer) ?? {
|
|
848
|
+
reducer: e.reducer,
|
|
849
|
+
count: 0,
|
|
850
|
+
failures: 0,
|
|
851
|
+
beforeChars: 0,
|
|
852
|
+
afterChars: 0,
|
|
853
|
+
savedChars: 0
|
|
854
|
+
};
|
|
855
|
+
b.count += 1;
|
|
856
|
+
if (e.reductionFailed) b.failures += 1;
|
|
857
|
+
b.beforeChars += e.beforeChars;
|
|
858
|
+
b.afterChars += e.afterChars;
|
|
859
|
+
b.savedChars += e.beforeChars - e.afterChars;
|
|
860
|
+
byReducerMap.set(e.reducer, b);
|
|
861
|
+
}
|
|
862
|
+
const byReducer = [...byReducerMap.values()].sort((a, b) => b.savedChars - a.savedChars);
|
|
863
|
+
const byHarness = [...byHarnessMap.values()].map((h) => ({ ...h, reductionPct: pct(h.beforeChars, h.afterChars) })).sort((a, b) => b.savedChars - a.savedChars);
|
|
864
|
+
return {
|
|
865
|
+
events: events.length,
|
|
866
|
+
beforeChars,
|
|
867
|
+
afterChars,
|
|
868
|
+
savedChars: beforeChars - afterChars,
|
|
869
|
+
reductionPct: pct(beforeChars, afterChars),
|
|
870
|
+
byReducer,
|
|
871
|
+
byHarness,
|
|
872
|
+
reduced,
|
|
873
|
+
passThrough,
|
|
874
|
+
passThroughRate: events.length === 0 ? 0 : Math.round(passThrough / events.length * 1e3) / 10,
|
|
875
|
+
reducerFailures,
|
|
876
|
+
reductionErrors,
|
|
877
|
+
grewChars
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function parseTrimEvents(jsonl) {
|
|
881
|
+
const out = [];
|
|
882
|
+
for (const line of jsonl.split(/\r?\n/)) {
|
|
883
|
+
const trimmed = line.trim();
|
|
884
|
+
if (!trimmed) continue;
|
|
885
|
+
let parsed;
|
|
886
|
+
try {
|
|
887
|
+
parsed = JSON.parse(trimmed);
|
|
888
|
+
} catch {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (isTrimEvent(parsed)) out.push(normalize(parsed));
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
}
|
|
895
|
+
function isTrimEvent(value) {
|
|
896
|
+
if (typeof value !== "object" || value === null) return false;
|
|
897
|
+
const v = value;
|
|
898
|
+
return typeof v.beforeChars === "number" && typeof v.afterChars === "number" && typeof v.tool === "string" && (typeof v.reducer === "string" || v.reducer === null);
|
|
899
|
+
}
|
|
900
|
+
function normalize(v) {
|
|
901
|
+
return {
|
|
902
|
+
schemaVersion: typeof v.schemaVersion === "number" ? v.schemaVersion : 0,
|
|
903
|
+
eventId: typeof v.eventId === "string" ? v.eventId : "",
|
|
904
|
+
ts: v.ts,
|
|
905
|
+
harness: v.harness,
|
|
906
|
+
tool: v.tool,
|
|
907
|
+
reducer: v.reducer,
|
|
908
|
+
beforeChars: v.beforeChars,
|
|
909
|
+
afterChars: v.afterChars,
|
|
910
|
+
changed: typeof v.changed === "boolean" ? v.changed : true,
|
|
911
|
+
reductionFailed: typeof v.reductionFailed === "boolean" ? v.reductionFailed : false,
|
|
912
|
+
beforeTokens: typeof v.beforeTokens === "number" ? v.beforeTokens : null,
|
|
913
|
+
afterTokens: typeof v.afterTokens === "number" ? v.afterTokens : null
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// ../core/src/presets/index.ts
|
|
918
|
+
var PRESETS = {
|
|
919
|
+
"lean-debug": {
|
|
920
|
+
name: "lean-debug",
|
|
921
|
+
description: "Debugging with noisy logs: aggressively slim tool output, keep effort low.",
|
|
922
|
+
adapter: { mode: "active", minLength: 300, compactionHandoff: true },
|
|
923
|
+
skills: ["debug-log-slim", "delta-response"],
|
|
924
|
+
reasoningEffort: "low",
|
|
925
|
+
notes: "Mechanical debug/grep loops rarely need high reasoning; the win is cutting log noise."
|
|
926
|
+
},
|
|
927
|
+
"lean-review": {
|
|
928
|
+
name: "lean-review",
|
|
929
|
+
description: "Code review focused on problems, not restating the diff.",
|
|
930
|
+
adapter: { mode: "active", minLength: 400, compactionHandoff: true },
|
|
931
|
+
skills: ["review-delta", "delta-response"],
|
|
932
|
+
reasoningEffort: "medium",
|
|
933
|
+
notes: "Review benefits from some reasoning; output discipline avoids diff-narration bloat."
|
|
934
|
+
},
|
|
935
|
+
"lean-scaffold": {
|
|
936
|
+
name: "lean-scaffold",
|
|
937
|
+
description: "Boilerplate and mechanical transforms: minimal reasoning, terse output.",
|
|
938
|
+
adapter: { mode: "active", minLength: 400, compactionHandoff: true },
|
|
939
|
+
skills: ["scaffold-fast", "delegate-bulk", "delta-response"],
|
|
940
|
+
reasoningEffort: "minimal",
|
|
941
|
+
notes: "Settled-shape work; spend budget on code, and delegate bulk volume to isolated contexts."
|
|
942
|
+
},
|
|
943
|
+
"deep-architecture": {
|
|
944
|
+
name: "deep-architecture",
|
|
945
|
+
description: "Architecture / complex debugging: preserve more context, allow high reasoning.",
|
|
946
|
+
adapter: { mode: "active", minLength: 800, compactionHandoff: true },
|
|
947
|
+
skills: ["delta-response", "compact-handoff"],
|
|
948
|
+
reasoningEffort: "high",
|
|
949
|
+
notes: "Higher minLength keeps more tool context intact; handoff matters across compaction."
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
function getPreset(name) {
|
|
953
|
+
return PRESETS[name];
|
|
954
|
+
}
|
|
955
|
+
function listPresets() {
|
|
956
|
+
return Object.values(PRESETS);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ../core/src/hook-output.ts
|
|
960
|
+
function extractBashOutput(rawJson, allowLegacyOutput = false) {
|
|
961
|
+
let parsed;
|
|
962
|
+
try {
|
|
963
|
+
parsed = JSON.parse(rawJson);
|
|
964
|
+
} catch {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
968
|
+
const p = parsed;
|
|
969
|
+
if (p.tool_name !== "Bash") return null;
|
|
970
|
+
if (p.hook_event_name !== void 0 && p.hook_event_name !== "PostToolUse") return null;
|
|
971
|
+
const value = Object.hasOwn(p, "tool_response") ? p.tool_response : allowLegacyOutput ? p.tool_output : void 0;
|
|
972
|
+
if (typeof value === "string") {
|
|
973
|
+
return { toolName: p.tool_name, text: value, before: value, replace: (text) => text };
|
|
974
|
+
}
|
|
975
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
976
|
+
const output = value;
|
|
977
|
+
if (output.isImage === true || Array.isArray(output.content)) return null;
|
|
978
|
+
for (const key of ["stdout", "output", "content"]) {
|
|
979
|
+
if (typeof output[key] !== "string") continue;
|
|
980
|
+
return {
|
|
981
|
+
toolName: p.tool_name,
|
|
982
|
+
text: output[key],
|
|
983
|
+
before: JSON.stringify(output),
|
|
984
|
+
// stderr, exit codes, interruption/truncation flags and unknown metadata survive.
|
|
985
|
+
replace: (text) => ({ ...output, [key]: text })
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
function serializeToolOutput(value) {
|
|
991
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export {
|
|
995
|
+
DEFAULT_MIN_LENGTH,
|
|
996
|
+
reduceAuto,
|
|
997
|
+
makeTrimEvent,
|
|
998
|
+
summarize,
|
|
999
|
+
parseTrimEvents,
|
|
1000
|
+
getPreset,
|
|
1001
|
+
listPresets,
|
|
1002
|
+
extractBashOutput,
|
|
1003
|
+
serializeToolOutput
|
|
1004
|
+
};
|