@cairn-tool/cairn 3.4.0 → 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.
- package/README.md +6 -4
- package/dist/agent/manifest.d.ts +10 -0
- package/dist/agent/manifest.js +23 -0
- package/dist/agent/manifest.js.map +1 -1
- package/dist/agent/parser.js +1 -15
- package/dist/agent/parser.js.map +1 -1
- package/dist/agent/render.js +47 -3
- package/dist/agent/render.js.map +1 -1
- package/dist/cli.js +46 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/qa-list.d.ts +7 -0
- package/dist/commands/qa-list.js +69 -0
- package/dist/commands/qa-list.js.map +1 -0
- package/dist/commands/qa-run.d.ts +3 -0
- package/dist/commands/qa-run.js +106 -0
- package/dist/commands/qa-run.js.map +1 -0
- package/dist/commands/qa-summary.d.ts +6 -0
- package/dist/commands/qa-summary.js +38 -0
- package/dist/commands/qa-summary.js.map +1 -0
- package/dist/config-schema.d.ts +2 -0
- package/dist/config-schema.js +4 -0
- package/dist/config-schema.js.map +1 -1
- package/dist/config.d.ts +12 -2
- package/dist/config.js +36 -7
- package/dist/config.js.map +1 -1
- package/dist/contract/registry.js +43 -0
- package/dist/contract/registry.js.map +1 -1
- package/dist/contract/schemas/index.js +2 -0
- package/dist/contract/schemas/index.js.map +1 -1
- package/dist/contract/schemas/qa.d.ts +2 -0
- package/dist/contract/schemas/qa.js +111 -0
- package/dist/contract/schemas/qa.js.map +1 -0
- package/dist/qa/agent.d.ts +25 -0
- package/dist/qa/agent.js +142 -0
- package/dist/qa/agent.js.map +1 -0
- package/dist/qa/agents/claude-code.d.ts +2 -0
- package/dist/qa/agents/claude-code.js +121 -0
- package/dist/qa/agents/claude-code.js.map +1 -0
- package/dist/qa/agents/cursor.d.ts +5 -0
- package/dist/qa/agents/cursor.js +143 -0
- package/dist/qa/agents/cursor.js.map +1 -0
- package/dist/qa/agents/index.d.ts +32 -0
- package/dist/qa/agents/index.js +83 -0
- package/dist/qa/agents/index.js.map +1 -0
- package/dist/qa/agents/types.d.ts +61 -0
- package/dist/qa/agents/types.js +18 -0
- package/dist/qa/agents/types.js.map +1 -0
- package/dist/qa/async.d.ts +27 -0
- package/dist/qa/async.js +91 -0
- package/dist/qa/async.js.map +1 -0
- package/dist/qa/casefile.d.ts +35 -0
- package/dist/qa/casefile.js +181 -0
- package/dist/qa/casefile.js.map +1 -0
- package/dist/qa/cases.d.ts +51 -0
- package/dist/qa/cases.js +144 -0
- package/dist/qa/cases.js.map +1 -0
- package/dist/qa/config.d.ts +50 -0
- package/dist/qa/config.js +105 -0
- package/dist/qa/config.js.map +1 -0
- package/dist/qa/demo.d.ts +10 -0
- package/dist/qa/demo.js +194 -0
- package/dist/qa/demo.js.map +1 -0
- package/dist/qa/format.d.ts +38 -0
- package/dist/qa/format.js +208 -0
- package/dist/qa/format.js.map +1 -0
- package/dist/qa/harness.d.ts +68 -0
- package/dist/qa/harness.js +528 -0
- package/dist/qa/harness.js.map +1 -0
- package/dist/qa/keys.d.ts +12 -0
- package/dist/qa/keys.js +96 -0
- package/dist/qa/keys.js.map +1 -0
- package/dist/qa/layout.d.ts +102 -0
- package/dist/qa/layout.js +278 -0
- package/dist/qa/layout.js.map +1 -0
- package/dist/qa/lock.d.ts +5 -0
- package/dist/qa/lock.js +114 -0
- package/dist/qa/lock.js.map +1 -0
- package/dist/qa/options.d.ts +38 -0
- package/dist/qa/options.js +165 -0
- package/dist/qa/options.js.map +1 -0
- package/dist/qa/outcome.d.ts +30 -0
- package/dist/qa/outcome.js +162 -0
- package/dist/qa/outcome.js.map +1 -0
- package/dist/qa/reports.d.ts +51 -0
- package/dist/qa/reports.js +121 -0
- package/dist/qa/reports.js.map +1 -0
- package/dist/qa/schedule.d.ts +55 -0
- package/dist/qa/schedule.js +145 -0
- package/dist/qa/schedule.js.map +1 -0
- package/dist/qa/screen.d.ts +31 -0
- package/dist/qa/screen.js +146 -0
- package/dist/qa/screen.js.map +1 -0
- package/dist/qa/slot.d.ts +28 -0
- package/dist/qa/slot.js +126 -0
- package/dist/qa/slot.js.map +1 -0
- package/dist/qa/tracker.d.ts +36 -0
- package/dist/qa/tracker.js +357 -0
- package/dist/qa/tracker.js.map +1 -0
- package/dist/query/execute.js +0 -0
- package/dist/query/execute.js.map +1 -1
- package/dist/scripts/resolve.js +1 -4
- package/dist/scripts/resolve.js.map +1 -1
- package/dist/usage/providers/cursor.js +1 -1
- package/dist/usage/providers/cursor.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A latch workers await while dispatch is paused. Opening it wakes everyone at once. */
|
|
2
|
+
export declare class Gate {
|
|
3
|
+
private opened;
|
|
4
|
+
private waiters;
|
|
5
|
+
constructor(opened?: boolean);
|
|
6
|
+
get isOpen(): boolean;
|
|
7
|
+
set(opened: boolean): void;
|
|
8
|
+
wait(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
11
|
+
export declare function signalNumber(sig: NodeJS.Signals): number;
|
|
12
|
+
/**
|
|
13
|
+
* Actions that must run exactly once, on every exit path. LIFO, idempotent, synchronous only —
|
|
14
|
+
* a `process.on('exit')` handler cannot await. The terminal restore and the lock release both
|
|
15
|
+
* register here rather than installing competing handlers that fight over process.exit.
|
|
16
|
+
*
|
|
17
|
+
* Signals are deliberately NOT handled here: the harness maps them to requestStop() so a run
|
|
18
|
+
* unwinds gracefully, and the resulting normal exit fires this via 'exit'.
|
|
19
|
+
*/
|
|
20
|
+
export declare class Cleanup {
|
|
21
|
+
private actions;
|
|
22
|
+
private ran;
|
|
23
|
+
private installed;
|
|
24
|
+
add(action: () => void): void;
|
|
25
|
+
run(): void;
|
|
26
|
+
install(onFatal: (err: unknown) => void): void;
|
|
27
|
+
}
|
package/dist/qa/async.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { constants as osConstants } from "node:os";
|
|
2
|
+
/** A latch workers await while dispatch is paused. Opening it wakes everyone at once. */
|
|
3
|
+
export class Gate {
|
|
4
|
+
opened;
|
|
5
|
+
waiters = [];
|
|
6
|
+
constructor(opened = true) {
|
|
7
|
+
this.opened = opened;
|
|
8
|
+
}
|
|
9
|
+
get isOpen() {
|
|
10
|
+
return this.opened;
|
|
11
|
+
}
|
|
12
|
+
set(opened) {
|
|
13
|
+
this.opened = opened;
|
|
14
|
+
if (!opened)
|
|
15
|
+
return;
|
|
16
|
+
const pending = this.waiters;
|
|
17
|
+
this.waiters = [];
|
|
18
|
+
for (const wake of pending)
|
|
19
|
+
wake();
|
|
20
|
+
}
|
|
21
|
+
wait() {
|
|
22
|
+
if (this.opened)
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
this.waiters.push(resolve);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function sleep(ms, signal) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
if (signal?.aborted) {
|
|
32
|
+
resolve();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const onAbort = () => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
resolve();
|
|
38
|
+
};
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
signal?.removeEventListener("abort", onAbort);
|
|
41
|
+
resolve();
|
|
42
|
+
}, ms);
|
|
43
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function signalNumber(sig) {
|
|
47
|
+
return osConstants.signals[sig] ?? 0;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Actions that must run exactly once, on every exit path. LIFO, idempotent, synchronous only —
|
|
51
|
+
* a `process.on('exit')` handler cannot await. The terminal restore and the lock release both
|
|
52
|
+
* register here rather than installing competing handlers that fight over process.exit.
|
|
53
|
+
*
|
|
54
|
+
* Signals are deliberately NOT handled here: the harness maps them to requestStop() so a run
|
|
55
|
+
* unwinds gracefully, and the resulting normal exit fires this via 'exit'.
|
|
56
|
+
*/
|
|
57
|
+
export class Cleanup {
|
|
58
|
+
actions = [];
|
|
59
|
+
ran = false;
|
|
60
|
+
installed = false;
|
|
61
|
+
add(action) {
|
|
62
|
+
this.actions.push(action);
|
|
63
|
+
}
|
|
64
|
+
run() {
|
|
65
|
+
if (this.ran)
|
|
66
|
+
return;
|
|
67
|
+
this.ran = true;
|
|
68
|
+
for (const action of [...this.actions].reverse()) {
|
|
69
|
+
try {
|
|
70
|
+
action();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* a failing cleanup must not block the others */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
install(onFatal) {
|
|
78
|
+
if (this.installed)
|
|
79
|
+
return;
|
|
80
|
+
this.installed = true;
|
|
81
|
+
process.on("exit", () => this.run());
|
|
82
|
+
const fatal = (err) => {
|
|
83
|
+
this.run();
|
|
84
|
+
onFatal(err);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
};
|
|
87
|
+
process.on("uncaughtException", fatal);
|
|
88
|
+
process.on("unhandledRejection", fatal);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/qa/async.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD,yFAAyF;AACzF,MAAM,OAAO,IAAI;IACP,MAAM,CAAU;IAChB,OAAO,GAAsB,EAAE,CAAC;IAExC,YAAY,MAAM,GAAG,IAAI;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,MAAe;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAmB;IAC9C,OAAQ,WAAW,CAAC,OAA6C,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,OAAO;IACV,OAAO,GAAsB,EAAE,CAAC;IAChC,GAAG,GAAG,KAAK,CAAC;IACZ,SAAS,GAAG,KAAK,CAAC;IAE1B,GAAG,CAAC,MAAkB;QACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,GAAG;QACD,IAAI,IAAI,CAAC,GAAG;YAAE,OAAO;QACrB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;QAChB,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC;YACX,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,OAA+B;QACrC,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAQ,EAAE;YACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC;QACF,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** One `_plans/tc-<N>.yaml`, validated. Everything the harness needs about a case. */
|
|
2
|
+
export interface CaseFile {
|
|
3
|
+
/** `tc-24` — the identity, derived from the filename and cross-checked against `id`. */
|
|
4
|
+
name: string;
|
|
5
|
+
number: number;
|
|
6
|
+
/** Absolute path to the YAML itself. */
|
|
7
|
+
file: string;
|
|
8
|
+
/** YAML `id`, as written (`TC-24`). */
|
|
9
|
+
id: string;
|
|
10
|
+
/** YAML `name` — the human title. */
|
|
11
|
+
title: string;
|
|
12
|
+
agent: string;
|
|
13
|
+
/** YAML `model`, or null when the case defers to `--model`. */
|
|
14
|
+
model: string | null;
|
|
15
|
+
parallel: boolean;
|
|
16
|
+
tags: string[];
|
|
17
|
+
/** The plan body, verbatim. */
|
|
18
|
+
plan: string;
|
|
19
|
+
}
|
|
20
|
+
/** `tc-24.yaml` -> `tc-24`, or null when the name is not a case file at all. */
|
|
21
|
+
export declare function caseNameOf(entry: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Validate one parsed YAML document against the schema. Every problem is reported, not just the
|
|
24
|
+
* first, so a single run tells the author everything wrong with the file.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateCaseFile(doc: unknown, name: string, file: string, fail: (message: string) => void): CaseFile | null;
|
|
27
|
+
export interface LoadResult {
|
|
28
|
+
cases: CaseFile[];
|
|
29
|
+
errors: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every `tc-<N>.yaml` under `_plans/`, parsed and validated. Errors accumulate across files so one
|
|
33
|
+
* invocation reports every broken case rather than stopping at the first.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadCaseFiles(plansDir: string): LoadResult;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { MAX_PROMPT_BYTES } from "./config.js";
|
|
5
|
+
import { SUPPORTED_AGENTS } from "./agents/index.js";
|
|
6
|
+
const KEYS = new Set(["id", "name", "agent", "model", "parallel", "tags", "plan"]);
|
|
7
|
+
const YAML_EXT = [".yaml", ".yml"];
|
|
8
|
+
const isFile = (path) => {
|
|
9
|
+
try {
|
|
10
|
+
return statSync(path).isFile();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/** `tc-24.yaml` -> `tc-24`, or null when the name is not a case file at all. */
|
|
17
|
+
export function caseNameOf(entry) {
|
|
18
|
+
for (const ext of YAML_EXT) {
|
|
19
|
+
if (!entry.endsWith(ext))
|
|
20
|
+
continue;
|
|
21
|
+
const name = entry.slice(0, -ext.length);
|
|
22
|
+
return /^tc-\d+$/.test(name) ? name : null;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
/** A parse error carrying the position `yaml` gave us, so the message can point at a line. */
|
|
27
|
+
function parseErrorText(err) {
|
|
28
|
+
const pos = err.linePos;
|
|
29
|
+
const where = pos && pos[0] ? `${pos[0].line}:${pos[0].col}: ` : "";
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
// yaml already prefixes its message with the position; keep ours and drop the duplicate block.
|
|
32
|
+
return `${where}${message.split("\n")[0]}`;
|
|
33
|
+
}
|
|
34
|
+
function str(value, key, chk) {
|
|
35
|
+
if (typeof value !== "string") {
|
|
36
|
+
chk.fail(`\`${key}\` must be a string`);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (value.trim() === "") {
|
|
40
|
+
chk.fail(`\`${key}\` must not be empty`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validate one parsed YAML document against the schema. Every problem is reported, not just the
|
|
47
|
+
* first, so a single run tells the author everything wrong with the file.
|
|
48
|
+
*/
|
|
49
|
+
export function validateCaseFile(doc, name, file, fail) {
|
|
50
|
+
if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
|
|
51
|
+
fail("expected a YAML mapping at the top level");
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const raw = doc;
|
|
55
|
+
const chk = { fail };
|
|
56
|
+
const unknown = Object.keys(raw).filter((k) => !KEYS.has(k));
|
|
57
|
+
if (unknown.length > 0) {
|
|
58
|
+
fail(`unknown key(s): ${unknown.join(", ")} (expected ${[...KEYS].join(", ")})`);
|
|
59
|
+
}
|
|
60
|
+
const number = Number.parseInt(name.slice("tc-".length), 10);
|
|
61
|
+
const id = str(raw["id"], "id", chk);
|
|
62
|
+
if (id !== null) {
|
|
63
|
+
const match = /^tc-(\d+)$/i.exec(id.trim());
|
|
64
|
+
if (!match)
|
|
65
|
+
fail(`\`id\` must look like TC-24, not ${JSON.stringify(id)}`);
|
|
66
|
+
else if (Number.parseInt(match[1], 10) !== number) {
|
|
67
|
+
fail(`\`id\` is ${id.trim()} but the file is named ${name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const title = str(raw["name"], "name", chk);
|
|
71
|
+
const plan = str(raw["plan"], "plan", chk);
|
|
72
|
+
let agent = SUPPORTED_AGENTS[0];
|
|
73
|
+
if (raw["agent"] !== undefined) {
|
|
74
|
+
const value = str(raw["agent"], "agent", chk);
|
|
75
|
+
if (value !== null) {
|
|
76
|
+
if (!SUPPORTED_AGENTS.includes(value)) {
|
|
77
|
+
fail(`\`agent\` must be one of ${SUPPORTED_AGENTS.join(", ")}, not ${JSON.stringify(value)}`);
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
agent = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let model = null;
|
|
84
|
+
if (raw["model"] !== undefined)
|
|
85
|
+
model = str(raw["model"], "model", chk);
|
|
86
|
+
let parallel = true;
|
|
87
|
+
if (raw["parallel"] !== undefined) {
|
|
88
|
+
if (typeof raw["parallel"] !== "boolean")
|
|
89
|
+
fail("`parallel` must be true or false");
|
|
90
|
+
else
|
|
91
|
+
parallel = raw["parallel"];
|
|
92
|
+
}
|
|
93
|
+
const tags = [];
|
|
94
|
+
if (raw["tags"] !== undefined) {
|
|
95
|
+
if (!Array.isArray(raw["tags"])) {
|
|
96
|
+
fail("`tags` must be a list of strings");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
for (const [i, tag] of raw["tags"].entries()) {
|
|
100
|
+
const value = str(tag, `tags[${i}]`, chk);
|
|
101
|
+
if (value !== null)
|
|
102
|
+
tags.push(value.trim());
|
|
103
|
+
}
|
|
104
|
+
if (tags.length > 0 && parallel) {
|
|
105
|
+
fail("`tags` only constrains a case with `parallel: false`; remove one or the other");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (plan !== null) {
|
|
110
|
+
// The plan travels in the prompt argv, so an unbounded one would die at spawn with E2BIG.
|
|
111
|
+
const bytes = Buffer.byteLength(plan, "utf8");
|
|
112
|
+
if (bytes > MAX_PROMPT_BYTES) {
|
|
113
|
+
fail(`\`plan\` is ${bytes} bytes; the limit is ${MAX_PROMPT_BYTES}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (id === null || title === null || plan === null)
|
|
117
|
+
return null;
|
|
118
|
+
return { name, number, file, id: id.trim(), title, agent, model, parallel, tags, plan };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Every `tc-<N>.yaml` under `_plans/`, parsed and validated. Errors accumulate across files so one
|
|
122
|
+
* invocation reports every broken case rather than stopping at the first.
|
|
123
|
+
*/
|
|
124
|
+
export function loadCaseFiles(plansDir) {
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = readdirSync(plansDir);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return { cases: [], errors: [] };
|
|
131
|
+
}
|
|
132
|
+
const errors = [];
|
|
133
|
+
const byName = new Map();
|
|
134
|
+
const collided = new Set();
|
|
135
|
+
const found = [];
|
|
136
|
+
for (const entry of entries.sort()) {
|
|
137
|
+
const name = caseNameOf(entry);
|
|
138
|
+
if (name === null)
|
|
139
|
+
continue;
|
|
140
|
+
const file = join(plansDir, entry);
|
|
141
|
+
if (!isFile(file))
|
|
142
|
+
continue;
|
|
143
|
+
const seen = byName.get(name);
|
|
144
|
+
if (seen !== undefined) {
|
|
145
|
+
// Refuse both rather than guessing which one the author meant.
|
|
146
|
+
errors.push(`${name}: both ${seen} and ${entry} exist; keep one`);
|
|
147
|
+
collided.add(name);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
byName.set(name, entry);
|
|
151
|
+
found.push([name, file]);
|
|
152
|
+
}
|
|
153
|
+
const cases = [];
|
|
154
|
+
for (const [name, file] of found) {
|
|
155
|
+
if (collided.has(name))
|
|
156
|
+
continue;
|
|
157
|
+
const problems = [];
|
|
158
|
+
const fail = (message) => {
|
|
159
|
+
problems.push(message);
|
|
160
|
+
};
|
|
161
|
+
let doc;
|
|
162
|
+
try {
|
|
163
|
+
doc = parse(readFileSync(file, "utf8"));
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
errors.push(`${name}.yaml: ${parseErrorText(err)}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const kase = validateCaseFile(doc, name, file, fail);
|
|
170
|
+
if (problems.length > 0) {
|
|
171
|
+
for (const problem of problems)
|
|
172
|
+
errors.push(`${name}: ${problem}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (kase !== null)
|
|
176
|
+
cases.push(kase);
|
|
177
|
+
}
|
|
178
|
+
cases.sort((a, b) => a.number - b.number);
|
|
179
|
+
return { cases, errors };
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=casefile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casefile.js","sourceRoot":"","sources":["../../src/qa/casefile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CaseFile } from "./casefile.js";
|
|
2
|
+
export declare class Case {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly number: number;
|
|
5
|
+
/** Absolute path to `_plans/tc-N.yaml`. */
|
|
6
|
+
readonly file: string;
|
|
7
|
+
readonly outDir: string;
|
|
8
|
+
readonly repo: string;
|
|
9
|
+
/** The YAML `name` — a human title, distinct from `name` above, which is the identity. */
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly agentName: string;
|
|
12
|
+
readonly model: string;
|
|
13
|
+
readonly parallel: boolean;
|
|
14
|
+
readonly tags: readonly string[];
|
|
15
|
+
/** The plan text itself, verbatim from the YAML block scalar. */
|
|
16
|
+
readonly plan: string;
|
|
17
|
+
constructor(spec: CaseFile, outDir: string, repo: string, defaultModel: string);
|
|
18
|
+
/** Python: Path.relative_to(repo).as_posix(), falling back to the absolute path. */
|
|
19
|
+
rel(path: string): string;
|
|
20
|
+
get fileRel(): string;
|
|
21
|
+
get outRel(): string;
|
|
22
|
+
get tempDir(): string;
|
|
23
|
+
get tempRel(): string;
|
|
24
|
+
/** How this case schedules, for the dry-run listing and the pane note. */
|
|
25
|
+
get constraint(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Load-bearing: handed verbatim to cursor-agent. The dash is an em dash (U+2014). The plan is
|
|
28
|
+
* inlined rather than @-referenced, so the write constraint is stated before it, not after.
|
|
29
|
+
*/
|
|
30
|
+
get prompt(): string;
|
|
31
|
+
}
|
|
32
|
+
/** Python: int(re.search(r"(\d+)", name).group(1)) */
|
|
33
|
+
export declare function caseNumber(name: string): number;
|
|
34
|
+
export interface Catalog {
|
|
35
|
+
cases: Case[];
|
|
36
|
+
errors: string[];
|
|
37
|
+
}
|
|
38
|
+
/** Every case under `_plans/`, whether or not it has been run, plus every load error. */
|
|
39
|
+
export declare function catalog(runs: string, repo: string, defaultModel: string | Readonly<Record<string, string>>): Catalog;
|
|
40
|
+
/** Cases whose run folder does not exist, natural-sorted, then filtered. */
|
|
41
|
+
export declare function discover(all: readonly Case[], only?: ReadonlySet<string> | null, limit?: number | null): Case[];
|
|
42
|
+
export declare function stillEligible(kase: Case): boolean;
|
|
43
|
+
export declare function agentCommand(binary: string, kase: Case): string[];
|
|
44
|
+
export declare function commandFor(kase: Case, explicitAgent: string | null): string[];
|
|
45
|
+
export declare function shellQuote(s: string): string;
|
|
46
|
+
export declare function shellJoin(argv: readonly string[]): string;
|
|
47
|
+
/** The argv with the plan body elided, for a `--dry-run` listing that stays readable. */
|
|
48
|
+
export declare function elidedCommand(binary: string, kase: Case): string[];
|
|
49
|
+
/** Argv for the --dry-run listing: never spawned, so an absent backend is not an error. */
|
|
50
|
+
export declare function displayCommandFor(kase: Case, explicitAgent: string | null): string[];
|
|
51
|
+
export declare function elidedCommandFor(kase: Case, explicitAgent: string | null): string[];
|
package/dist/qa/cases.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { displayBinary, resolveAgent, resolveBinary } from "./agents/index.js";
|
|
4
|
+
import { loadCaseFiles } from "./casefile.js";
|
|
5
|
+
export class Case {
|
|
6
|
+
name;
|
|
7
|
+
number;
|
|
8
|
+
/** Absolute path to `_plans/tc-N.yaml`. */
|
|
9
|
+
file;
|
|
10
|
+
outDir;
|
|
11
|
+
repo;
|
|
12
|
+
/** The YAML `name` — a human title, distinct from `name` above, which is the identity. */
|
|
13
|
+
title;
|
|
14
|
+
agentName;
|
|
15
|
+
model;
|
|
16
|
+
parallel;
|
|
17
|
+
tags;
|
|
18
|
+
/** The plan text itself, verbatim from the YAML block scalar. */
|
|
19
|
+
plan;
|
|
20
|
+
constructor(spec, outDir, repo, defaultModel) {
|
|
21
|
+
this.name = spec.name;
|
|
22
|
+
this.number = spec.number;
|
|
23
|
+
this.file = spec.file;
|
|
24
|
+
this.outDir = outDir;
|
|
25
|
+
this.repo = repo;
|
|
26
|
+
this.title = spec.title;
|
|
27
|
+
this.agentName = spec.agent;
|
|
28
|
+
this.model = spec.model ?? defaultModel;
|
|
29
|
+
this.parallel = spec.parallel;
|
|
30
|
+
this.tags = spec.tags;
|
|
31
|
+
this.plan = spec.plan;
|
|
32
|
+
}
|
|
33
|
+
/** Python: Path.relative_to(repo).as_posix(), falling back to the absolute path. */
|
|
34
|
+
rel(path) {
|
|
35
|
+
const rel = relative(this.repo, path);
|
|
36
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
37
|
+
return path;
|
|
38
|
+
return rel;
|
|
39
|
+
}
|
|
40
|
+
get fileRel() {
|
|
41
|
+
return this.rel(this.file);
|
|
42
|
+
}
|
|
43
|
+
get outRel() {
|
|
44
|
+
return `${this.rel(this.outDir)}/`;
|
|
45
|
+
}
|
|
46
|
+
get tempDir() {
|
|
47
|
+
return join(dirname(this.outDir), `${basename(this.outDir)}-temp`);
|
|
48
|
+
}
|
|
49
|
+
get tempRel() {
|
|
50
|
+
return `${this.rel(this.tempDir)}/`;
|
|
51
|
+
}
|
|
52
|
+
/** How this case schedules, for the dry-run listing and the pane note. */
|
|
53
|
+
get constraint() {
|
|
54
|
+
if (this.parallel)
|
|
55
|
+
return "parallel";
|
|
56
|
+
if (this.tags.length === 0)
|
|
57
|
+
return "exclusive";
|
|
58
|
+
return `tags ${this.tags.join(", ")}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load-bearing: handed verbatim to cursor-agent. The dash is an em dash (U+2014). The plan is
|
|
62
|
+
* inlined rather than @-referenced, so the write constraint is stated before it, not after.
|
|
63
|
+
*/
|
|
64
|
+
get prompt() {
|
|
65
|
+
return (`Perform the test-case plan below. Write every deliverable to ${this.tempRel}, ` +
|
|
66
|
+
`creating that folder yourself. Do not write to ${this.outRel} — the harness ` +
|
|
67
|
+
`renames the temp folder there only after this run succeeds.\n\n---\n\n${this.plan}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Python: int(re.search(r"(\d+)", name).group(1)) */
|
|
71
|
+
export function caseNumber(name) {
|
|
72
|
+
const match = /(\d+)/.exec(name);
|
|
73
|
+
if (!match)
|
|
74
|
+
throw new Error(`no number in case name: ${name}`);
|
|
75
|
+
return Number.parseInt(match[1], 10);
|
|
76
|
+
}
|
|
77
|
+
function isFile(path) {
|
|
78
|
+
try {
|
|
79
|
+
return statSync(path).isFile();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function fallbackModel(spec, defaults) {
|
|
86
|
+
if (typeof defaults === "string")
|
|
87
|
+
return spec.model ?? defaults;
|
|
88
|
+
return spec.model ?? defaults[spec.agent] ?? resolveAgent(spec.agent).defaultModel;
|
|
89
|
+
}
|
|
90
|
+
/** Every case under `_plans/`, whether or not it has been run, plus every load error. */
|
|
91
|
+
export function catalog(runs, repo, defaultModel) {
|
|
92
|
+
const loaded = loadCaseFiles(join(runs, "_plans"));
|
|
93
|
+
const cases = loaded.cases.map((spec) => new Case(spec, join(runs, spec.name), repo, fallbackModel(spec, defaultModel)));
|
|
94
|
+
// loadCaseFiles already sorts numerically — tc-2 must precede tc-10.
|
|
95
|
+
return { cases, errors: loaded.errors };
|
|
96
|
+
}
|
|
97
|
+
/** Cases whose run folder does not exist, natural-sorted, then filtered. */
|
|
98
|
+
export function discover(all, only = null, limit = null) {
|
|
99
|
+
let cases = all.filter((c) => !existsSync(c.outDir));
|
|
100
|
+
if (only !== null)
|
|
101
|
+
cases = cases.filter((c) => only.has(c.name));
|
|
102
|
+
if (limit !== null)
|
|
103
|
+
cases = cases.slice(0, limit);
|
|
104
|
+
return cases;
|
|
105
|
+
}
|
|
106
|
+
export function stillEligible(kase) {
|
|
107
|
+
return isFile(kase.file) && !existsSync(kase.outDir);
|
|
108
|
+
}
|
|
109
|
+
export function agentCommand(binary, kase) {
|
|
110
|
+
return resolveAgent(kase.agentName).buildArgv({ repo: kase.repo, model: kase.model, prompt: kase.prompt }, binary);
|
|
111
|
+
}
|
|
112
|
+
export function commandFor(kase, explicitAgent) {
|
|
113
|
+
return agentCommand(resolveBinary(kase.agentName, explicitAgent), kase);
|
|
114
|
+
}
|
|
115
|
+
/** Python: shlex._find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search */
|
|
116
|
+
const SHELL_SAFE = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
117
|
+
export function shellQuote(s) {
|
|
118
|
+
if (SHELL_SAFE.test(s))
|
|
119
|
+
return s;
|
|
120
|
+
return `'${s.replaceAll("'", `'"'"'`)}'`;
|
|
121
|
+
}
|
|
122
|
+
export function shellJoin(argv) {
|
|
123
|
+
return argv.map(shellQuote).join(" ");
|
|
124
|
+
}
|
|
125
|
+
/** The argv with the plan body elided, for a `--dry-run` listing that stays readable. */
|
|
126
|
+
export function elidedCommand(binary, kase) {
|
|
127
|
+
const argv = agentCommand(binary, kase);
|
|
128
|
+
const head = kase.prompt.slice(0, kase.prompt.indexOf("\n\n---\n\n"));
|
|
129
|
+
const hidden = kase.plan.length;
|
|
130
|
+
argv[argv.length - 1] = `${head} …[+${hidden.toLocaleString("en-US")} chars of plan]…`;
|
|
131
|
+
return argv;
|
|
132
|
+
}
|
|
133
|
+
/** Argv for the --dry-run listing: never spawned, so an absent backend is not an error. */
|
|
134
|
+
export function displayCommandFor(kase, explicitAgent) {
|
|
135
|
+
return agentCommand(displayBinary(kase.agentName, explicitAgent), kase);
|
|
136
|
+
}
|
|
137
|
+
export function elidedCommandFor(kase, explicitAgent) {
|
|
138
|
+
const argv = displayCommandFor(kase, explicitAgent);
|
|
139
|
+
const head = kase.prompt.slice(0, kase.prompt.indexOf("\n\n---\n\n"));
|
|
140
|
+
const hidden = kase.plan.length;
|
|
141
|
+
argv[argv.length - 1] = `${head} …[+${hidden.toLocaleString("en-US")} chars of plan]…`;
|
|
142
|
+
return argv;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cases.js","sourceRoot":"","sources":["../../src/qa/cases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,MAAM,OAAO,IAAI;IACN,IAAI,CAAS;IACb,MAAM,CAAS;IACxB,2CAA2C;IAClC,IAAI,CAAS;IACb,MAAM,CAAS;IACf,IAAI,CAAS;IACtB,0FAA0F;IACjF,KAAK,CAAS;IACd,SAAS,CAAS;IAClB,KAAK,CAAS;IACd,QAAQ,CAAU;IAClB,IAAI,CAAoB;IACjC,iEAAiE;IACxD,IAAI,CAAS;IAEtB,YAAY,IAAc,EAAE,MAAc,EAAE,IAAY,EAAE,YAAoB;QAC5E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;QACxC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;IAED,oFAAoF;IACpF,GAAG,CAAC,IAAY;QACd,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACvE,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;IACrC,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,OAAO;QACT,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IACtC,CAAC;IAED,0EAA0E;IAC1E,IAAI,UAAU;QACZ,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,UAAU,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,WAAW,CAAC;QAC/C,OAAO,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACxC,CAAC;IAED;;;OAGG;IACH,IAAI,MAAM;QACR,OAAO,CACL,gEAAgE,IAAI,CAAC,OAAO,IAAI;YAChF,kDAAkD,IAAI,CAAC,MAAM,iBAAiB;YAC9E,yEAAyE,IAAI,CAAC,IAAI,EAAE,CACrF,CAAC;IACJ,CAAC;CACF;AAED,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAOD,SAAS,aAAa,CACpB,IAAc,EACd,QAAmD;IAEnD,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC;IAChE,OAAO,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC;AACrF,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,OAAO,CACrB,IAAY,EACZ,IAAY,EACZ,YAAuD;IAEvD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAC5B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CACzF,CAAC;IACF,qEAAqE;IACrE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CACtB,GAAoB,EACpB,OAAmC,IAAI,EACvC,QAAuB,IAAI;IAE3B,IAAI,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,IAAI,IAAI,KAAK,IAAI;QAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,IAAI,KAAK,KAAK,IAAI;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAU;IACtC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,IAAU;IACrD,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,SAAS,CAC3C,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAC3D,MAAM,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAU,EAAE,aAA4B;IACjE,OAAO,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAE9C,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAuB;IAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,IAAU;IACtD,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI,OAAO,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,kBAAkB,CAAC;IACvF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAAC,IAAU,EAAE,aAA4B;IACxE,OAAO,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAU,EAAE,aAA4B;IACvE,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI,OAAO,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,kBAAkB,CAAC;IACvF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Every `raise SystemExit("msg")` in the Python becomes one of these. */
|
|
2
|
+
export declare class UserError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export declare const DEFAULT_PARALLEL = 8;
|
|
5
|
+
export declare const DEFAULT_TIMEOUT_MIN = 60;
|
|
6
|
+
export declare const REPORT = "_report.md";
|
|
7
|
+
export declare const RESULTS = "_results.md";
|
|
8
|
+
export declare const TRANSCRIPT_CAP = 400;
|
|
9
|
+
export declare const RENDER_HZ = 4;
|
|
10
|
+
/** Digits 1..9 zoom a pane; a tenth pane simply has no key. */
|
|
11
|
+
export declare const MAX_ZOOM_KEYS = 9;
|
|
12
|
+
/**
|
|
13
|
+
* A case's plan travels inside the prompt argv, so it is bounded by ARG_MAX (1 MiB here, shared
|
|
14
|
+
* with the environment). Well under it, with a message that names the file instead of E2BIG.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_PROMPT_BYTES: number;
|
|
17
|
+
export declare const DONE_OK = "ok";
|
|
18
|
+
export declare const DONE_FAIL: ReadonlySet<string>;
|
|
19
|
+
export interface Options {
|
|
20
|
+
repo: string;
|
|
21
|
+
runsDir: string;
|
|
22
|
+
/** Maximum agents in flight; per-case `parallel`/`tags` may hold it below this. */
|
|
23
|
+
parallel: number;
|
|
24
|
+
/**
|
|
25
|
+
* Session-log label for `--model`. Per-case fallbacks live on the Case via
|
|
26
|
+
* `models` / the profile default — a mixed queue does not share one slug.
|
|
27
|
+
*/
|
|
28
|
+
model: string;
|
|
29
|
+
only: ReadonlySet<string> | null;
|
|
30
|
+
limit: number | null;
|
|
31
|
+
dryRun: boolean;
|
|
32
|
+
showPrompts: boolean;
|
|
33
|
+
noTui: boolean;
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
/** Explicit binary for every backend, or null to resolve per profile on PATH. */
|
|
36
|
+
agent: string | null;
|
|
37
|
+
/** When true, main() does not print the human console summary (JSON owns stdout). */
|
|
38
|
+
json: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Python: resolve_dir(raw, base, what) */
|
|
41
|
+
export declare function resolveDir(raw: string, base: string, what: string): string;
|
|
42
|
+
/** Python: shutil.which — POSIX PATH walk, no PATHEXT handling. */
|
|
43
|
+
export declare function which(name: string): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* An explicit `--agent` path. Kept expanduser'd as given — it is not absolutized,
|
|
46
|
+
* and the result goes verbatim into command.txt and --dry-run output.
|
|
47
|
+
*/
|
|
48
|
+
export declare function findExplicitAgent(explicit: string): string;
|
|
49
|
+
/** Python: parse_only("27, tc-28,") -> {"tc-27", "tc-28"} */
|
|
50
|
+
export declare function parseOnly(raw: string | null | undefined): ReadonlySet<string> | null;
|