tewip-ai 0.62.0 → 0.63.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.
@@ -1,5 +1,5 @@
1
1
  // JSONL persistence shared by the reporter, the history script and future layers.
2
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
2
+ import { openSync, readSync, closeSync, fstatSync, appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
3
3
  import { dirname, isAbsolute, join, resolve } from 'node:path';
4
4
  export const PROJECT_ROOT = join(import.meta.dirname, '..');
5
5
  export const DATA_DIR = join(PROJECT_ROOT, 'data');
@@ -13,23 +13,68 @@ export function appendJsonl(file, rows) {
13
13
  mkdirSync(dirname(file), { recursive: true });
14
14
  appendFileSync(file, rows.map((row) => JSON.stringify(row)).join('\n') + '\n', 'utf8');
15
15
  }
16
- /** Reads a JSONL file; missing file yields []. Malformed lines are skipped with a warning. */
16
+ /**
17
+ * Reads a JSONL file; missing file yields []. Malformed lines are skipped with a warning.
18
+ *
19
+ * Read in chunks rather than whole. `readFileSync(file, 'utf8')` builds one string, and a string in
20
+ * Node cannot exceed 0x1fffffe8 characters - about 512MB. These files only ever grow: the weekly
21
+ * shadow run appends every CI failure it harvests from a public project and keeps the lot, so that
22
+ * accuracy is measured against everything ever collected rather than one week of it. gutenberg's
23
+ * dataset crossed the limit and **every run since has died on this line**, for weeks, while the
24
+ * workflow's only symptom was a red tick nobody was looking at.
25
+ *
26
+ * A UTF-8 character can straddle a chunk boundary, so the last partial line is carried into the next
27
+ * read rather than decoded on its own - decoding a split character gives U+FFFD and a line that no
28
+ * longer parses.
29
+ */
17
30
  export function readJsonl(file) {
18
31
  if (!existsSync(file))
19
32
  return [];
20
33
  const rows = [];
21
- readFileSync(file, 'utf8')
22
- .split(/\r?\n/)
23
- .forEach((line, i) => {
24
- if (!line.trim())
25
- return;
26
- try {
27
- rows.push(JSON.parse(line));
34
+ const handle = openSync(file, 'r');
35
+ try {
36
+ const size = fstatSync(handle).size;
37
+ const buffer = Buffer.allocUnsafe(8 * 1024 * 1024);
38
+ let carry = Buffer.alloc(0);
39
+ let line = 0;
40
+ let offset = 0;
41
+ while (offset < size) {
42
+ const read = readSync(handle, buffer, 0, buffer.length, offset);
43
+ if (read <= 0)
44
+ break;
45
+ offset += read;
46
+ const chunk = Buffer.concat([carry, buffer.subarray(0, read)]);
47
+ let from = 0;
48
+ for (let i = 0; i < chunk.length; i += 1) {
49
+ if (chunk[i] !== 0x0a)
50
+ continue;
51
+ const text = chunk.subarray(from, i).toString('utf8').replace(/\r$/, '');
52
+ from = i + 1;
53
+ line += 1;
54
+ if (!text.trim())
55
+ continue;
56
+ try {
57
+ rows.push(JSON.parse(text));
58
+ }
59
+ catch {
60
+ console.warn(`[triage] skipping malformed line ${line} in ${file}`);
61
+ }
62
+ }
63
+ carry = chunk.subarray(from);
28
64
  }
29
- catch {
30
- console.warn(`[triage] skipping malformed line ${i + 1} in ${file}`);
65
+ const last = carry.toString('utf8').replace(/\r$/, '');
66
+ if (last.trim()) {
67
+ try {
68
+ rows.push(JSON.parse(last));
69
+ }
70
+ catch {
71
+ console.warn(`[triage] skipping malformed line ${line + 1} in ${file}`);
72
+ }
31
73
  }
32
- });
74
+ }
75
+ finally {
76
+ closeSync(handle);
77
+ }
33
78
  return rows;
34
79
  }
35
80
  export const readTriageRecords = (file = TRIAGE_FILE) => readJsonl(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
4
4
  "mcpName": "io.github.hmamut39/tewip",
5
5
  "description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
6
6
  "keywords": [