@lcv-ideas-software/cross-review 4.2.4 → 4.2.5

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.
@@ -3,9 +3,78 @@ function valueOrDash(value) {
3
3
  return "-";
4
4
  return String(value);
5
5
  }
6
- function costText(session) {
7
- const total = session.totals.cost.total_cost;
8
- return total == null ? "unknown" : `$${total.toFixed(6)} ${session.totals.cost.currency}`;
6
+ function moneyText(value, currency = "USD") {
7
+ return value == null ? "unknown" : `$${value.toFixed(6)} ${currency}`;
8
+ }
9
+ function moneyAmountText(value) {
10
+ return `$${value.toFixed(6)}`;
11
+ }
12
+ export function sessionCostBreakdown(session) {
13
+ const currency = session.totals.cost.currency ?? "USD";
14
+ let peerTotal = 0;
15
+ let peerSeen = false;
16
+ for (const round of session.rounds) {
17
+ for (const peer of round.peers) {
18
+ const value = peer.cost?.total_cost;
19
+ if (value == null || !Number.isFinite(value))
20
+ continue;
21
+ peerSeen = true;
22
+ peerTotal += value;
23
+ }
24
+ }
25
+ let generationTotal = 0;
26
+ let generationSeen = false;
27
+ for (const generation of session.generation_files ?? []) {
28
+ const value = generation.cost?.total_cost;
29
+ if (value == null || !Number.isFinite(value))
30
+ continue;
31
+ generationSeen = true;
32
+ generationTotal += value;
33
+ }
34
+ const total = session.totals.cost.total_cost ?? null;
35
+ return {
36
+ currency,
37
+ total,
38
+ peer_total: peerSeen ? peerTotal : null,
39
+ generation_total: generationSeen ? generationTotal : null,
40
+ };
41
+ }
42
+ function costSummaryLines(session) {
43
+ const breakdown = sessionCostBreakdown(session);
44
+ const lines = [
45
+ `- Cost: ${moneyText(breakdown.total, breakdown.currency)}`,
46
+ `- Peer call cost: ${moneyText(breakdown.peer_total, breakdown.currency)}`,
47
+ `- Generation cost: ${moneyText(breakdown.generation_total, breakdown.currency)}`,
48
+ ];
49
+ if (breakdown.total != null &&
50
+ breakdown.peer_total != null &&
51
+ breakdown.generation_total != null) {
52
+ lines.push(`- Cost reconciliation: ${moneyText(breakdown.total, breakdown.currency)} = ${moneyAmountText(breakdown.peer_total)} peer + ${moneyAmountText(breakdown.generation_total)} generation`);
53
+ }
54
+ return lines;
55
+ }
56
+ function evidenceChecklistLines(session) {
57
+ const checklist = session.evidence_checklist ?? [];
58
+ if (!checklist.length)
59
+ return [];
60
+ const counts = new Map();
61
+ for (const item of checklist) {
62
+ const status = item.status ?? "open";
63
+ counts.set(status, (counts.get(status) ?? 0) + 1);
64
+ }
65
+ const lines = ["## Evidence Checklist", ""];
66
+ for (const [status, count] of [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
67
+ lines.push(`- ${status}: ${count}`);
68
+ }
69
+ const notResurfaced = checklist.filter((item) => item.status === "not_resurfaced");
70
+ if (notResurfaced.length) {
71
+ lines.push("- not_resurfaced means the ask was not repeated; it is not proof that evidence was satisfied.");
72
+ for (const item of notResurfaced.slice(0, 10)) {
73
+ lines.push(` - ${item.peer}/${item.id}: ${item.ask}`);
74
+ }
75
+ }
76
+ lines.push("");
77
+ return lines;
9
78
  }
10
79
  export function sessionReportMarkdown(session, events = []) {
11
80
  const latestRound = session.rounds.at(-1);
@@ -22,7 +91,7 @@ export function sessionReportMarkdown(session, events = []) {
22
91
  `- Outcome reason: ${valueOrDash(session.outcome_reason)}`,
23
92
  `- Health: ${valueOrDash(session.convergence_health?.state)} - ${valueOrDash(session.convergence_health?.detail)}`,
24
93
  `- Rounds: ${session.rounds.length}`,
25
- `- Cost: ${costText(session)}`,
94
+ ...costSummaryLines(session),
26
95
  `- Total tokens: ${valueOrDash(session.totals.usage.total_tokens)}`,
27
96
  "",
28
97
  "## Task",
@@ -46,6 +115,7 @@ export function sessionReportMarkdown(session, events = []) {
46
115
  "## Peer Decisions",
47
116
  "",
48
117
  ];
118
+ lines.push(...evidenceChecklistLines(session));
49
119
  if (session.generation_files?.length) {
50
120
  lines.push("## Generations", "");
51
121
  for (const generation of session.generation_files) {
@@ -1 +1 @@
1
- {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/core/reports.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/core/reports.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,7 @@ export declare class SessionStore {
25
25
  readTextArtifact(sessionId: string, relativePath: string, maxChars: number): string;
26
26
  private peekNextSeq;
27
27
  private commitSeq;
28
+ private appendEventRecord;
28
29
  appendEvent(event: RuntimeEvent): Promise<void>;
29
30
  flushPendingEvents(): Promise<void>;
30
31
  readEvents(sessionId: string, sinceSeq?: number): SessionEvent[];
@@ -9,6 +9,45 @@ export const SWEEP_MIN_IDLE_MS = 24 * 60 * 60 * 1000;
9
9
  function now() {
10
10
  return new Date().toISOString();
11
11
  }
12
+ function isStubSession(session) {
13
+ const peerCosts = session.rounds.flatMap((round) => round.peers.map((peer) => peer.cost));
14
+ const generationCosts = (session.generation_files ?? []).map((generation) => generation.cost);
15
+ const costs = [...peerCosts, ...generationCosts].filter(Boolean);
16
+ if (costs.length > 0)
17
+ return costs.every((cost) => cost?.source === "stub");
18
+ return session.capability_snapshot.some((probe) => probe.provider.startsWith("stub-") || probe.model.startsWith("stub-"));
19
+ }
20
+ function sessionPeerCostTotal(session) {
21
+ let total = 0;
22
+ let seen = false;
23
+ for (const round of session.rounds) {
24
+ for (const peer of round.peers) {
25
+ const value = peer.cost?.total_cost;
26
+ if (value == null || !Number.isFinite(value))
27
+ continue;
28
+ seen = true;
29
+ total += value;
30
+ }
31
+ }
32
+ return seen ? total : null;
33
+ }
34
+ function sessionGenerationCostTotal(session) {
35
+ let total = 0;
36
+ let seen = false;
37
+ for (const generation of session.generation_files ?? []) {
38
+ const value = generation.cost?.total_cost;
39
+ if (value == null || !Number.isFinite(value))
40
+ continue;
41
+ seen = true;
42
+ total += value;
43
+ }
44
+ return seen ? total : null;
45
+ }
46
+ function addNullableCost(a, b) {
47
+ if (a == null && b == null)
48
+ return null;
49
+ return (a ?? 0) + (b ?? 0);
50
+ }
12
51
  // v2.4.0 / audit closure (P1.3): atomicWriteFile retry on Windows.
13
52
  // `fs.renameSync` in Win32 fails with EPERM/EACCES/EBUSY when the
14
53
  // destination is briefly held by another handle (AV scan, indexing,
@@ -392,6 +431,16 @@ export class SessionStore {
392
431
  commitSeq(sessionId, committed) {
393
432
  this.seqCache.set(sessionId, committed);
394
433
  }
434
+ appendEventRecord(event) {
435
+ const sessionId = event.session_id;
436
+ if (!sessionId)
437
+ return;
438
+ const file = this.eventsPath(sessionId);
439
+ fs.mkdirSync(path.dirname(file), { recursive: true });
440
+ const seq = this.peekNextSeq(sessionId, file);
441
+ fs.appendFileSync(file, `${JSON.stringify({ ...event, seq, ts: event.ts ?? now() })}\n`, "utf8");
442
+ this.commitSeq(sessionId, seq);
443
+ }
395
444
  // v4.1.0: durable event persistence. withSessionLock became async
396
445
  // with the proper-lockfile refactor; appendEvent awaits the lock so
397
446
  // callers that read events after persisting get the expected
@@ -405,13 +454,11 @@ export class SessionStore {
405
454
  const write = (async () => {
406
455
  try {
407
456
  await this.withSessionLock(sessionId, () => {
408
- const file = this.eventsPath(sessionId);
409
- const seq = this.peekNextSeq(sessionId, file);
410
- fs.appendFileSync(file, `${JSON.stringify({ ...event, seq, ts: event.ts ?? now() })}\n`, "utf8");
411
457
  // Only commit the cache AFTER the durable append succeeded.
412
- // If appendFileSync threw above, the cache still reflects the
413
- // last persisted seq and the next call reuses this seq number.
414
- this.commitSeq(sessionId, seq);
458
+ // If appendFileSync threw inside appendEventRecord, the cache
459
+ // still reflects the last persisted seq and the next call
460
+ // reuses this seq number.
461
+ this.appendEventRecord(event);
415
462
  });
416
463
  }
417
464
  catch {
@@ -730,13 +777,26 @@ export class SessionStore {
730
777
  if (reason)
731
778
  meta.outcome_reason = reason;
732
779
  delete meta.in_flight;
780
+ const ts = now();
733
781
  meta.convergence_health = {
734
782
  state: outcome === "converged" ? "converged" : outcome === "max-rounds" ? "blocked" : "stale",
735
- last_event_at: now(),
783
+ last_event_at: ts,
736
784
  detail: reason ?? outcome,
737
785
  };
738
- meta.updated_at = now();
786
+ meta.updated_at = ts;
739
787
  await writeJson(this.metaPath(sessionId), meta);
788
+ try {
789
+ this.appendEventRecord({
790
+ type: "session.finalized",
791
+ session_id: sessionId,
792
+ ts,
793
+ message: `Session finalized as ${outcome}${reason ? `: ${reason}` : ""}`,
794
+ data: { outcome, reason: reason ?? null },
795
+ });
796
+ }
797
+ catch {
798
+ /* event persistence is best-effort; session_doctor will flag gaps */
799
+ }
740
800
  return meta;
741
801
  });
742
802
  }
@@ -763,6 +823,7 @@ export class SessionStore {
763
823
  async markCancelled(sessionId, reason = "cancelled") {
764
824
  return this.withSessionLock(sessionId, async () => {
765
825
  const meta = this.read(sessionId);
826
+ const ts = now();
766
827
  meta.outcome = "aborted";
767
828
  meta.outcome_reason = reason;
768
829
  delete meta.in_flight;
@@ -771,15 +832,27 @@ export class SessionStore {
771
832
  reason,
772
833
  job_id: meta.control?.job_id,
773
834
  requested_at: meta.control?.requested_at,
774
- updated_at: now(),
835
+ updated_at: ts,
775
836
  };
776
837
  meta.convergence_health = {
777
838
  state: "stale",
778
- last_event_at: now(),
839
+ last_event_at: ts,
779
840
  detail: reason,
780
841
  };
781
- meta.updated_at = now();
842
+ meta.updated_at = ts;
782
843
  await writeJson(this.metaPath(sessionId), meta);
844
+ try {
845
+ this.appendEventRecord({
846
+ type: "session.cancelled",
847
+ session_id: sessionId,
848
+ ts,
849
+ message: `Session cancelled: ${reason}`,
850
+ data: { outcome: "aborted", reason },
851
+ });
852
+ }
853
+ catch {
854
+ /* event persistence is best-effort; session_doctor will flag gaps */
855
+ }
783
856
  return meta;
784
857
  });
785
858
  }
@@ -1349,11 +1422,19 @@ export class SessionStore {
1349
1422
  const maxRoundsSessions = [];
1350
1423
  const selfLeadMetadata = [];
1351
1424
  const openEvidenceSessions = [];
1425
+ const notResurfacedEvidenceSessions = [];
1352
1426
  const grokProviderErrorSessions = [];
1353
1427
  const eventReadErrorSessions = [];
1428
+ const terminalEventMissingSessions = [];
1354
1429
  let eventsTotal = 0;
1355
1430
  let tokenDeltaEvents = 0;
1356
1431
  let tokenCompletedEvents = 0;
1432
+ let realSessions = 0;
1433
+ let stubSessions = 0;
1434
+ let peerCallCostUsd = null;
1435
+ let generationCostUsd = null;
1436
+ let totalCostUsd = null;
1437
+ let terminalEventMissingCount = 0;
1357
1438
  const pushLimited = (target, entry) => {
1358
1439
  if (target.length < cappedLimit)
1359
1440
  target.push(entry);
@@ -1365,7 +1446,18 @@ export class SessionStore {
1365
1446
  const evidenceList = session.evidence_checklist ?? [];
1366
1447
  const openEvidenceItemsList = evidenceList.filter((item) => (item.status ?? "open") === "open");
1367
1448
  const openEvidenceItems = openEvidenceItemsList.length;
1449
+ const notResurfacedEvidenceItems = evidenceList.filter((item) => item.status === "not_resurfaced").length;
1368
1450
  const grokProviderErrors = (session.failed_attempts ?? []).filter((failure) => failure.peer === "grok" && failure.failure_class === "provider_error").length;
1451
+ if (isStubSession(session))
1452
+ stubSessions += 1;
1453
+ else
1454
+ realSessions += 1;
1455
+ peerCallCostUsd = addNullableCost(peerCallCostUsd, sessionPeerCostTotal(session));
1456
+ generationCostUsd = addNullableCost(generationCostUsd, sessionGenerationCostTotal(session));
1457
+ const sessionTotalCost = session.totals.cost.total_cost;
1458
+ if (sessionTotalCost != null && Number.isFinite(sessionTotalCost)) {
1459
+ totalCostUsd = addNullableCost(totalCostUsd, sessionTotalCost);
1460
+ }
1369
1461
  const entry = {
1370
1462
  session_id: session.session_id,
1371
1463
  version: session.version,
@@ -1379,6 +1471,9 @@ export class SessionStore {
1379
1471
  rounds: session.rounds.length,
1380
1472
  updated_at: session.updated_at,
1381
1473
  ...(openEvidenceItems > 0 ? { open_evidence_items: openEvidenceItems } : {}),
1474
+ ...(notResurfacedEvidenceItems > 0
1475
+ ? { not_resurfaced_evidence_items: notResurfacedEvidenceItems }
1476
+ : {}),
1382
1477
  ...(grokProviderErrors > 0 ? { grok_provider_errors: grokProviderErrors } : {}),
1383
1478
  };
1384
1479
  // v2.22.0 (B.P2): drill-down for open-evidence entries. Aggregate
@@ -1419,6 +1514,8 @@ export class SessionStore {
1419
1514
  pushLimited(selfLeadMetadata, entry);
1420
1515
  if (openEvidenceItems > 0)
1421
1516
  pushLimited(openEvidenceSessions, entry);
1517
+ if (notResurfacedEvidenceItems > 0)
1518
+ pushLimited(notResurfacedEvidenceSessions, entry);
1422
1519
  if (grokProviderErrors > 0)
1423
1520
  pushLimited(grokProviderErrorSessions, entry);
1424
1521
  let sessionEvents = [];
@@ -1429,6 +1526,18 @@ export class SessionStore {
1429
1526
  entry.event_read_error = redact(error instanceof Error ? error.message : String(error));
1430
1527
  pushLimited(eventReadErrorSessions, entry);
1431
1528
  }
1529
+ if (session.outcome) {
1530
+ const expectedTerminalEvent = session.control?.status === "cancelled" || session.outcome_reason === "session_cancelled"
1531
+ ? "session.cancelled"
1532
+ : "session.finalized";
1533
+ const hasExpectedTerminalEvent = sessionEvents.some((event) => event.type === expectedTerminalEvent);
1534
+ if (!hasExpectedTerminalEvent) {
1535
+ terminalEventMissingCount += 1;
1536
+ entry.terminal_event_missing = true;
1537
+ entry.terminal_event_expected = expectedTerminalEvent;
1538
+ pushLimited(terminalEventMissingSessions, entry);
1539
+ }
1540
+ }
1432
1541
  for (const event of sessionEvents) {
1433
1542
  eventsTotal += 1;
1434
1543
  if (event.type === "peer.token.delta")
@@ -1464,6 +1573,9 @@ export class SessionStore {
1464
1573
  if (openEvidenceSessions.length > 0) {
1465
1574
  recommendations.push("Address or explicitly terminal-mark open evidence checklist items before expecting convergence.");
1466
1575
  }
1576
+ if (notResurfacedEvidenceSessions.length > 0) {
1577
+ recommendations.push("`not_resurfaced` evidence items are inference-only; review them separately from satisfied/deferred/rejected items.");
1578
+ }
1467
1579
  if (grokProviderErrorSessions.length > 0) {
1468
1580
  recommendations.push("Run a Grok-specific smoke/probe for sessions with grok provider errors before relying on Grok in release gates.");
1469
1581
  }
@@ -1473,21 +1585,32 @@ export class SessionStore {
1473
1585
  if (eventsTotal > 0 && tokenDeltaEvents / eventsTotal > 0.5) {
1474
1586
  recommendations.push("Token delta events dominate this corpus; increase CROSS_REVIEW_TOKEN_DELTA_CHARS_THRESHOLD or disable token streaming for low-noise audits.");
1475
1587
  }
1588
+ if (terminalEventMissingCount > 0) {
1589
+ recommendations.push("Terminal outcome metadata exists without matching terminal events; treat as legacy/event-gap evidence and inspect before relying on event-only analytics.");
1590
+ }
1476
1591
  return {
1477
1592
  generated_at: now(),
1478
1593
  scope: "all",
1479
1594
  limit: cappedLimit,
1480
1595
  totals: {
1481
1596
  sessions: sessions.length,
1597
+ real_sessions: realSessions,
1598
+ stub_sessions: stubSessions,
1482
1599
  open: sessions.filter((session) => !session.outcome).length,
1483
- stale: sessions.filter((session) => session.convergence_health?.state === "stale").length,
1484
- blocked: sessions.filter((session) => session.convergence_health?.state === "blocked")
1485
- .length,
1600
+ stale: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "stale").length,
1601
+ blocked: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "blocked").length,
1486
1602
  max_rounds: sessions.filter((session) => session.outcome === "max-rounds").length,
1487
1603
  self_lead_metadata: selfLeadCount,
1488
1604
  open_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => (item.status ?? "open") === "open")).length,
1605
+ not_resurfaced_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => item.status === "not_resurfaced")).length,
1489
1606
  grok_provider_error_sessions: sessions.filter((session) => (session.failed_attempts ?? []).some((failure) => failure.peer === "grok" && failure.failure_class === "provider_error")).length,
1490
1607
  event_read_error_sessions: eventReadErrorSessions.length,
1608
+ terminal_event_missing_sessions: terminalEventMissingCount,
1609
+ },
1610
+ cost_breakdown: {
1611
+ total_cost_usd: totalCostUsd,
1612
+ peer_call_cost_usd: peerCallCostUsd,
1613
+ generation_cost_usd: generationCostUsd,
1491
1614
  },
1492
1615
  findings: {
1493
1616
  open_sessions: openSessions,
@@ -1499,8 +1622,10 @@ export class SessionStore {
1499
1622
  // in `totals.self_lead_metadata`.
1500
1623
  self_lead_metadata: includeLegacy ? selfLeadMetadata : [],
1501
1624
  open_evidence_sessions: openEvidenceSessions,
1625
+ not_resurfaced_evidence_sessions: notResurfacedEvidenceSessions,
1502
1626
  grok_provider_error_sessions: grokProviderErrorSessions,
1503
1627
  event_read_error_sessions: eventReadErrorSessions,
1628
+ terminal_event_missing_sessions: terminalEventMissingSessions,
1504
1629
  },
1505
1630
  event_noise: {
1506
1631
  events_total: eventsTotal,
@@ -1793,17 +1918,30 @@ export class SessionStore {
1793
1918
  continue;
1794
1919
  const finalized = await this.withSessionLock(session.session_id, async () => {
1795
1920
  const current = this.read(session.session_id);
1921
+ const ts = now();
1796
1922
  current.outcome = outcome;
1797
1923
  current.outcome_reason = reason;
1798
1924
  delete current.in_flight;
1799
1925
  current.convergence_health = {
1800
1926
  state: "stale",
1801
- last_event_at: now(),
1927
+ last_event_at: ts,
1802
1928
  detail: reason,
1803
1929
  idle_ms: idleFor,
1804
1930
  };
1805
- current.updated_at = now();
1931
+ current.updated_at = ts;
1806
1932
  await writeJson(this.metaPath(session.session_id), current);
1933
+ try {
1934
+ this.appendEventRecord({
1935
+ type: "session.finalized",
1936
+ session_id: session.session_id,
1937
+ ts,
1938
+ message: `Session finalized as ${outcome}${reason ? `: ${reason}` : ""}`,
1939
+ data: { outcome, reason, idle_ms: idleFor },
1940
+ });
1941
+ }
1942
+ catch {
1943
+ /* event persistence is best-effort; session_doctor will flag gaps */
1944
+ }
1807
1945
  return current;
1808
1946
  });
1809
1947
  swept.push(finalized);