jevprune 0.1.0 → 0.1.1
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/README.md +98 -143
- package/dist/cli.js +1107 -1020
- package/dist/index.d.ts +15 -5
- package/dist/index.js +356 -102
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -246,6 +246,15 @@ declare class LineRangeError extends JevpruneError {
|
|
|
246
246
|
|
|
247
247
|
type SelectionMode = "fast-path" | "passthrough" | "jev" | "fallback";
|
|
248
248
|
type DecisionReason = "fast-path" | "passthrough" | "tail" | "signature" | "context" | "blank" | "oversize" | "jev" | "collapse-min" | "head" | "fallback";
|
|
249
|
+
type FallbackReason = {
|
|
250
|
+
readonly kind: "unavailable";
|
|
251
|
+
readonly detail: string;
|
|
252
|
+
} | {
|
|
253
|
+
readonly kind: "size-limit";
|
|
254
|
+
readonly maxBytes: number;
|
|
255
|
+
} | {
|
|
256
|
+
readonly kind: "not-utf8";
|
|
257
|
+
};
|
|
249
258
|
interface Decision {
|
|
250
259
|
keep: boolean;
|
|
251
260
|
reason: DecisionReason;
|
|
@@ -263,7 +272,7 @@ interface FooterInput {
|
|
|
263
272
|
readonly linesOut: number;
|
|
264
273
|
readonly exitCode?: number | null;
|
|
265
274
|
readonly logPath?: string;
|
|
266
|
-
readonly fallbackReason?:
|
|
275
|
+
readonly fallbackReason?: FallbackReason;
|
|
267
276
|
readonly passthroughNote?: string;
|
|
268
277
|
readonly storeFailureCode?: string;
|
|
269
278
|
readonly home?: string;
|
|
@@ -326,7 +335,7 @@ interface SelectInput {
|
|
|
326
335
|
interface PassthroughInput {
|
|
327
336
|
readonly bytes: number;
|
|
328
337
|
readonly lines: number;
|
|
329
|
-
readonly reason?:
|
|
338
|
+
readonly reason?: FallbackReason;
|
|
330
339
|
}
|
|
331
340
|
interface SelectionResult {
|
|
332
341
|
readonly mode: SelectionMode;
|
|
@@ -339,12 +348,13 @@ interface SelectionResult {
|
|
|
339
348
|
readonly windows: number;
|
|
340
349
|
readonly jevRequests: number;
|
|
341
350
|
readonly jevInputTokens: number;
|
|
342
|
-
readonly fallbackReason?:
|
|
351
|
+
readonly fallbackReason?: FallbackReason;
|
|
343
352
|
readonly decisions: Map<number, Decision>;
|
|
344
353
|
}
|
|
345
354
|
declare function passthroughSelection(input: PassthroughInput): SelectionResult;
|
|
346
355
|
declare function questionFor(n: number): string;
|
|
347
356
|
declare function selectLines(input: SelectInput): Promise<SelectionResult>;
|
|
357
|
+
declare function fallbackReasonText(reason: FallbackReason): string;
|
|
348
358
|
|
|
349
359
|
interface RunMeta {
|
|
350
360
|
readonly id: string;
|
|
@@ -438,7 +448,7 @@ interface PruneResult {
|
|
|
438
448
|
readonly mode: SelectionMode;
|
|
439
449
|
readonly linesIn: number;
|
|
440
450
|
readonly linesOut: number;
|
|
441
|
-
readonly fallbackReason?:
|
|
451
|
+
readonly fallbackReason?: FallbackReason;
|
|
442
452
|
readonly logPath?: string;
|
|
443
453
|
readonly footer: string;
|
|
444
454
|
}
|
|
@@ -459,4 +469,4 @@ interface ResolvedTask {
|
|
|
459
469
|
}
|
|
460
470
|
declare function resolveTask(input: TaskInput): Promise<ResolvedTask>;
|
|
461
471
|
|
|
462
|
-
export { CHARS_PER_TOKEN, type ChoiceAnswer, type ChoiceQuestionSpec, type ChoiceRequest, type ChoiceResult, type ChoiceScorer, type Config, ConfigError, DEFAULT_ALLOWLIST, DEFAULT_CONFIG, DEFAULT_JEV_MAX_RETRIES, DEFAULT_JEV_MODEL, DEFAULT_JEV_TIMEOUT_MS, DEFAULT_WINDOW_CONCURRENCY, DEFAULT_WINDOW_TIMEOUT_MS, DEFAULT_WINDOW_TOKENS, type Decision, type DecisionReason, type DroppedRange, type FakeJevCall, FakeJevClient, type FakeJevOptions, type FooterInput, type GainEntry, type GainTotals, JevAbortError, JevBudgetError, type JevClient, JevConfigError, JevCoreError, JevInputError, JevRequestError, type JevRequestOptions, JevResponseError, type JevState, JevTimeoutError, type JevUsage, JevpruneError, type KeepReason, type Line, LineRangeError, type LineTerminator, MAX_REQUEST_TOKENS, MAX_TASK_LENGTH, type MergeOptions, type MergeResult, NOT_UTF8_NOTE, NOT_UTF8_REASON, type NoulRequest, type NoulResult, type NoulScorer, type OversizeCapture, type PassthroughInput, type PlanWindowsOptions, type PruneInput, type PruneResult, type PruneStreamInput, RUBRIC, type ResolvedConfig, type ResolvedTask, type RetentionConfig, type RunMeta, RunNotFoundError, type RunRecord, RunStore, RunStoreError, type RunWindowsOptions, type RunWriter, SIGNATURE_CASE_INSENSITIVE, SIGNATURE_CASE_SENSITIVE, type SelectInput, type SelectionMode, type SelectionResult, SpawnError, TYPESAFE_API_KEY_ENV, TYPESAFE_BASE_URL_ENV, type TaskInput, type TaskSource, TranscriptError, TypeSafeJevClient, type TypeSafeJevClientConfig, UsageError, type WindowItem, type WindowJudge, type WindowPlan, byteLineStarts, collapseMarker, computeKeeps, countByteLines, createJevClientFromEnv, describeError, displayPath, estimateJsonTokens, estimateTokens, footerAfter, formatCount, formatFooter, isSignatureLine, isValidUtf8, joinLines, loadConfig, mergeDecisions, newRunId, parseThreshold, passthroughSelection, planWindows, pruneOutput, pruneStream, questionFor, resolveHome, resolveTask, runWindows, selectLines, splitLines, toJevError, validateChoiceAnswers, validateNoulAnswers, withFooter };
|
|
472
|
+
export { CHARS_PER_TOKEN, type ChoiceAnswer, type ChoiceQuestionSpec, type ChoiceRequest, type ChoiceResult, type ChoiceScorer, type Config, ConfigError, DEFAULT_ALLOWLIST, DEFAULT_CONFIG, DEFAULT_JEV_MAX_RETRIES, DEFAULT_JEV_MODEL, DEFAULT_JEV_TIMEOUT_MS, DEFAULT_WINDOW_CONCURRENCY, DEFAULT_WINDOW_TIMEOUT_MS, DEFAULT_WINDOW_TOKENS, type Decision, type DecisionReason, type DroppedRange, type FakeJevCall, FakeJevClient, type FakeJevOptions, type FallbackReason, type FooterInput, type GainEntry, type GainTotals, JevAbortError, JevBudgetError, type JevClient, JevConfigError, JevCoreError, JevInputError, JevRequestError, type JevRequestOptions, JevResponseError, type JevState, JevTimeoutError, type JevUsage, JevpruneError, type KeepReason, type Line, LineRangeError, type LineTerminator, MAX_REQUEST_TOKENS, MAX_TASK_LENGTH, type MergeOptions, type MergeResult, NOT_UTF8_NOTE, NOT_UTF8_REASON, type NoulRequest, type NoulResult, type NoulScorer, type OversizeCapture, type PassthroughInput, type PlanWindowsOptions, type PruneInput, type PruneResult, type PruneStreamInput, RUBRIC, type ResolvedConfig, type ResolvedTask, type RetentionConfig, type RunMeta, RunNotFoundError, type RunRecord, RunStore, RunStoreError, type RunWindowsOptions, type RunWriter, SIGNATURE_CASE_INSENSITIVE, SIGNATURE_CASE_SENSITIVE, type SelectInput, type SelectionMode, type SelectionResult, SpawnError, TYPESAFE_API_KEY_ENV, TYPESAFE_BASE_URL_ENV, type TaskInput, type TaskSource, TranscriptError, TypeSafeJevClient, type TypeSafeJevClientConfig, UsageError, type WindowItem, type WindowJudge, type WindowPlan, byteLineStarts, collapseMarker, computeKeeps, countByteLines, createJevClientFromEnv, describeError, displayPath, estimateJsonTokens, estimateTokens, fallbackReasonText, footerAfter, formatCount, formatFooter, isSignatureLine, isValidUtf8, joinLines, loadConfig, mergeDecisions, newRunId, parseThreshold, passthroughSelection, planWindows, pruneOutput, pruneStream, questionFor, resolveHome, resolveTask, runWindows, selectLines, splitLines, toJevError, validateChoiceAnswers, validateNoulAnswers, withFooter };
|
package/dist/index.js
CHANGED
|
@@ -1196,7 +1196,7 @@ var DEFAULT_CONFIG = {
|
|
|
1196
1196
|
concurrency: 4,
|
|
1197
1197
|
maxPruneBytes: 16777216,
|
|
1198
1198
|
retention: { maxRuns: 200, maxBytes: 268435456 },
|
|
1199
|
-
autoWrap:
|
|
1199
|
+
autoWrap: false,
|
|
1200
1200
|
allowlist: DEFAULT_ALLOWLIST
|
|
1201
1201
|
};
|
|
1202
1202
|
function resolveHome(env = process.env) {
|
|
@@ -1320,48 +1320,6 @@ function describeValue(value) {
|
|
|
1320
1320
|
// src/footer.ts
|
|
1321
1321
|
import { homedir as homedir2 } from "os";
|
|
1322
1322
|
import { sep } from "path";
|
|
1323
|
-
var LF2 = 10;
|
|
1324
|
-
function formatFooter(input) {
|
|
1325
|
-
if (input.mode === "fast-path") return "";
|
|
1326
|
-
const parts = [];
|
|
1327
|
-
if (input.mode === "passthrough") {
|
|
1328
|
-
if (input.exitCode !== void 0 && input.exitCode !== null) parts.push(`exit ${String(input.exitCode)}`);
|
|
1329
|
-
const note = input.passthroughNote === void 0 ? "" : ` (${input.passthroughNote})`;
|
|
1330
|
-
parts.push(`${formatCount(input.linesIn)} lines passed through${note}`);
|
|
1331
|
-
} else {
|
|
1332
|
-
if (input.mode === "fallback") parts.push(`fallback (no Jev: ${input.fallbackReason ?? "unknown"})`);
|
|
1333
|
-
parts.push(`${formatCount(input.linesIn)} \u2192 ${formatCount(input.linesOut)} lines`);
|
|
1334
|
-
if (input.exitCode !== void 0 && input.exitCode !== null) parts.push(`exit ${String(input.exitCode)}`);
|
|
1335
|
-
}
|
|
1336
|
-
if (input.storeFailureCode !== void 0) {
|
|
1337
|
-
parts.push(`run store unavailable (${input.storeFailureCode})`);
|
|
1338
|
-
} else if (input.logPath !== void 0) {
|
|
1339
|
-
parts.push(`full output ${displayPath(input.logPath, input.home)}`);
|
|
1340
|
-
}
|
|
1341
|
-
return `jevprune: ${parts.join(", ")}`;
|
|
1342
|
-
}
|
|
1343
|
-
function footerAfter(lastByte, footer) {
|
|
1344
|
-
if (footer.length === 0) return "";
|
|
1345
|
-
const separator = lastByte === void 0 || lastByte === LF2 ? "" : "\n";
|
|
1346
|
-
return `${separator}${footer}
|
|
1347
|
-
`;
|
|
1348
|
-
}
|
|
1349
|
-
function withFooter(kept, footer) {
|
|
1350
|
-
if (footer.length === 0) return kept;
|
|
1351
|
-
const separator = kept.length === 0 || kept.endsWith("\n") ? "" : "\n";
|
|
1352
|
-
return `${kept}${separator}${footer}
|
|
1353
|
-
`;
|
|
1354
|
-
}
|
|
1355
|
-
function formatCount(value) {
|
|
1356
|
-
const digits = Math.trunc(Math.abs(value)).toString();
|
|
1357
|
-
const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1358
|
-
return value < 0 ? `-${grouped}` : grouped;
|
|
1359
|
-
}
|
|
1360
|
-
function displayPath(path, home = homedir2()) {
|
|
1361
|
-
if (path === home) return "~";
|
|
1362
|
-
if (home.length > 0 && path.startsWith(home + sep)) return `~${path.slice(home.length)}`;
|
|
1363
|
-
return path;
|
|
1364
|
-
}
|
|
1365
1323
|
|
|
1366
1324
|
// src/keeps.ts
|
|
1367
1325
|
var SIGNATURE_CASE_SENSITIVE = /(^|\s)(FAIL|FAILED|ERROR|PANIC|FATAL)(\s|:|$)|\b[A-Z][A-Za-z]*(Error|Exception|Panic)\b|^\s*E\s{2,}\S|^npm ERR!|^\s*(Test Files|Tests|Test Suites)\s/;
|
|
@@ -1478,18 +1436,6 @@ function mergeDecisions(lines, decisions, options) {
|
|
|
1478
1436
|
return { kept, dropped };
|
|
1479
1437
|
}
|
|
1480
1438
|
|
|
1481
|
-
// src/io.ts
|
|
1482
|
-
async function readStreamBytes(stream) {
|
|
1483
|
-
const chunks = [];
|
|
1484
|
-
for await (const chunk of stream) {
|
|
1485
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
1486
|
-
}
|
|
1487
|
-
return Buffer.concat(chunks);
|
|
1488
|
-
}
|
|
1489
|
-
async function readStream(stream) {
|
|
1490
|
-
return (await readStreamBytes(stream)).toString("utf8");
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
1439
|
// src/select.ts
|
|
1494
1440
|
var UNAUTHORIZED_REASON = "unauthorized (401)";
|
|
1495
1441
|
var NOT_UTF8_REASON = "not valid UTF-8";
|
|
@@ -1543,7 +1489,14 @@ async function selectLines(input) {
|
|
|
1543
1489
|
candidates.push({ id: `l${String(line.n)}`, n: line.n, text: line.text });
|
|
1544
1490
|
}
|
|
1545
1491
|
if (input.client === null) {
|
|
1546
|
-
return fallbackSelection({
|
|
1492
|
+
return fallbackSelection({
|
|
1493
|
+
lines,
|
|
1494
|
+
keeps,
|
|
1495
|
+
input,
|
|
1496
|
+
reason: { kind: "unavailable", detail: "API key not set" },
|
|
1497
|
+
bytesIn,
|
|
1498
|
+
linesIn: lines.length
|
|
1499
|
+
});
|
|
1547
1500
|
}
|
|
1548
1501
|
let verdicts;
|
|
1549
1502
|
try {
|
|
@@ -1553,7 +1506,7 @@ async function selectLines(input) {
|
|
|
1553
1506
|
lines,
|
|
1554
1507
|
keeps,
|
|
1555
1508
|
input,
|
|
1556
|
-
reason:
|
|
1509
|
+
reason: unavailableReason(error),
|
|
1557
1510
|
bytesIn,
|
|
1558
1511
|
linesIn: lines.length
|
|
1559
1512
|
});
|
|
@@ -1660,7 +1613,7 @@ function oversizeSelection(input, oversize, captured, bytesIn) {
|
|
|
1660
1613
|
lines,
|
|
1661
1614
|
decisions: fallbackDecisions(lines, keeps, input.config.headLines),
|
|
1662
1615
|
input,
|
|
1663
|
-
reason:
|
|
1616
|
+
reason: { kind: "size-limit", maxBytes: input.config.maxPruneBytes },
|
|
1664
1617
|
bytesIn,
|
|
1665
1618
|
linesIn: totalLines,
|
|
1666
1619
|
totalLines
|
|
@@ -1701,7 +1654,20 @@ function fallbackResult(fallback) {
|
|
|
1701
1654
|
decisions: fallback.decisions
|
|
1702
1655
|
};
|
|
1703
1656
|
}
|
|
1704
|
-
function
|
|
1657
|
+
function fallbackReasonText(reason) {
|
|
1658
|
+
switch (reason.kind) {
|
|
1659
|
+
case "size-limit":
|
|
1660
|
+
return `output over ${String(reason.maxBytes)} bytes`;
|
|
1661
|
+
case "not-utf8":
|
|
1662
|
+
return NOT_UTF8_REASON;
|
|
1663
|
+
case "unavailable":
|
|
1664
|
+
return reason.detail;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
function unavailableReason(error) {
|
|
1668
|
+
return { kind: "unavailable", detail: unavailableDetail(error) };
|
|
1669
|
+
}
|
|
1670
|
+
function unavailableDetail(error) {
|
|
1705
1671
|
if (error instanceof JevTimeoutError) return "timeout";
|
|
1706
1672
|
if (error instanceof JevResponseError) return "invalid response";
|
|
1707
1673
|
if (error instanceof JevRequestError) {
|
|
@@ -1741,6 +1707,207 @@ function everyLine(lines, mode, text, bytesIn) {
|
|
|
1741
1707
|
};
|
|
1742
1708
|
}
|
|
1743
1709
|
|
|
1710
|
+
// src/footer.ts
|
|
1711
|
+
var LF2 = 10;
|
|
1712
|
+
function formatFooter(input) {
|
|
1713
|
+
if (input.mode === "fast-path") return "";
|
|
1714
|
+
const parts = [];
|
|
1715
|
+
if (input.mode === "passthrough") {
|
|
1716
|
+
if (input.exitCode !== void 0 && input.exitCode !== null) parts.push(`exit ${String(input.exitCode)}`);
|
|
1717
|
+
const note = input.passthroughNote === void 0 ? "" : ` (${input.passthroughNote})`;
|
|
1718
|
+
parts.push(`${formatCount(input.linesIn)} lines passed through${note}`);
|
|
1719
|
+
} else {
|
|
1720
|
+
if (input.mode === "fallback") parts.push(`fallback (${fallbackNote(input.fallbackReason)})`);
|
|
1721
|
+
parts.push(`${formatCount(input.linesIn)} \u2192 ${formatCount(input.linesOut)} lines`);
|
|
1722
|
+
if (input.exitCode !== void 0 && input.exitCode !== null) parts.push(`exit ${String(input.exitCode)}`);
|
|
1723
|
+
}
|
|
1724
|
+
if (input.storeFailureCode !== void 0) {
|
|
1725
|
+
parts.push(`full output was not saved (${input.storeFailureCode})`);
|
|
1726
|
+
} else if (input.logPath !== void 0) {
|
|
1727
|
+
parts.push(`full output ${displayPath(input.logPath, input.home)}`);
|
|
1728
|
+
}
|
|
1729
|
+
return `jevprune: ${parts.join(", ")}`;
|
|
1730
|
+
}
|
|
1731
|
+
function fallbackNote(reason) {
|
|
1732
|
+
if (reason === void 0) return "Jev unavailable: unknown";
|
|
1733
|
+
switch (reason.kind) {
|
|
1734
|
+
case "size-limit":
|
|
1735
|
+
return `output over ${String(reason.maxBytes)} bytes`;
|
|
1736
|
+
case "not-utf8":
|
|
1737
|
+
return NOT_UTF8_NOTE;
|
|
1738
|
+
case "unavailable":
|
|
1739
|
+
return `Jev unavailable: ${reason.detail}`;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function footerAfter(lastByte, footer) {
|
|
1743
|
+
if (footer.length === 0) return "";
|
|
1744
|
+
const separator = lastByte === void 0 || lastByte === LF2 ? "" : "\n";
|
|
1745
|
+
return `${separator}${footer}
|
|
1746
|
+
`;
|
|
1747
|
+
}
|
|
1748
|
+
function withFooter(kept, footer) {
|
|
1749
|
+
if (footer.length === 0) return kept;
|
|
1750
|
+
const separator = kept.length === 0 || kept.endsWith("\n") ? "" : "\n";
|
|
1751
|
+
return `${kept}${separator}${footer}
|
|
1752
|
+
`;
|
|
1753
|
+
}
|
|
1754
|
+
function formatCount(value) {
|
|
1755
|
+
const digits = Math.trunc(Math.abs(value)).toString();
|
|
1756
|
+
const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1757
|
+
return value < 0 ? `-${grouped}` : grouped;
|
|
1758
|
+
}
|
|
1759
|
+
function displayPath(path, home = homedir2()) {
|
|
1760
|
+
if (path === home) return "~";
|
|
1761
|
+
if (home.length > 0 && path.startsWith(home + sep)) return `~${path.slice(home.length)}`;
|
|
1762
|
+
return path;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// src/capture.ts
|
|
1766
|
+
var TAIL_RING_BYTES = 256 * 1024;
|
|
1767
|
+
var LF3 = 10;
|
|
1768
|
+
var CR2 = 13;
|
|
1769
|
+
var LineCounter = class {
|
|
1770
|
+
#terminators = 0;
|
|
1771
|
+
#pendingCr = false;
|
|
1772
|
+
#lastByte;
|
|
1773
|
+
push(chunk) {
|
|
1774
|
+
for (const byte of chunk) {
|
|
1775
|
+
if (this.#pendingCr) {
|
|
1776
|
+
this.#pendingCr = false;
|
|
1777
|
+
this.#terminators += 1;
|
|
1778
|
+
if (byte === LF3) {
|
|
1779
|
+
this.#lastByte = byte;
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (byte === CR2) {
|
|
1784
|
+
this.#pendingCr = true;
|
|
1785
|
+
this.#lastByte = byte;
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
if (byte === LF3) this.#terminators += 1;
|
|
1789
|
+
this.#lastByte = byte;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
get terminators() {
|
|
1793
|
+
return this.#terminators + (this.#pendingCr ? 1 : 0);
|
|
1794
|
+
}
|
|
1795
|
+
get lines() {
|
|
1796
|
+
if (this.#lastByte === void 0) return 0;
|
|
1797
|
+
const endsWithTerminator = this.#lastByte === LF3 || this.#lastByte === CR2;
|
|
1798
|
+
return this.terminators + (endsWithTerminator ? 0 : 1);
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
var CaptureBuffer = class {
|
|
1802
|
+
#maxBytes;
|
|
1803
|
+
#head = [];
|
|
1804
|
+
#ring = [];
|
|
1805
|
+
#headCounter = new LineCounter();
|
|
1806
|
+
#headBytes = 0;
|
|
1807
|
+
#ringBytes = 0;
|
|
1808
|
+
#oversize = false;
|
|
1809
|
+
constructor(maxBytes) {
|
|
1810
|
+
this.#maxBytes = maxBytes;
|
|
1811
|
+
}
|
|
1812
|
+
get oversize() {
|
|
1813
|
+
return this.#oversize;
|
|
1814
|
+
}
|
|
1815
|
+
get headSegmentLines() {
|
|
1816
|
+
return this.#oversize ? this.#headCounter.terminators : this.#headCounter.lines;
|
|
1817
|
+
}
|
|
1818
|
+
push(chunk) {
|
|
1819
|
+
let rest = chunk;
|
|
1820
|
+
if (!this.#oversize) {
|
|
1821
|
+
const room = this.#maxBytes - this.#headBytes;
|
|
1822
|
+
if (rest.length <= room) {
|
|
1823
|
+
this.#head.push(rest);
|
|
1824
|
+
this.#headCounter.push(rest);
|
|
1825
|
+
this.#headBytes += rest.length;
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (room > 0) {
|
|
1829
|
+
const head = rest.subarray(0, room);
|
|
1830
|
+
this.#head.push(head);
|
|
1831
|
+
this.#headCounter.push(head);
|
|
1832
|
+
this.#headBytes += room;
|
|
1833
|
+
rest = rest.subarray(room);
|
|
1834
|
+
}
|
|
1835
|
+
this.#oversize = true;
|
|
1836
|
+
}
|
|
1837
|
+
this.#ring.push(rest);
|
|
1838
|
+
this.#ringBytes += rest.length;
|
|
1839
|
+
this.#trimRing();
|
|
1840
|
+
}
|
|
1841
|
+
bytes() {
|
|
1842
|
+
if (!this.#oversize) {
|
|
1843
|
+
const single = this.#head.length === 1 ? this.#head[0] : void 0;
|
|
1844
|
+
return single ?? Buffer.concat(this.#head);
|
|
1845
|
+
}
|
|
1846
|
+
const head = trimToLastTerminator(Buffer.concat(this.#head));
|
|
1847
|
+
const tail = trimToFirstLine(Buffer.concat(this.#ring));
|
|
1848
|
+
return Buffer.concat([head, tail]);
|
|
1849
|
+
}
|
|
1850
|
+
#trimRing() {
|
|
1851
|
+
while (this.#ringBytes > TAIL_RING_BYTES) {
|
|
1852
|
+
const first = this.#ring[0];
|
|
1853
|
+
if (first === void 0) return;
|
|
1854
|
+
const excess = this.#ringBytes - TAIL_RING_BYTES;
|
|
1855
|
+
if (first.length <= excess) {
|
|
1856
|
+
this.#ring.shift();
|
|
1857
|
+
this.#ringBytes -= first.length;
|
|
1858
|
+
} else {
|
|
1859
|
+
this.#ring[0] = first.subarray(excess);
|
|
1860
|
+
this.#ringBytes -= excess;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
var OutputCapture = class {
|
|
1866
|
+
#buffer;
|
|
1867
|
+
#counter = new LineCounter();
|
|
1868
|
+
#bytes = 0;
|
|
1869
|
+
constructor(maxBytes) {
|
|
1870
|
+
this.#buffer = new CaptureBuffer(maxBytes);
|
|
1871
|
+
}
|
|
1872
|
+
push(chunk) {
|
|
1873
|
+
this.#bytes += chunk.length;
|
|
1874
|
+
this.#counter.push(chunk);
|
|
1875
|
+
this.#buffer.push(chunk);
|
|
1876
|
+
}
|
|
1877
|
+
result() {
|
|
1878
|
+
return {
|
|
1879
|
+
captured: this.#buffer.bytes(),
|
|
1880
|
+
bytes: this.#bytes,
|
|
1881
|
+
lines: this.#counter.lines,
|
|
1882
|
+
headSegmentLines: this.#buffer.headSegmentLines,
|
|
1883
|
+
oversize: this.#buffer.oversize
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
function captureBytes(bytes, maxBytes) {
|
|
1888
|
+
const capture = new OutputCapture(maxBytes);
|
|
1889
|
+
capture.push(bytes);
|
|
1890
|
+
return capture.result();
|
|
1891
|
+
}
|
|
1892
|
+
function trimToLastTerminator(buffer) {
|
|
1893
|
+
for (let index = buffer.length - 1; index >= 0; index -= 1) {
|
|
1894
|
+
const byte = buffer[index];
|
|
1895
|
+
if (byte === LF3 || byte === CR2) return buffer.subarray(0, index + 1);
|
|
1896
|
+
}
|
|
1897
|
+
return buffer.subarray(0, 0);
|
|
1898
|
+
}
|
|
1899
|
+
function trimToFirstLine(buffer) {
|
|
1900
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
1901
|
+
const byte = buffer[index];
|
|
1902
|
+
if (byte === LF3) return buffer.subarray(index + 1);
|
|
1903
|
+
if (byte === CR2) {
|
|
1904
|
+
const next = buffer[index + 1];
|
|
1905
|
+
return buffer.subarray(next === LF3 ? index + 2 : index + 1);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
return Buffer.alloc(0);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1744
1911
|
// src/store.ts
|
|
1745
1912
|
import { randomBytes } from "crypto";
|
|
1746
1913
|
import { createReadStream } from "fs";
|
|
@@ -2031,54 +2198,49 @@ function storeError(message, error) {
|
|
|
2031
2198
|
|
|
2032
2199
|
// src/prune.ts
|
|
2033
2200
|
async function pruneOutput(input) {
|
|
2034
|
-
const
|
|
2035
|
-
const config = mergeConfig(await loadConfig(env), input.config);
|
|
2036
|
-
const client = input.client !== void 0 ? input.client : clientFromEnv(env);
|
|
2037
|
-
const runId = newRunId();
|
|
2038
|
-
const command = input.command ?? "";
|
|
2039
|
-
const store = input.save === false ? null : new RunStore({ home: config.home, retention: config.retention });
|
|
2201
|
+
const prepared = await prepare(input);
|
|
2040
2202
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2041
|
-
const
|
|
2042
|
-
|
|
2203
|
+
const bytes = Buffer.from(input.text, "utf8");
|
|
2204
|
+
const capture = captureBytes(bytes, prepared.config.maxPruneBytes);
|
|
2205
|
+
return await selectAndRecord({
|
|
2206
|
+
prepared,
|
|
2207
|
+
capture,
|
|
2208
|
+
text: capture.oversize ? capture.captured.toString("utf8") : input.text,
|
|
2043
2209
|
task: input.task,
|
|
2044
|
-
command,
|
|
2045
2210
|
exitCode: input.exitCode ?? null,
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
runId
|
|
2211
|
+
startedAt,
|
|
2212
|
+
logBytes: bytes
|
|
2049
2213
|
});
|
|
2050
|
-
const recorded = await recordRun({
|
|
2051
|
-
store,
|
|
2052
|
-
selection,
|
|
2053
|
-
logText: input.text,
|
|
2054
|
-
meta: {
|
|
2055
|
-
id: runId,
|
|
2056
|
-
command,
|
|
2057
|
-
argv: [],
|
|
2058
|
-
startedAt,
|
|
2059
|
-
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2060
|
-
exitCode: input.exitCode ?? null,
|
|
2061
|
-
signal: null,
|
|
2062
|
-
bytes: selection.bytesIn,
|
|
2063
|
-
lines: selection.linesIn,
|
|
2064
|
-
task: input.task
|
|
2065
|
-
}
|
|
2066
|
-
});
|
|
2067
|
-
return {
|
|
2068
|
-
kept: selection.kept,
|
|
2069
|
-
dropped: selection.dropped,
|
|
2070
|
-
runId,
|
|
2071
|
-
mode: selection.mode,
|
|
2072
|
-
linesIn: selection.linesIn,
|
|
2073
|
-
linesOut: selection.linesOut,
|
|
2074
|
-
...selection.fallbackReason !== void 0 ? { fallbackReason: selection.fallbackReason } : {},
|
|
2075
|
-
...recorded.logPath !== void 0 ? { logPath: recorded.logPath } : {},
|
|
2076
|
-
footer: recorded.footer
|
|
2077
|
-
};
|
|
2078
2214
|
}
|
|
2079
2215
|
async function pruneStream(input) {
|
|
2080
2216
|
const { stream, ...rest } = input;
|
|
2081
|
-
|
|
2217
|
+
const prepared = await prepare(rest);
|
|
2218
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2219
|
+
const sink = await openLogSink(prepared.store, prepared.runId);
|
|
2220
|
+
const capture = new OutputCapture(prepared.config.maxPruneBytes);
|
|
2221
|
+
try {
|
|
2222
|
+
for await (const chunk of stream) {
|
|
2223
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
2224
|
+
capture.push(bytes);
|
|
2225
|
+
await sink.write(bytes);
|
|
2226
|
+
}
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
await sink.close();
|
|
2229
|
+
await discardPartialRun(prepared.store, prepared.runId);
|
|
2230
|
+
throw error;
|
|
2231
|
+
}
|
|
2232
|
+
await sink.close();
|
|
2233
|
+
const output = capture.result();
|
|
2234
|
+
const failureCode = sink.failureCode();
|
|
2235
|
+
return await selectAndRecord({
|
|
2236
|
+
prepared,
|
|
2237
|
+
capture: output,
|
|
2238
|
+
text: output.captured.toString("utf8"),
|
|
2239
|
+
task: input.task,
|
|
2240
|
+
exitCode: input.exitCode ?? null,
|
|
2241
|
+
startedAt,
|
|
2242
|
+
...failureCode !== void 0 ? { storeFailureCode: failureCode } : {}
|
|
2243
|
+
});
|
|
2082
2244
|
}
|
|
2083
2245
|
async function recordRun(input) {
|
|
2084
2246
|
const { store, selection, meta } = input;
|
|
@@ -2095,7 +2257,7 @@ async function recordRun(input) {
|
|
|
2095
2257
|
...meta,
|
|
2096
2258
|
mode: selection.mode,
|
|
2097
2259
|
linesOut: selection.linesOut,
|
|
2098
|
-
...selection.fallbackReason !== void 0 ? { fallbackReason: selection.fallbackReason } : {}
|
|
2260
|
+
...selection.fallbackReason !== void 0 ? { fallbackReason: fallbackReasonText(selection.fallbackReason) } : {}
|
|
2099
2261
|
});
|
|
2100
2262
|
}
|
|
2101
2263
|
await store.appendGain({
|
|
@@ -2106,7 +2268,7 @@ async function recordRun(input) {
|
|
|
2106
2268
|
linesOut: selection.linesOut,
|
|
2107
2269
|
bytesIn: selection.bytesIn,
|
|
2108
2270
|
bytesOut: selection.bytesOut,
|
|
2109
|
-
...selection.fallbackReason !== void 0 ? { reason: selection.fallbackReason } : {}
|
|
2271
|
+
...selection.fallbackReason !== void 0 ? { reason: fallbackReasonText(selection.fallbackReason) } : {}
|
|
2110
2272
|
});
|
|
2111
2273
|
await store.enforceRetention();
|
|
2112
2274
|
} catch (error) {
|
|
@@ -2143,6 +2305,97 @@ function mergeConfig(base, overrides) {
|
|
|
2143
2305
|
}
|
|
2144
2306
|
return { ...base, ...defined };
|
|
2145
2307
|
}
|
|
2308
|
+
async function prepare(input) {
|
|
2309
|
+
const env = input.env ?? process.env;
|
|
2310
|
+
const config = mergeConfig(await loadConfig(env), input.config);
|
|
2311
|
+
return {
|
|
2312
|
+
config,
|
|
2313
|
+
client: input.client !== void 0 ? input.client : clientFromEnv(env),
|
|
2314
|
+
store: input.save === false ? null : new RunStore({ home: config.home, retention: config.retention }),
|
|
2315
|
+
runId: newRunId(),
|
|
2316
|
+
command: input.command ?? ""
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
async function selectAndRecord(input) {
|
|
2320
|
+
const { prepared, capture } = input;
|
|
2321
|
+
const selection = await selectLines({
|
|
2322
|
+
text: input.text,
|
|
2323
|
+
task: input.task,
|
|
2324
|
+
command: prepared.command,
|
|
2325
|
+
exitCode: input.exitCode,
|
|
2326
|
+
...capture.oversize ? { oversize: { lines: capture.lines, headSegmentLines: capture.headSegmentLines } } : {},
|
|
2327
|
+
client: prepared.client,
|
|
2328
|
+
config: prepared.config,
|
|
2329
|
+
runId: prepared.runId
|
|
2330
|
+
});
|
|
2331
|
+
const recorded = await recordRun({
|
|
2332
|
+
store: prepared.store,
|
|
2333
|
+
selection,
|
|
2334
|
+
...input.logBytes !== void 0 ? { logBytes: input.logBytes } : {},
|
|
2335
|
+
...input.storeFailureCode !== void 0 ? { storeFailureCode: input.storeFailureCode } : {},
|
|
2336
|
+
meta: {
|
|
2337
|
+
id: prepared.runId,
|
|
2338
|
+
command: prepared.command,
|
|
2339
|
+
argv: [],
|
|
2340
|
+
startedAt: input.startedAt,
|
|
2341
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2342
|
+
exitCode: input.exitCode,
|
|
2343
|
+
signal: null,
|
|
2344
|
+
bytes: capture.bytes,
|
|
2345
|
+
lines: selection.linesIn,
|
|
2346
|
+
task: input.task
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
return {
|
|
2350
|
+
kept: selection.kept,
|
|
2351
|
+
dropped: selection.dropped,
|
|
2352
|
+
runId: prepared.runId,
|
|
2353
|
+
mode: selection.mode,
|
|
2354
|
+
linesIn: selection.linesIn,
|
|
2355
|
+
linesOut: selection.linesOut,
|
|
2356
|
+
...selection.fallbackReason !== void 0 ? { fallbackReason: selection.fallbackReason } : {},
|
|
2357
|
+
...recorded.logPath !== void 0 ? { logPath: recorded.logPath } : {},
|
|
2358
|
+
footer: recorded.footer
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
async function openLogSink(store, id) {
|
|
2362
|
+
if (store === null) return discardingSink();
|
|
2363
|
+
let writer;
|
|
2364
|
+
try {
|
|
2365
|
+
writer = await store.openRun({ id });
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
if (!(error instanceof RunStoreError)) throw error;
|
|
2368
|
+
const code = error.code ?? "failed";
|
|
2369
|
+
return { ...discardingSink(), failureCode: () => code };
|
|
2370
|
+
}
|
|
2371
|
+
return {
|
|
2372
|
+
write: async (chunk) => {
|
|
2373
|
+
if (writer.write(chunk)) return;
|
|
2374
|
+
await new Promise((resolve2) => {
|
|
2375
|
+
writer.onDrain(resolve2);
|
|
2376
|
+
});
|
|
2377
|
+
},
|
|
2378
|
+
close: async () => {
|
|
2379
|
+
await writer.close();
|
|
2380
|
+
},
|
|
2381
|
+
failureCode: () => writer.failure === void 0 ? void 0 : writer.failure.code ?? "failed"
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
async function discardPartialRun(store, id) {
|
|
2385
|
+
if (store === null) return;
|
|
2386
|
+
try {
|
|
2387
|
+
await store.discardRun(id);
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
if (!(error instanceof RunStoreError)) throw error;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
function discardingSink() {
|
|
2393
|
+
return {
|
|
2394
|
+
write: () => Promise.resolve(),
|
|
2395
|
+
close: () => Promise.resolve(),
|
|
2396
|
+
failureCode: () => void 0
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2146
2399
|
async function writeLog(store, id, bytes) {
|
|
2147
2400
|
const writer = await store.openRun({ id });
|
|
2148
2401
|
writer.write(bytes);
|
|
@@ -2261,6 +2514,7 @@ export {
|
|
|
2261
2514
|
displayPath,
|
|
2262
2515
|
estimateJsonTokens,
|
|
2263
2516
|
estimateTokens,
|
|
2517
|
+
fallbackReasonText,
|
|
2264
2518
|
footerAfter,
|
|
2265
2519
|
formatCount,
|
|
2266
2520
|
formatFooter,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jevprune",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Filter command output for coding agents using a task description.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ibrahemid",
|
|
7
7
|
"repository": {
|