@akagilnc/pi-workflow-roles 0.1.4204 → 0.1.4228

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.
@@ -24,47 +24,78 @@ function isRecord(value) {
24
24
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
25
  }
26
26
  /**
27
- * Read session JSONL with honest live-tail semantics:
28
- * a malformed line is tolerated only when it is an unfinished final
29
- * fragment at EOF (no record terminator after it). Any malformed line
30
- * completed by a line terminator must fail loudly with file and 1-based
31
- * line context — even when no non-empty record follows — never silently
32
- * under-count.
33
- *
34
- * Loud failures throw LedgerSessionJsonlError carrying prefixRows so the
35
- * single parse kernel can still expose facts obtained before the bad line.
27
+ * Sole line-level scan of a session volume (single parse kernel, two readings).
28
+ * Keeps live-tail semantics: a malformed line is reported only when a record
29
+ * terminator completed it; an unfinished final fragment at EOF ends the scan.
30
+ * This reading never throws on a bad line — `readLedgerSessionJsonl` puts the
31
+ * loud failure back on top for the consumers that require it.
36
32
  */
37
- export async function readLedgerSessionJsonl(path) {
33
+ export async function readLedgerSessionJsonlLines(path) {
38
34
  const text = await readFile(path, "utf8");
39
35
  // split keeps a trailing empty segment iff text ends with "\n", so
40
36
  // index < lines.length - 1 means this segment was terminated.
41
37
  const lines = text.split("\n");
42
- const rows = [];
38
+ const out = [];
43
39
  for (let index = 0; index < lines.length; index += 1) {
44
- const line = lines[index];
45
- if (!line.trim())
40
+ const raw = lines[index];
41
+ if (!raw.trim())
46
42
  continue;
43
+ const lineNumber = index + 1;
47
44
  let row;
48
45
  try {
49
- row = JSON.parse(line);
46
+ row = JSON.parse(raw);
50
47
  }
51
48
  catch (error) {
52
49
  if (!(error instanceof SyntaxError))
53
50
  throw error;
54
51
  const completedByTerminator = index < lines.length - 1;
55
- if (completedByTerminator) {
56
- throw new LedgerSessionJsonlError(`malformed JSONL record in ${path} at line ${index + 1}: ${error.message}`, { path, line: index + 1, prefixRows: rows });
57
- }
58
- // unfinished fragment at EOF — keep prior complete rows
59
- break;
52
+ // unfinished fragment at EOF — not a line at all
53
+ if (!completedByTerminator)
54
+ break;
55
+ out.push({
56
+ line: lineNumber,
57
+ raw,
58
+ error: `malformed JSONL record in ${path} at line ${lineNumber}: ${error.message}`,
59
+ });
60
+ continue;
60
61
  }
61
62
  // Syntactically complete line: must be a session object. Silent omission
62
63
  // would under-count ledger evidence (failure honesty).
63
64
  if (!isRecord(row)) {
64
65
  const kind = row === null ? "null" : Array.isArray(row) ? "array" : typeof row;
65
- throw new LedgerSessionJsonlError(`complete non-object JSONL record in ${path} at line ${index + 1}: expected object, got ${kind}`, { path, line: index + 1, prefixRows: rows });
66
+ out.push({
67
+ line: lineNumber,
68
+ raw,
69
+ error: `complete non-object JSONL record in ${path} at line ${lineNumber}: expected object, got ${kind}`,
70
+ });
71
+ continue;
72
+ }
73
+ out.push({ line: lineNumber, raw, row });
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Read session JSONL with honest live-tail semantics:
79
+ * a malformed line is tolerated only when it is an unfinished final
80
+ * fragment at EOF (no record terminator after it). Any malformed line
81
+ * completed by a line terminator must fail loudly with file and 1-based
82
+ * line context — even when no non-empty record follows — never silently
83
+ * under-count.
84
+ *
85
+ * Loud failures throw LedgerSessionJsonlError carrying prefixRows so the
86
+ * single parse kernel can still expose facts obtained before the bad line.
87
+ */
88
+ export async function readLedgerSessionJsonl(path) {
89
+ const rows = [];
90
+ for (const line of await readLedgerSessionJsonlLines(path)) {
91
+ if (line.row === undefined) {
92
+ throw new LedgerSessionJsonlError(line.error ?? `unreadable JSONL record in ${path} at line ${line.line}`, {
93
+ path,
94
+ line: line.line,
95
+ prefixRows: rows,
96
+ });
66
97
  }
67
- rows.push(row);
98
+ rows.push(line.row);
68
99
  }
69
100
  return rows;
70
101
  }