@siuver/omp-debug-mode 0.1.2 → 0.1.3

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/src/log-files.ts CHANGED
@@ -24,6 +24,75 @@ export function readJsonlLines(file: string | null): string[] {
24
24
  }
25
25
  }
26
26
 
27
+ interface CountEntry {
28
+ size: number;
29
+ mtimeMs: number;
30
+ count: number;
31
+ }
32
+
33
+ /**
34
+ * Line counts keyed by file identity. The reproduction gate polls the active
35
+ * log once a second, so re-reading every archived run each time would scale
36
+ * with the length of the debugging session for no new information.
37
+ */
38
+ export class JsonlLineCounter {
39
+ #cache = new Map<string, CountEntry>();
40
+
41
+ count(file: string | null): number {
42
+ if (!file) return 0;
43
+ const stat = fs.statSync(file, { throwIfNoEntry: false });
44
+ if (!stat) {
45
+ this.#cache.delete(file);
46
+ return 0;
47
+ }
48
+ const hit = this.#cache.get(file);
49
+ if (hit && hit.size === stat.size && hit.mtimeMs === stat.mtimeMs) return hit.count;
50
+ const count = readJsonlLines(file).length;
51
+ this.#cache.set(file, { size: stat.size, mtimeMs: stat.mtimeMs, count });
52
+ return count;
53
+ }
54
+
55
+ clear(): void {
56
+ this.#cache.clear();
57
+ }
58
+ }
59
+
60
+ export interface HypothesisTally {
61
+ id: string;
62
+ count: number;
63
+ }
64
+
65
+ const UNATTRIBUTED = "(unattributed)";
66
+
67
+ /**
68
+ * Group observations by the hypothesis each probe was meant to settle, so the
69
+ * user and the agent can see which hypotheses actually produced evidence
70
+ * before anyone reads the raw log.
71
+ */
72
+ export function summarizeHypotheses(lines: readonly string[]): HypothesisTally[] {
73
+ const counts = new Map<string, number>();
74
+ for (const line of lines) {
75
+ let id = UNATTRIBUTED;
76
+ try {
77
+ const entry = JSON.parse(line) as { hypothesisId?: unknown };
78
+ if (typeof entry.hypothesisId === "string" && entry.hypothesisId.trim().length > 0) {
79
+ id = entry.hypothesisId.trim();
80
+ }
81
+ } catch {
82
+ continue;
83
+ }
84
+ counts.set(id, (counts.get(id) ?? 0) + 1);
85
+ }
86
+ return [...counts]
87
+ .map(([id, count]) => ({ id, count }))
88
+ .sort((a, b) => (b.count !== a.count ? b.count - a.count : a.id < b.id ? -1 : 1));
89
+ }
90
+
91
+ export function describeHypotheses(tallies: readonly HypothesisTally[]): string {
92
+ if (tallies.length === 0) return "no hypothesis-attributed observations";
93
+ return tallies.map(t => `${t.id}=${t.count}`).join(", ");
94
+ }
95
+
27
96
  export function prepareRunLog(debugDir: string, previousRun: string | null): string {
28
97
  const activeFile = path.join(debugDir, ACTIVE_LOG_FILE);
29
98
  const archivedFile = previousRun ? path.join(debugDir, `${previousRun}.jsonl`) : null;