@cairn-tool/cairn 3.3.1 → 3.5.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.
Files changed (108) hide show
  1. package/README.md +6 -4
  2. package/dist/agent/manifest.d.ts +10 -0
  3. package/dist/agent/manifest.js +23 -0
  4. package/dist/agent/manifest.js.map +1 -1
  5. package/dist/agent/parser.js +1 -15
  6. package/dist/agent/parser.js.map +1 -1
  7. package/dist/agent/render.js +47 -3
  8. package/dist/agent/render.js.map +1 -1
  9. package/dist/cli.js +48 -3
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/qa-list.d.ts +7 -0
  12. package/dist/commands/qa-list.js +69 -0
  13. package/dist/commands/qa-list.js.map +1 -0
  14. package/dist/commands/qa-run.d.ts +3 -0
  15. package/dist/commands/qa-run.js +106 -0
  16. package/dist/commands/qa-run.js.map +1 -0
  17. package/dist/commands/qa-summary.d.ts +6 -0
  18. package/dist/commands/qa-summary.js +38 -0
  19. package/dist/commands/qa-summary.js.map +1 -0
  20. package/dist/commands/scripts.d.ts +16 -1
  21. package/dist/commands/scripts.js +27 -4
  22. package/dist/commands/scripts.js.map +1 -1
  23. package/dist/config-schema.d.ts +2 -0
  24. package/dist/config-schema.js +4 -0
  25. package/dist/config-schema.js.map +1 -1
  26. package/dist/config.d.ts +12 -2
  27. package/dist/config.js +36 -7
  28. package/dist/config.js.map +1 -1
  29. package/dist/contract/registry.js +46 -3
  30. package/dist/contract/registry.js.map +1 -1
  31. package/dist/contract/schemas/index.js +2 -0
  32. package/dist/contract/schemas/index.js.map +1 -1
  33. package/dist/contract/schemas/qa.d.ts +2 -0
  34. package/dist/contract/schemas/qa.js +111 -0
  35. package/dist/contract/schemas/qa.js.map +1 -0
  36. package/dist/qa/agent.d.ts +25 -0
  37. package/dist/qa/agent.js +142 -0
  38. package/dist/qa/agent.js.map +1 -0
  39. package/dist/qa/agents/claude-code.d.ts +2 -0
  40. package/dist/qa/agents/claude-code.js +121 -0
  41. package/dist/qa/agents/claude-code.js.map +1 -0
  42. package/dist/qa/agents/cursor.d.ts +5 -0
  43. package/dist/qa/agents/cursor.js +143 -0
  44. package/dist/qa/agents/cursor.js.map +1 -0
  45. package/dist/qa/agents/index.d.ts +32 -0
  46. package/dist/qa/agents/index.js +83 -0
  47. package/dist/qa/agents/index.js.map +1 -0
  48. package/dist/qa/agents/types.d.ts +61 -0
  49. package/dist/qa/agents/types.js +18 -0
  50. package/dist/qa/agents/types.js.map +1 -0
  51. package/dist/qa/async.d.ts +27 -0
  52. package/dist/qa/async.js +91 -0
  53. package/dist/qa/async.js.map +1 -0
  54. package/dist/qa/casefile.d.ts +35 -0
  55. package/dist/qa/casefile.js +181 -0
  56. package/dist/qa/casefile.js.map +1 -0
  57. package/dist/qa/cases.d.ts +51 -0
  58. package/dist/qa/cases.js +144 -0
  59. package/dist/qa/cases.js.map +1 -0
  60. package/dist/qa/config.d.ts +50 -0
  61. package/dist/qa/config.js +105 -0
  62. package/dist/qa/config.js.map +1 -0
  63. package/dist/qa/demo.d.ts +10 -0
  64. package/dist/qa/demo.js +194 -0
  65. package/dist/qa/demo.js.map +1 -0
  66. package/dist/qa/format.d.ts +38 -0
  67. package/dist/qa/format.js +208 -0
  68. package/dist/qa/format.js.map +1 -0
  69. package/dist/qa/harness.d.ts +68 -0
  70. package/dist/qa/harness.js +528 -0
  71. package/dist/qa/harness.js.map +1 -0
  72. package/dist/qa/keys.d.ts +12 -0
  73. package/dist/qa/keys.js +96 -0
  74. package/dist/qa/keys.js.map +1 -0
  75. package/dist/qa/layout.d.ts +102 -0
  76. package/dist/qa/layout.js +278 -0
  77. package/dist/qa/layout.js.map +1 -0
  78. package/dist/qa/lock.d.ts +5 -0
  79. package/dist/qa/lock.js +114 -0
  80. package/dist/qa/lock.js.map +1 -0
  81. package/dist/qa/options.d.ts +38 -0
  82. package/dist/qa/options.js +165 -0
  83. package/dist/qa/options.js.map +1 -0
  84. package/dist/qa/outcome.d.ts +30 -0
  85. package/dist/qa/outcome.js +162 -0
  86. package/dist/qa/outcome.js.map +1 -0
  87. package/dist/qa/reports.d.ts +51 -0
  88. package/dist/qa/reports.js +121 -0
  89. package/dist/qa/reports.js.map +1 -0
  90. package/dist/qa/schedule.d.ts +55 -0
  91. package/dist/qa/schedule.js +145 -0
  92. package/dist/qa/schedule.js.map +1 -0
  93. package/dist/qa/screen.d.ts +31 -0
  94. package/dist/qa/screen.js +146 -0
  95. package/dist/qa/screen.js.map +1 -0
  96. package/dist/qa/slot.d.ts +28 -0
  97. package/dist/qa/slot.js +126 -0
  98. package/dist/qa/slot.js.map +1 -0
  99. package/dist/qa/tracker.d.ts +36 -0
  100. package/dist/qa/tracker.js +357 -0
  101. package/dist/qa/tracker.js.map +1 -0
  102. package/dist/query/execute.js +0 -0
  103. package/dist/query/execute.js.map +1 -1
  104. package/dist/scripts/resolve.js +1 -4
  105. package/dist/scripts/resolve.js.map +1 -1
  106. package/dist/usage/providers/cursor.js +1 -1
  107. package/dist/usage/providers/cursor.js.map +1 -1
  108. package/package.json +2 -1
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The work queue plus the per-case concurrency constraints.
3
+ *
4
+ * A `parallel: true` case is unconstrained (except by the per-backend cap). A
5
+ * `parallel: false` case with tags holds each of those tags as a mutex while it
6
+ * runs. A `parallel: false` case with no tags is a barrier: nothing runs beside
7
+ * it, and nothing behind it starts while it waits for the agents in front to finish.
8
+ *
9
+ * Invariant: with `running === 0` and a non-empty queue this never returns null —
10
+ * no tag can be held, an exclusive case is startable, and a per-backend cap is
11
+ * skipped when the queue is idle so a cap can never deadlock an idle queue.
12
+ */
13
+ export class Scheduler {
14
+ pending;
15
+ heldTags = new Set();
16
+ caps;
17
+ agentRunning = new Map();
18
+ running = 0;
19
+ exclusive = null;
20
+ waiters = [];
21
+ /** Bumped by every wake. A worker samples it before `take()` so a wake cannot be lost. */
22
+ generation = 0;
23
+ /** Set by `take()` when it returns null, so the TUI can say why nothing is starting. */
24
+ blocked = null;
25
+ constructor(cases, caps = new Map()) {
26
+ this.pending = [...cases];
27
+ this.caps = caps;
28
+ }
29
+ /** Cases not yet dispatched. */
30
+ get size() {
31
+ return this.pending.length;
32
+ }
33
+ get inFlight() {
34
+ return this.running;
35
+ }
36
+ /** Sample before `take()`; hand back to `changed()` so a wake in between is not missed. */
37
+ get generationId() {
38
+ return this.generation;
39
+ }
40
+ capFor(kase) {
41
+ return this.caps.get(kase.agentName) ?? Number.POSITIVE_INFINITY;
42
+ }
43
+ atCap(kase) {
44
+ return (this.agentRunning.get(kase.agentName) ?? 0) >= this.capFor(kase);
45
+ }
46
+ /**
47
+ * The first case that may start right now, with its tags reserved. Null when the queue is empty
48
+ * or every startable candidate is blocked.
49
+ *
50
+ * Invariant: with `running === 0` and a non-empty queue this never returns null — no tag can be
51
+ * held and an exclusive case is startable — so a queue can never stall with nothing in flight.
52
+ */
53
+ take() {
54
+ if (this.pending.length === 0) {
55
+ this.blocked = null;
56
+ return null;
57
+ }
58
+ if (this.exclusive !== null) {
59
+ this.blocked = `${this.exclusive.name} is running alone`;
60
+ return null;
61
+ }
62
+ const waitingOn = new Set();
63
+ const waitingAgents = new Set();
64
+ for (const [i, kase] of this.pending.entries()) {
65
+ if (this.atCap(kase) && this.running > 0) {
66
+ waitingAgents.add(kase.agentName);
67
+ continue;
68
+ }
69
+ if (kase.parallel)
70
+ return this.claim(i, kase);
71
+ if (kase.tags.length === 0) {
72
+ if (this.running === 0)
73
+ return this.claim(i, kase);
74
+ // A barrier: hold everything behind it rather than starving it.
75
+ this.blocked = `waiting for ${this.running} agent(s) to finish before ${kase.name}`;
76
+ return null;
77
+ }
78
+ const clash = kase.tags.filter((t) => this.heldTags.has(t));
79
+ if (clash.length === 0)
80
+ return this.claim(i, kase);
81
+ for (const tag of clash)
82
+ waitingOn.add(tag);
83
+ }
84
+ if (waitingAgents.size > 0) {
85
+ this.blocked = `waiting on agent cap: ${[...waitingAgents].sort().join(", ")}`;
86
+ return null;
87
+ }
88
+ this.blocked =
89
+ waitingOn.size > 0 ? `waiting on tag: ${[...waitingOn].sort().join(", ")}` : null;
90
+ return null;
91
+ }
92
+ claim(index, kase) {
93
+ this.pending.splice(index, 1);
94
+ this.running += 1;
95
+ this.agentRunning.set(kase.agentName, (this.agentRunning.get(kase.agentName) ?? 0) + 1);
96
+ this.blocked = null;
97
+ if (kase.parallel)
98
+ return kase;
99
+ if (kase.tags.length === 0)
100
+ this.exclusive = kase;
101
+ else
102
+ for (const tag of kase.tags)
103
+ this.heldTags.add(tag);
104
+ return kase;
105
+ }
106
+ /** Give back whatever `take()` reserved, then wake every parked worker to re-scan. */
107
+ release(kase) {
108
+ this.running = Math.max(0, this.running - 1);
109
+ const held = (this.agentRunning.get(kase.agentName) ?? 1) - 1;
110
+ if (held <= 0)
111
+ this.agentRunning.delete(kase.agentName);
112
+ else
113
+ this.agentRunning.set(kase.agentName, held);
114
+ if (this.exclusive === kase)
115
+ this.exclusive = null;
116
+ for (const tag of kase.tags)
117
+ this.heldTags.delete(tag);
118
+ this.wake();
119
+ }
120
+ /** Release parked workers without freeing anything — for stop and drain. */
121
+ wake() {
122
+ this.generation += 1;
123
+ const parked = this.waiters;
124
+ this.waiters = [];
125
+ for (const resume of parked)
126
+ resume();
127
+ }
128
+ /**
129
+ * Park until something is released, or the harness wakes us to shut down. `seen` is the
130
+ * generation sampled before the `take()` that came back empty: if a wake landed in between,
131
+ * this returns immediately rather than sleeping through it.
132
+ */
133
+ changed(seen) {
134
+ if (seen !== this.generation)
135
+ return Promise.resolve();
136
+ return new Promise((resolve) => {
137
+ this.waiters.push(resolve);
138
+ });
139
+ }
140
+ /** Why nothing can start, for the pane region when no agent is running. */
141
+ blockedReason() {
142
+ return this.blocked;
143
+ }
144
+ }
145
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../src/qa/schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /** What Screen needs from the harness to act on a keypress. */
2
+ export interface KeyTarget {
3
+ requestStop(): void;
4
+ requestDrain(): void;
5
+ togglePause(): void;
6
+ }
7
+ export declare class Screen {
8
+ readonly enabled: boolean;
9
+ mode: "panes" | "table";
10
+ /** The zoomed *slot id*, not a screen position — panes come and go as cases start and finish. */
11
+ zoom: number | null;
12
+ /** Slot ids currently on screen, in order, refreshed each frame. Digit `k` picks the k-th. */
13
+ visibleSlots: readonly number[];
14
+ scroll: number;
15
+ page: number;
16
+ private entered;
17
+ private restored;
18
+ private stopKeys;
19
+ constructor(enabled: boolean);
20
+ enter(): void;
21
+ /** Idempotent, synchronous, safe to call from an exit handler. */
22
+ restore(): void;
23
+ size(): [rows: number, cols: number];
24
+ render(frame: readonly string[], rows: number, cols: number): void;
25
+ /**
26
+ * Read keys in raw mode. The 250 ms debounce replaces the Python's separate keyboard task:
27
+ * a buffer still holding a lone ESC after that long is a real Escape keypress.
28
+ */
29
+ listen(target: KeyTarget, onKey?: () => void): void;
30
+ handleKey(key: string, target: KeyTarget): void;
31
+ }
@@ -0,0 +1,146 @@
1
+ import { writeSync } from "node:fs";
2
+ import { padClip } from "./format.js";
3
+ import { KeyDecoder } from "./keys.js";
4
+ const ENTER = "\x1b[?1049h\x1b[?7l\x1b[?25l\x1b[H\x1b[2J";
5
+ const LEAVE = "\x1b[?7h\x1b[?25h\x1b[?1049l";
6
+ export class Screen {
7
+ enabled;
8
+ mode = "panes";
9
+ /** The zoomed *slot id*, not a screen position — panes come and go as cases start and finish. */
10
+ zoom = null;
11
+ /** Slot ids currently on screen, in order, refreshed each frame. Digit `k` picks the k-th. */
12
+ visibleSlots = [];
13
+ scroll = 0;
14
+ page = 10;
15
+ entered = false;
16
+ restored = false;
17
+ stopKeys = null;
18
+ constructor(enabled) {
19
+ this.enabled = enabled && process.stdout.isTTY === true && process.stdin.isTTY === true;
20
+ }
21
+ enter() {
22
+ if (!this.enabled || this.entered)
23
+ return;
24
+ this.entered = true;
25
+ process.stdin.setRawMode(true);
26
+ process.stdin.resume();
27
+ // writeSync, not process.stdout.write: guaranteed to land even during teardown.
28
+ writeSync(1, ENTER);
29
+ }
30
+ /** Idempotent, synchronous, safe to call from an exit handler. */
31
+ restore() {
32
+ if (!this.entered || this.restored)
33
+ return;
34
+ this.restored = true;
35
+ this.stopKeys?.();
36
+ this.stopKeys = null;
37
+ try {
38
+ writeSync(1, LEAVE);
39
+ }
40
+ catch {
41
+ /* stdout already gone */
42
+ }
43
+ try {
44
+ process.stdin.setRawMode(false);
45
+ }
46
+ catch {
47
+ /* not a tty any more */
48
+ }
49
+ // Not optional: a resumed TTY holds an active handle and the CLI would hang on exit.
50
+ process.stdin.pause();
51
+ }
52
+ size() {
53
+ return [process.stdout.rows || 24, process.stdout.columns || 80];
54
+ }
55
+ render(frame, rows, cols) {
56
+ if (!this.enabled)
57
+ return;
58
+ const shown = frame.slice(0, rows);
59
+ const buf = ["\x1b[H"];
60
+ const last = shown.length - 1;
61
+ for (const [i, line] of shown.entries()) {
62
+ buf.push(padClip(line, cols), "\x1b[K");
63
+ if (i !== last)
64
+ buf.push("\r\n");
65
+ }
66
+ if (shown.length < rows)
67
+ buf.push("\x1b[J");
68
+ process.stdout.write(buf.join(""));
69
+ }
70
+ /**
71
+ * Read keys in raw mode. The 250 ms debounce replaces the Python's separate keyboard task:
72
+ * a buffer still holding a lone ESC after that long is a real Escape keypress.
73
+ */
74
+ listen(target, onKey) {
75
+ if (!this.enabled || this.stopKeys)
76
+ return;
77
+ const decoder = new KeyDecoder();
78
+ let timer;
79
+ const deliver = (keys) => {
80
+ for (const key of keys)
81
+ this.handleKey(key, target);
82
+ if (keys.length > 0)
83
+ onKey?.();
84
+ };
85
+ const onData = (chunk) => {
86
+ if (timer)
87
+ clearTimeout(timer);
88
+ deliver(decoder.push(chunk.toString("utf8")));
89
+ if (decoder.pending) {
90
+ timer = setTimeout(() => deliver(decoder.flush()), 250);
91
+ timer.unref();
92
+ }
93
+ };
94
+ process.stdin.on("data", onData);
95
+ this.stopKeys = () => {
96
+ process.stdin.off("data", onData);
97
+ if (timer)
98
+ clearTimeout(timer);
99
+ };
100
+ }
101
+ handleKey(key, target) {
102
+ if (key === "q" || key === "Q" || key === "\x03") {
103
+ target.requestStop();
104
+ return;
105
+ }
106
+ if (key === "p" || key === "P") {
107
+ target.togglePause();
108
+ return;
109
+ }
110
+ if (key === "d" || key === "D") {
111
+ target.requestDrain();
112
+ return;
113
+ }
114
+ if (key === "r" || key === "R") {
115
+ this.mode = this.mode === "table" ? "panes" : "table";
116
+ return;
117
+ }
118
+ if (this.mode === "table") {
119
+ if (key === "esc" || key === "0")
120
+ this.mode = "panes";
121
+ else if (key === "up")
122
+ this.scroll -= 1;
123
+ else if (key === "down")
124
+ this.scroll += 1;
125
+ else if (key === "pgup")
126
+ this.scroll -= this.page;
127
+ else if (key === "pgdn")
128
+ this.scroll += this.page;
129
+ else if (key === "home")
130
+ this.scroll = 0;
131
+ else if (key === "end")
132
+ this.scroll = 10 ** 9;
133
+ return;
134
+ }
135
+ if (key === "esc" || key === "0") {
136
+ this.zoom = null;
137
+ }
138
+ else if (key.length === 1 && key >= "1" && key <= "9") {
139
+ // The digit is the pane's position on screen, which is the number its header shows.
140
+ const slot = this.visibleSlots[Number.parseInt(key, 10) - 1];
141
+ if (slot !== undefined)
142
+ this.zoom = this.zoom === slot ? null : slot;
143
+ }
144
+ }
145
+ }
146
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/qa/screen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,KAAK,GAAG,2CAA2C,CAAC;AAC1D,MAAM,KAAK,GAAG,8BAA8B,CAAC;AAS7C,MAAM,OAAO,MAAM;IACR,OAAO,CAAU;IAC1B,IAAI,GAAsB,OAAO,CAAC;IAClC,iGAAiG;IACjG,IAAI,GAAkB,IAAI,CAAC;IAC3B,8FAA8F;IAC9F,YAAY,GAAsB,EAAE,CAAC;IACrC,MAAM,GAAG,CAAC,CAAC;IACX,IAAI,GAAG,EAAE,CAAC;IAEF,OAAO,GAAG,KAAK,CAAC;IAChB,QAAQ,GAAG,KAAK,CAAC;IACjB,QAAQ,GAAwB,IAAI,CAAC;IAE7C,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC;IAC1F,CAAC;IAED,KAAK;QACH,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACvB,gFAAgF;QAChF,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,kEAAkE;IAClE,OAAO;QACL,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC;YACH,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;QACD,qFAAqF;QACrF,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI;QACF,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,CAAC,KAAwB,EAAE,IAAY,EAAE,IAAY;QACzD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACnC,MAAM,GAAG,GAAa,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,MAAiB,EAAE,KAAkB;QAC1C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC3C,MAAM,OAAO,GAAG,IAAI,UAAU,EAAE,CAAC;QACjC,IAAI,KAAiC,CAAC;QAEtC,MAAM,OAAO,GAAG,CAAC,IAAuB,EAAQ,EAAE;YAChD,KAAK,MAAM,GAAG,IAAI,IAAI;gBAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACpD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,KAAK,EAAE,EAAE,CAAC;QACjC,CAAC,CAAC;QAEF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;YACrC,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACxD,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,GAAS,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAClC,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,MAAiB;QACtC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACjD,MAAM,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAC/B,MAAM,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAC/B,MAAM,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YACtD,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG;gBAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;iBACjD,IAAI,GAAG,KAAK,IAAI;gBAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;iBACnC,IAAI,GAAG,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;iBACrC,IAAI,GAAG,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;iBAC7C,IAAI,GAAG,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;iBAC7C,IAAI,GAAG,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;iBACpC,IAAI,GAAG,KAAK,KAAK;gBAAE,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;YACxD,oFAAoF;YACpF,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7D,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACvE,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import type { Usage } from "./outcome.js";
2
+ import type { Case } from "./cases.js";
3
+ import type { QaEvent } from "./agents/types.js";
4
+ export type SlotStatus = "idle" | "run" | "ok" | "error" | "timeout" | "no-output" | "no-folder" | "skipped";
5
+ export declare class Slot {
6
+ readonly index: number;
7
+ case: Case | null;
8
+ status: SlotStatus;
9
+ startedMs: number | null;
10
+ endedMs: number | null;
11
+ tools: number;
12
+ currentTool: string;
13
+ streamedBytes: number;
14
+ usage: Usage | null;
15
+ sessionId: string;
16
+ exitCode: number | null;
17
+ note: string;
18
+ thinking: string;
19
+ transcript: string[];
20
+ logDir: string | null;
21
+ private transcriptFd;
22
+ constructor(index: number);
23
+ reset(kase: Case | null, logDir?: string | null): void;
24
+ closeTranscript(): void;
25
+ add(line: string): void;
26
+ elapsedSeconds(nowMs: number): number;
27
+ applyEvent(event: QaEvent): void;
28
+ }
@@ -0,0 +1,126 @@
1
+ import { closeSync, mkdirSync, openSync, writeSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { CYAN, DIM, RED, RESET, flatten, pyStrip } from "./format.js";
4
+ import { TRANSCRIPT_CAP } from "./config.js";
5
+ export class Slot {
6
+ index;
7
+ case = null;
8
+ status = "idle";
9
+ startedMs = null;
10
+ endedMs = null;
11
+ tools = 0;
12
+ currentTool = "";
13
+ streamedBytes = 0;
14
+ usage = null;
15
+ sessionId = "";
16
+ exitCode = null;
17
+ note = "";
18
+ thinking = "";
19
+ transcript = [];
20
+ logDir = null;
21
+ transcriptFd = null;
22
+ constructor(index) {
23
+ this.index = index;
24
+ }
25
+ reset(kase, logDir = null) {
26
+ this.closeTranscript();
27
+ this.case = kase;
28
+ this.status = kase === null ? "idle" : "run";
29
+ this.startedMs = kase ? performance.now() : null;
30
+ this.endedMs = null;
31
+ this.tools = 0;
32
+ this.currentTool = "";
33
+ this.streamedBytes = 0;
34
+ this.usage = null;
35
+ this.sessionId = "";
36
+ this.exitCode = null;
37
+ this.note = "";
38
+ this.thinking = "";
39
+ this.transcript = [];
40
+ this.logDir = logDir;
41
+ if (logDir !== null) {
42
+ mkdirSync(logDir, { recursive: true });
43
+ // Append + writeSync per line: a SIGKILLed harness must leave a complete transcript.
44
+ this.transcriptFd = openSync(join(logDir, "transcript.txt"), "a");
45
+ }
46
+ }
47
+ closeTranscript() {
48
+ if (this.transcriptFd === null)
49
+ return;
50
+ try {
51
+ closeSync(this.transcriptFd);
52
+ }
53
+ catch {
54
+ /* already gone */
55
+ }
56
+ this.transcriptFd = null;
57
+ }
58
+ add(line) {
59
+ const text = line.replace(/\n+$/, "");
60
+ if (text === "")
61
+ return;
62
+ this.transcript.push(text);
63
+ if (this.transcript.length > TRANSCRIPT_CAP) {
64
+ this.transcript.splice(0, this.transcript.length - TRANSCRIPT_CAP);
65
+ }
66
+ if (this.transcriptFd !== null) {
67
+ try {
68
+ writeSync(this.transcriptFd, `${text}\n`);
69
+ }
70
+ catch {
71
+ /* the log is best-effort; never take the run down for it */
72
+ }
73
+ }
74
+ }
75
+ elapsedSeconds(nowMs) {
76
+ if (this.startedMs === null)
77
+ return 0;
78
+ const end = this.endedMs ?? nowMs;
79
+ return (end - this.startedMs) / 1000;
80
+ }
81
+ applyEvent(event) {
82
+ if (event.sessionId && !this.sessionId)
83
+ this.sessionId = event.sessionId;
84
+ if (event.kind === "thinking") {
85
+ if (event.phase === "delta") {
86
+ this.thinking += event.text ?? "";
87
+ }
88
+ else {
89
+ const text = event.text ?? this.thinking;
90
+ if (pyStrip(text) !== "") {
91
+ this.add(`${DIM}> ${RESET}${flatten(text)}`);
92
+ }
93
+ this.thinking = "";
94
+ }
95
+ return;
96
+ }
97
+ if (event.kind === "text") {
98
+ this.add(`» ${flatten(event.text)}`);
99
+ return;
100
+ }
101
+ if (event.kind === "tool") {
102
+ const shown = `${event.name} ${event.summary}`.replace(/[ \t\n\r\v\f]+$/, "");
103
+ if (event.phase === "started") {
104
+ this.tools += 1;
105
+ this.currentTool = shown;
106
+ this.add(`${CYAN}! ${RESET}${shown}`);
107
+ }
108
+ else {
109
+ this.currentTool = "";
110
+ if (event.failed)
111
+ this.add(`${RED}x ${RESET}${shown}`);
112
+ }
113
+ return;
114
+ }
115
+ if (event.kind === "usage") {
116
+ this.usage = event.usage;
117
+ if (event.error)
118
+ this.note = flatten(event.error).slice(0, 160);
119
+ return;
120
+ }
121
+ if (event.kind === "init" && event.model) {
122
+ this.add(`${DIM}model ${event.model}${RESET}`);
123
+ }
124
+ }
125
+ }
126
+ //# sourceMappingURL=slot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/qa/slot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ export interface CaseAudit {
2
+ has: Record<string, boolean>;
3
+ artifacts: number;
4
+ notes: string[];
5
+ verdict: string;
6
+ passed: number;
7
+ failed: number;
8
+ total: number;
9
+ ticked: number;
10
+ unticked: number;
11
+ fidelity: string;
12
+ }
13
+ /** The verbatim body of the fenced block under a `## Script <name>` heading. */
14
+ export declare function sourceScript(scripts: string, name: string): string | null;
15
+ /** The case's title and priority as the index states them. */
16
+ export declare function indexRow(tc: number, indexText: string): [string, string];
17
+ /** The body of `## <number>. …` up to `## <following>.`, or "" when absent. */
18
+ export declare function section(text: string, number: string, following: string): string;
19
+ /** The Verdict column only. A tick elsewhere in the row is prose, not a result. */
20
+ export declare function rowVerdict(row: string): string;
21
+ /** Whether the executed script matches its documented source, and how it differs if not. */
22
+ export declare function scriptFidelity(folder: string, files: ReadonlySet<string>, scripts: string | null): [string, string];
23
+ /** Python: int(name.split("-")[1].removesuffix(".md")) */
24
+ export declare function caseNumber(name: string): number;
25
+ /** Everything the tracker knows about one case, whether or not it has been run. */
26
+ export declare function audit(tc: number, runs: string, scripts: string | null, planFile?: string | null): CaseAudit;
27
+ /** Python: date.today().isoformat() — LOCAL date. toISOString() would be UTC and can be a day off. */
28
+ export declare function todayIso(d?: Date): string;
29
+ export interface TrackerResult {
30
+ path: string;
31
+ planned: number;
32
+ run: number;
33
+ tally: Record<string, number>;
34
+ }
35
+ /** Write `<runs_dir>/summary.md`, re-deriving every column from the files on disk. */
36
+ export declare function writeCaseTracker(runsDir: string): TrackerResult;