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/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?: string;
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?: string;
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?: string;
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?: string;
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: true,
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({ lines, keeps, input, reason: "no api key", bytesIn, linesIn: lines.length });
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: fallbackReason(error),
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: `output over ${String(input.config.maxPruneBytes)} bytes`,
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 fallbackReason(error) {
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 env = input.env ?? process.env;
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 selection = await selectLines({
2042
- text: input.text,
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
- client,
2047
- config,
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
- return await pruneOutput({ ...rest, text: await readStream(stream) });
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.0",
4
- "description": "Keep only the lines of a command's output that matter for the current task, decided per line by Jev, with the full output saved on disk",
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": {