@post-print/agent-test 1.1.8 → 2.0.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 +18 -66
- package/dist/cli.d.ts +2 -64
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -691
- package/dist/cli.js.map +1 -1
- package/dist/compare-scenario.d.ts +1 -1
- package/dist/compare-scenario.d.ts.map +1 -1
- package/dist/compare-scenario.js +72 -75
- package/dist/compare-scenario.js.map +1 -1
- package/dist/failures.d.ts +5 -1
- package/dist/failures.d.ts.map +1 -1
- package/dist/failures.js +1 -1
- package/dist/failures.js.map +1 -1
- package/dist/host-login.d.ts +1 -26
- package/dist/host-login.d.ts.map +1 -1
- package/dist/host-login.js +1 -92
- package/dist/host-login.js.map +1 -1
- package/dist/index.d.ts +6 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -26
- package/dist/index.js.map +1 -1
- package/dist/scenario-story.d.ts.map +1 -1
- package/dist/scenario-story.js +57 -39
- package/dist/scenario-story.js.map +1 -1
- package/dist/sdk/cli.d.ts +3 -0
- package/dist/sdk/cli.d.ts.map +1 -0
- package/dist/sdk/cli.js +20 -0
- package/dist/sdk/cli.js.map +1 -0
- package/dist/sdk/definitions.d.ts +22 -0
- package/dist/sdk/definitions.d.ts.map +1 -0
- package/dist/sdk/definitions.js +58 -0
- package/dist/sdk/definitions.js.map +1 -0
- package/dist/sdk/expect.d.ts +31 -0
- package/dist/sdk/expect.d.ts.map +1 -0
- package/dist/sdk/expect.js +68 -0
- package/dist/sdk/expect.js.map +1 -0
- package/dist/sdk/judge.d.ts +16 -0
- package/dist/sdk/judge.d.ts.map +1 -0
- package/dist/sdk/judge.js +82 -0
- package/dist/sdk/judge.js.map +1 -0
- package/dist/sdk/metrics.d.ts +3 -0
- package/dist/sdk/metrics.d.ts.map +1 -0
- package/dist/sdk/metrics.js +24 -0
- package/dist/sdk/metrics.js.map +1 -0
- package/dist/sdk/reporter.d.ts +12 -0
- package/dist/sdk/reporter.d.ts.map +1 -0
- package/dist/sdk/reporter.js +58 -0
- package/dist/sdk/reporter.js.map +1 -0
- package/dist/sdk/runtime.d.ts +33 -0
- package/dist/sdk/runtime.d.ts.map +1 -0
- package/dist/sdk/runtime.js +188 -0
- package/dist/sdk/runtime.js.map +1 -0
- package/dist/sdk/test.d.ts +27 -0
- package/dist/sdk/test.d.ts.map +1 -0
- package/dist/sdk/test.js +92 -0
- package/dist/sdk/test.js.map +1 -0
- package/dist/sdk/types.d.ts +75 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +2 -0
- package/dist/sdk/types.js.map +1 -0
- package/dist/sdk/usage.d.ts +10 -0
- package/dist/sdk/usage.d.ts.map +1 -0
- package/dist/sdk/usage.js +13 -0
- package/dist/sdk/usage.js.map +1 -0
- package/dist/sdk/viewer-results.d.ts +43 -0
- package/dist/sdk/viewer-results.d.ts.map +1 -0
- package/dist/sdk/viewer-results.js +126 -0
- package/dist/sdk/viewer-results.js.map +1 -0
- package/dist/sdk/viewer.d.ts +11 -0
- package/dist/sdk/viewer.d.ts.map +1 -0
- package/dist/sdk/viewer.js +160 -0
- package/dist/sdk/viewer.js.map +1 -0
- package/dist/sdk/workspace.d.ts +33 -0
- package/dist/sdk/workspace.d.ts.map +1 -0
- package/dist/sdk/workspace.js +100 -0
- package/dist/sdk/workspace.js.map +1 -0
- package/dist/types.d.ts +4 -40
- package/dist/types.d.ts.map +1 -1
- package/dist/viewer/catalog.d.ts +2 -8
- package/dist/viewer/catalog.d.ts.map +1 -1
- package/dist/viewer/catalog.js +23 -128
- package/dist/viewer/catalog.js.map +1 -1
- package/dist/viewer/client.d.ts.map +1 -1
- package/dist/viewer/client.js +12 -12
- package/dist/viewer/client.js.map +1 -1
- package/dist/viewer/run-controller.d.ts +4 -2
- package/dist/viewer/run-controller.d.ts.map +1 -1
- package/dist/viewer/run-controller.js +297 -248
- package/dist/viewer/run-controller.js.map +1 -1
- package/dist/viewer/server.d.ts +4 -3
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +149 -112
- package/dist/viewer/server.js.map +1 -1
- package/dist/viewer/styles.d.ts +2 -0
- package/dist/viewer/styles.d.ts.map +1 -0
- package/dist/viewer/styles.js +444 -0
- package/dist/viewer/styles.js.map +1 -0
- package/dist/worker-pool.d.ts +5 -1
- package/dist/worker-pool.d.ts.map +1 -1
- package/dist/worker-pool.js +1 -1
- package/dist/worker-pool.js.map +1 -1
- package/package.json +7 -7
- package/dist/check.d.ts +0 -38
- package/dist/check.d.ts.map +0 -1
- package/dist/check.js +0 -169
- package/dist/check.js.map +0 -1
- package/dist/command-allowlist.d.ts +0 -9
- package/dist/command-allowlist.d.ts.map +0 -1
- package/dist/command-allowlist.js +0 -29
- package/dist/command-allowlist.js.map +0 -1
- package/dist/debug-bundle.d.ts +0 -81
- package/dist/debug-bundle.d.ts.map +0 -1
- package/dist/debug-bundle.js +0 -392
- package/dist/debug-bundle.js.map +0 -1
- package/dist/discover-suites.d.ts +0 -2
- package/dist/discover-suites.d.ts.map +0 -1
- package/dist/discover-suites.js +0 -19
- package/dist/discover-suites.js.map +0 -1
- package/dist/doctor.d.ts +0 -24
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js +0 -272
- package/dist/doctor.js.map +0 -1
- package/dist/expect.d.ts +0 -59
- package/dist/expect.d.ts.map +0 -1
- package/dist/expect.js +0 -518
- package/dist/expect.js.map +0 -1
- package/dist/host-log.d.ts +0 -17
- package/dist/host-log.d.ts.map +0 -1
- package/dist/host-log.js +0 -154
- package/dist/host-log.js.map +0 -1
- package/dist/hosts.d.ts +0 -22
- package/dist/hosts.d.ts.map +0 -1
- package/dist/hosts.js +0 -84
- package/dist/hosts.js.map +0 -1
- package/dist/html-report.d.ts +0 -18
- package/dist/html-report.d.ts.map +0 -1
- package/dist/html-report.js +0 -1276
- package/dist/html-report.js.map +0 -1
- package/dist/live-isolation.d.ts +0 -82
- package/dist/live-isolation.d.ts.map +0 -1
- package/dist/live-isolation.js +0 -399
- package/dist/live-isolation.js.map +0 -1
- package/dist/live-timeout.d.ts +0 -11
- package/dist/live-timeout.d.ts.map +0 -1
- package/dist/live-timeout.js +0 -29
- package/dist/live-timeout.js.map +0 -1
- package/dist/load-adapters.d.ts +0 -13
- package/dist/load-adapters.d.ts.map +0 -1
- package/dist/load-adapters.js +0 -76
- package/dist/load-adapters.js.map +0 -1
- package/dist/load-suite.d.ts +0 -22
- package/dist/load-suite.d.ts.map +0 -1
- package/dist/load-suite.js +0 -154
- package/dist/load-suite.js.map +0 -1
- package/dist/mcp-config.d.ts +0 -13
- package/dist/mcp-config.d.ts.map +0 -1
- package/dist/mcp-config.js +0 -121
- package/dist/mcp-config.js.map +0 -1
- package/dist/preflight.d.ts +0 -3
- package/dist/preflight.d.ts.map +0 -1
- package/dist/preflight.js +0 -17
- package/dist/preflight.js.map +0 -1
- package/dist/progress.d.ts +0 -24
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js +0 -111
- package/dist/progress.js.map +0 -1
- package/dist/record-trace.d.ts +0 -64
- package/dist/record-trace.d.ts.map +0 -1
- package/dist/record-trace.js +0 -124
- package/dist/record-trace.js.map +0 -1
- package/dist/report-preview-server.d.ts +0 -2
- package/dist/report-preview-server.d.ts.map +0 -1
- package/dist/report-preview-server.js +0 -24
- package/dist/report-preview-server.js.map +0 -1
- package/dist/report-preview.d.ts +0 -35
- package/dist/report-preview.d.ts.map +0 -1
- package/dist/report-preview.js +0 -205
- package/dist/report-preview.js.map +0 -1
- package/dist/run-suite.d.ts +0 -139
- package/dist/run-suite.d.ts.map +0 -1
- package/dist/run-suite.js +0 -1669
- package/dist/run-suite.js.map +0 -1
- package/dist/scenario-finalizer.d.ts +0 -27
- package/dist/scenario-finalizer.d.ts.map +0 -1
- package/dist/scenario-finalizer.js +0 -39
- package/dist/scenario-finalizer.js.map +0 -1
- package/dist/scenario-retry.d.ts +0 -15
- package/dist/scenario-retry.d.ts.map +0 -1
- package/dist/scenario-retry.js +0 -52
- package/dist/scenario-retry.js.map +0 -1
- package/dist/scenario-seed.d.ts +0 -13
- package/dist/scenario-seed.d.ts.map +0 -1
- package/dist/scenario-seed.js +0 -72
- package/dist/scenario-seed.js.map +0 -1
- package/dist/suite-summary.d.ts +0 -16
- package/dist/suite-summary.d.ts.map +0 -1
- package/dist/suite-summary.js +0 -218
- package/dist/suite-summary.js.map +0 -1
- package/dist/theme.d.ts +0 -96
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js +0 -446
- package/dist/theme.js.map +0 -1
- package/dist/validate-seeds.d.ts +0 -20
- package/dist/validate-seeds.d.ts.map +0 -1
- package/dist/validate-seeds.js +0 -72
- package/dist/validate-seeds.js.map +0 -1
- package/dist/validate-suite.d.ts +0 -24
- package/dist/validate-suite.d.ts.map +0 -1
- package/dist/validate-suite.js +0 -542
- package/dist/validate-suite.js.map +0 -1
- package/dist/viewer/cli.d.ts +0 -17
- package/dist/viewer/cli.d.ts.map +0 -1
- package/dist/viewer/cli.js +0 -38
- package/dist/viewer/cli.js.map +0 -1
- package/dist/viewer/compare-metrics.d.ts +0 -30
- package/dist/viewer/compare-metrics.d.ts.map +0 -1
- package/dist/viewer/compare-metrics.js +0 -101
- package/dist/viewer/compare-metrics.js.map +0 -1
- package/dist/viewer/context-files.d.ts +0 -8
- package/dist/viewer/context-files.d.ts.map +0 -1
- package/dist/viewer/context-files.js +0 -57
- package/dist/viewer/context-files.js.map +0 -1
- package/dist/viewer/emit.d.ts +0 -7
- package/dist/viewer/emit.d.ts.map +0 -1
- package/dist/viewer/emit.js +0 -25
- package/dist/viewer/emit.js.map +0 -1
- package/dist/viewer/live-runner.d.ts +0 -27
- package/dist/viewer/live-runner.d.ts.map +0 -1
- package/dist/viewer/live-runner.js +0 -166
- package/dist/viewer/live-runner.js.map +0 -1
- package/dist/viewer/session-server.d.ts +0 -2
- package/dist/viewer/session-server.d.ts.map +0 -1
- package/dist/viewer/session-server.js +0 -71
- package/dist/viewer/session-server.js.map +0 -1
- package/dist/warnings.d.ts +0 -3
- package/dist/warnings.d.ts.map +0 -1
- package/dist/warnings.js +0 -13
- package/dist/warnings.js.map +0 -1
package/dist/validate-seeds.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import { createSealedWorkspace, defaultSealedOverlayPaths, isCallerHeadWorkspace, } from "@post-print/agent-harness";
|
|
3
|
-
import { discoverSuites } from "./discover-suites.js";
|
|
4
|
-
import { loadSuiteFile } from "./load-suite.js";
|
|
5
|
-
import { seedScenarioWorktree } from "./scenario-seed.js";
|
|
6
|
-
import { resolveScenarioWorkspaceRel } from "./validate-suite.js";
|
|
7
|
-
/** Apply each seedPatch in a sealed workspace to verify hunks match the current baseline. */
|
|
8
|
-
export async function validateSeedPatches(options) {
|
|
9
|
-
const suitesDir = resolve(options.cwd, options.suitesDir);
|
|
10
|
-
const suitePaths = await discoverSuites(suitesDir);
|
|
11
|
-
const filtered = options.filter
|
|
12
|
-
? suitePaths.filter((path) => path.includes(`/${options.filter}/`) ||
|
|
13
|
-
path.endsWith(`/${options.filter}/scenarios.json`))
|
|
14
|
-
: suitePaths;
|
|
15
|
-
const issues = [];
|
|
16
|
-
let checked = 0;
|
|
17
|
-
for (const suitePath of filtered) {
|
|
18
|
-
const suite = await loadSuiteFile(suitePath, { rubricsDir: options.rubricsDir });
|
|
19
|
-
for (const scenario of suite.scenarios) {
|
|
20
|
-
if (!scenario.seedPatch) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
checked++;
|
|
24
|
-
try {
|
|
25
|
-
await validateOneSeed(options.cwd, scenario.seedPatch, {
|
|
26
|
-
stageOnly: scenario.seedStageOnly,
|
|
27
|
-
workspace: resolveScenarioWorkspaceRel(suite, scenario),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
issues.push({
|
|
32
|
-
suite: suite.name,
|
|
33
|
-
scenario: scenario.name,
|
|
34
|
-
seedPatch: scenario.seedPatch,
|
|
35
|
-
message: error instanceof Error ? error.message : String(error),
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return { ok: issues.length === 0, issues, checked };
|
|
41
|
-
}
|
|
42
|
-
async function validateOneSeed(repoRoot, seedPatch, options) {
|
|
43
|
-
let cleanup;
|
|
44
|
-
let applyPath;
|
|
45
|
-
try {
|
|
46
|
-
const sealed = await createSealedWorkspace({
|
|
47
|
-
callerCwd: repoRoot,
|
|
48
|
-
workspace: options.workspace,
|
|
49
|
-
overlayPaths: isCallerHeadWorkspace(options.workspace)
|
|
50
|
-
? defaultSealedOverlayPaths()
|
|
51
|
-
: undefined,
|
|
52
|
-
});
|
|
53
|
-
cleanup = sealed.cleanup;
|
|
54
|
-
applyPath = sealed.path;
|
|
55
|
-
await seedScenarioWorktree(repoRoot, applyPath, seedPatch, {
|
|
56
|
-
stageOnly: options.stageOnly,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
if (cleanup) {
|
|
61
|
-
await cleanup().catch(() => undefined);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
export function formatSeedValidationReport(report) {
|
|
66
|
-
if (report.ok) {
|
|
67
|
-
return `Validated ${report.checked} seed patch(es): OK`;
|
|
68
|
-
}
|
|
69
|
-
const lines = report.issues.map((issue) => ` - ${issue.suite}/${issue.scenario} (${issue.seedPatch}): ${issue.message}`);
|
|
70
|
-
return [`Seed validation failed (${report.issues.length} issue(s)):`, ...lines].join("\n");
|
|
71
|
-
}
|
|
72
|
-
//# sourceMappingURL=validate-seeds.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validate-seeds.js","sourceRoot":"","sources":["../src/validate-seeds.ts"],"names":[],"mappings":"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"}
|
package/dist/validate-suite.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { AgentScenario, AgentSuiteFile } from "./types.js";
|
|
2
|
-
export interface SuiteValidationIssue {
|
|
3
|
-
suitePath: string;
|
|
4
|
-
scenario?: string;
|
|
5
|
-
field: string;
|
|
6
|
-
message: string;
|
|
7
|
-
}
|
|
8
|
-
export interface SuiteValidationReport {
|
|
9
|
-
ok: boolean;
|
|
10
|
-
issues: SuiteValidationIssue[];
|
|
11
|
-
suitesChecked: number;
|
|
12
|
-
scenariosChecked: number;
|
|
13
|
-
}
|
|
14
|
-
/** Semantic validation beyond structural loadSuiteFile checks. */
|
|
15
|
-
export declare function validateSuiteFile(suitePath: string, suite: AgentSuiteFile): SuiteValidationIssue[];
|
|
16
|
-
export declare function validateSuitePaths(suitePaths: string[], options?: {
|
|
17
|
-
validatePaths?: boolean;
|
|
18
|
-
repoRoot?: string;
|
|
19
|
-
rubricsDir?: string;
|
|
20
|
-
}): Promise<SuiteValidationReport>;
|
|
21
|
-
export declare function formatValidationReport(report: SuiteValidationReport): string;
|
|
22
|
-
/** Scenario workspace wins over suite defaults. Caller HEAD when omit or `"."`. */
|
|
23
|
-
export declare function resolveScenarioWorkspaceRel(suite: Pick<AgentSuiteFile, "defaults">, scenario: Pick<AgentScenario, "workspace">): string | undefined;
|
|
24
|
-
//# sourceMappingURL=validate-suite.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validate-suite.d.ts","sourceRoot":"","sources":["../src/validate-suite.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACX,aAAa,EACb,cAAc,EAKd,MAAM,YAAY,CAAC;AAiEpB,MAAM,WAAW,oBAAoB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAspBD,kEAAkE;AAClE,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,cAAc,GACnB,oBAAoB,EAAE,CAsCxB;AAED,wBAAsB,kBAAkB,CACvC,UAAU,EAAE,MAAM,EAAE,EACpB,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,OAAO,CAAC,qBAAqB,CAAC,CAyHhC;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAY5E;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,EACvC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GACxC,MAAM,GAAG,SAAS,CAIpB"}
|
package/dist/validate-suite.js
DELETED
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
import { access, stat } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { isHostSlug, isKnownAgentHost, isRepoRelativeSkillPath, parseScenarioWorkspace, skillManifestRelPath, skillPathsFromSetting, } from "@post-print/agent-harness";
|
|
4
|
-
import { applyCompareArm, parseCompareArmId, resolveCompareArms } from "./compare-scenario.js";
|
|
5
|
-
import { loadSuiteFile } from "./load-suite.js";
|
|
6
|
-
const REPLAY_DEPRECATION = "Replay-based testing is deprecated and no longer supported; use Cursor, Claude, or OpenAI.";
|
|
7
|
-
function isKnownHostId(host) {
|
|
8
|
-
return isKnownAgentHost(host);
|
|
9
|
-
}
|
|
10
|
-
function unknownHostMessage(host) {
|
|
11
|
-
if (host === "replay") {
|
|
12
|
-
return REPLAY_DEPRECATION;
|
|
13
|
-
}
|
|
14
|
-
if (typeof host === "string" && isHostSlug(host)) {
|
|
15
|
-
return `unknown host "${host}". Register it with registerHostAdapter() or --adapter`;
|
|
16
|
-
}
|
|
17
|
-
return `host must be a lowercase slug (cursor|claude|openai or a registered adapter), got ${JSON.stringify(host)}`;
|
|
18
|
-
}
|
|
19
|
-
const VALID_PROFILES = new Set(["shared", "cursor", "claude", "skeleton"]);
|
|
20
|
-
const VALID_CONTEXT_MODES = new Set(["host-native", "harness-preamble"]);
|
|
21
|
-
function injectsSkillCatalog(value) {
|
|
22
|
-
if (value === undefined || value === "none")
|
|
23
|
-
return false;
|
|
24
|
-
if (Array.isArray(value))
|
|
25
|
-
return value.length > 0;
|
|
26
|
-
return value.mode !== "none";
|
|
27
|
-
}
|
|
28
|
-
function isValidSkillSetting(value) {
|
|
29
|
-
if (value === "none") {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
if (Array.isArray(value)) {
|
|
33
|
-
return (value.length > 0 &&
|
|
34
|
-
value.every((item) => typeof item === "string" && isRepoRelativeSkillPath(item)));
|
|
35
|
-
}
|
|
36
|
-
if (!value || typeof value !== "object") {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const record = value;
|
|
40
|
-
if (record.mode !== undefined &&
|
|
41
|
-
record.mode !== "none" &&
|
|
42
|
-
record.mode !== "catalog" &&
|
|
43
|
-
record.mode !== "full") {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (record.mode === "none") {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
return (Array.isArray(record.include) &&
|
|
50
|
-
record.include.length > 0 &&
|
|
51
|
-
record.include.every((item) => typeof item === "string" && isRepoRelativeSkillPath(item)));
|
|
52
|
-
}
|
|
53
|
-
const VALID_TIERS = new Set(["low", "medium", "high"]);
|
|
54
|
-
const VALID_REVIEW_DEPTHS = new Set([
|
|
55
|
-
"quick",
|
|
56
|
-
"standard",
|
|
57
|
-
"thorough",
|
|
58
|
-
"full",
|
|
59
|
-
]);
|
|
60
|
-
function pushIssue(issues, suitePath, field, message, scenario) {
|
|
61
|
-
issues.push({ suitePath, scenario, field, message });
|
|
62
|
-
}
|
|
63
|
-
function validateRubric(issues, suitePath, scenarioName, rubric, fieldPrefix = "rubric") {
|
|
64
|
-
if (rubric.tier !== undefined && !VALID_TIERS.has(rubric.tier)) {
|
|
65
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.tier`, `tier must be low|medium|high, got ${JSON.stringify(rubric.tier)}`, scenarioName);
|
|
66
|
-
}
|
|
67
|
-
if (rubric.reviewDepth !== undefined && !VALID_REVIEW_DEPTHS.has(rubric.reviewDepth)) {
|
|
68
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.reviewDepth`, `reviewDepth must be quick|standard|thorough|full, got ${JSON.stringify(rubric.reviewDepth)}`, scenarioName);
|
|
69
|
-
}
|
|
70
|
-
for (const key of [
|
|
71
|
-
"must",
|
|
72
|
-
"mustNot",
|
|
73
|
-
"mustRun",
|
|
74
|
-
"mustRunSuccessfully",
|
|
75
|
-
"allowedCommands",
|
|
76
|
-
"mustCallTool",
|
|
77
|
-
"mustCallToolsInOrder",
|
|
78
|
-
"mustNotCallTool",
|
|
79
|
-
"mustReadPath",
|
|
80
|
-
"mustNotReadPath",
|
|
81
|
-
]) {
|
|
82
|
-
const value = rubric[key];
|
|
83
|
-
if (value !== undefined && !Array.isArray(value)) {
|
|
84
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.${key}`, `${key} must be an array of strings`, scenarioName);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
for (const key of ["mustInvokeSkill", "mustNotInvokeSkill"]) {
|
|
88
|
-
const value = rubric[key];
|
|
89
|
-
if (value !== undefined && !Array.isArray(value)) {
|
|
90
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.${key}`, `${key} must be an array of skill folder names`, scenarioName);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (rubric.judge !== undefined) {
|
|
94
|
-
if (!Array.isArray(rubric.judge)) {
|
|
95
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.judge`, "judge must be an array of strings or { question } objects", scenarioName);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
for (const [index, item] of rubric.judge.entries()) {
|
|
99
|
-
if (typeof item === "string") {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (typeof item !== "object" || item === null || typeof item.question !== "string") {
|
|
103
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.judge[${index}]`, "judge item must be a string or { question: string }", scenarioName);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function validateOptionalDescription(issues, suitePath, field, value, scenarioName) {
|
|
110
|
-
if (value === undefined) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
114
|
-
pushIssue(issues, suitePath, field, "description must be a non-empty string", scenarioName);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function validateScenario(issues, suitePath, scenario) {
|
|
118
|
-
validateOptionalDescription(issues, suitePath, "description", scenario.description, scenario.name);
|
|
119
|
-
if (scenario.host !== undefined && !isKnownHostId(scenario.host)) {
|
|
120
|
-
pushIssue(issues, suitePath, "host", unknownHostMessage(scenario.host), scenario.name);
|
|
121
|
-
}
|
|
122
|
-
if (scenario.contextMode !== undefined && !VALID_CONTEXT_MODES.has(scenario.contextMode)) {
|
|
123
|
-
pushIssue(issues, suitePath, "contextMode", `contextMode must be host-native|harness-preamble, got ${JSON.stringify(scenario.contextMode)}`, scenario.name);
|
|
124
|
-
}
|
|
125
|
-
if ("replayTrace" in scenario) {
|
|
126
|
-
pushIssue(issues, suitePath, "replayTrace", REPLAY_DEPRECATION, scenario.name);
|
|
127
|
-
}
|
|
128
|
-
if (scenario.profile !== undefined && !VALID_PROFILES.has(scenario.profile)) {
|
|
129
|
-
pushIssue(issues, suitePath, "profile", `profile must be shared|cursor|claude|skeleton, got ${JSON.stringify(scenario.profile)}`, scenario.name);
|
|
130
|
-
}
|
|
131
|
-
if (scenario.contextSources !== undefined && !Array.isArray(scenario.contextSources)) {
|
|
132
|
-
pushIssue(issues, suitePath, "contextSources", "contextSources must be an array of strings", scenario.name);
|
|
133
|
-
}
|
|
134
|
-
if (scenario.skills !== undefined && !isValidSkillSetting(scenario.skills)) {
|
|
135
|
-
pushIssue(issues, suitePath, "skills", `skills must be "none" or repo-relative SKILL.md / skill-folder paths, got ${JSON.stringify(scenario.skills)}`, scenario.name);
|
|
136
|
-
}
|
|
137
|
-
if (scenario.workspace !== undefined) {
|
|
138
|
-
const parsed = parseScenarioWorkspace(scenario.workspace);
|
|
139
|
-
if (!parsed.ok) {
|
|
140
|
-
pushIssue(issues, suitePath, "workspace", parsed.message, scenario.name);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (scenario.allowUserSkills !== undefined && typeof scenario.allowUserSkills !== "boolean") {
|
|
144
|
-
pushIssue(issues, suitePath, "allowUserSkills", `allowUserSkills must be a boolean, got ${JSON.stringify(scenario.allowUserSkills)}`, scenario.name);
|
|
145
|
-
}
|
|
146
|
-
if (scenario.networkAccess !== undefined && typeof scenario.networkAccess !== "boolean") {
|
|
147
|
-
pushIssue(issues, suitePath, "networkAccess", `networkAccess must be a boolean, got ${JSON.stringify(scenario.networkAccess)}`, scenario.name);
|
|
148
|
-
}
|
|
149
|
-
if (scenario.seedStageOnly && !scenario.seedPatch) {
|
|
150
|
-
pushIssue(issues, suitePath, "seedStageOnly", "seedStageOnly requires seedPatch", scenario.name);
|
|
151
|
-
}
|
|
152
|
-
validateCompare(issues, suitePath, scenario);
|
|
153
|
-
validateRubric(issues, suitePath, scenario.name, scenario.rubric);
|
|
154
|
-
}
|
|
155
|
-
function validateCompareArmFields(issues, suitePath, scenarioName, fieldPrefix, arm) {
|
|
156
|
-
if (arm.label !== undefined && (typeof arm.label !== "string" || !arm.label.trim())) {
|
|
157
|
-
pushIssue(issues, suitePath, fieldPrefix, "label must be a non-empty string", scenarioName);
|
|
158
|
-
}
|
|
159
|
-
if (typeof arm.description !== "string" || !arm.description.trim()) {
|
|
160
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.description`, "description is required", scenarioName);
|
|
161
|
-
}
|
|
162
|
-
if (arm.prompt !== undefined && typeof arm.prompt !== "string") {
|
|
163
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.prompt`, "prompt must be a string", scenarioName);
|
|
164
|
-
}
|
|
165
|
-
if (arm.host !== undefined && !isKnownHostId(arm.host)) {
|
|
166
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.host`, unknownHostMessage(arm.host), scenarioName);
|
|
167
|
-
}
|
|
168
|
-
if (arm.contextMode !== undefined && !VALID_CONTEXT_MODES.has(arm.contextMode)) {
|
|
169
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.contextMode`, `contextMode must be host-native|harness-preamble, got ${JSON.stringify(arm.contextMode)}`, scenarioName);
|
|
170
|
-
}
|
|
171
|
-
if (arm.workspace !== undefined) {
|
|
172
|
-
const parsed = parseScenarioWorkspace(arm.workspace);
|
|
173
|
-
if (!parsed.ok) {
|
|
174
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.workspace`, parsed.message, scenarioName);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (arm.allowUserSkills !== undefined && typeof arm.allowUserSkills !== "boolean") {
|
|
178
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.allowUserSkills`, `allowUserSkills must be a boolean, got ${JSON.stringify(arm.allowUserSkills)}`, scenarioName);
|
|
179
|
-
}
|
|
180
|
-
if (arm.seedStageOnly && !arm.seedPatch) {
|
|
181
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.seedStageOnly`, "seedStageOnly requires seedPatch", scenarioName);
|
|
182
|
-
}
|
|
183
|
-
if (arm.skills !== undefined && !isValidSkillSetting(arm.skills)) {
|
|
184
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.skills`, `skills must be "none" or repo-relative SKILL.md / skill-folder paths, got ${JSON.stringify(arm.skills)}`, scenarioName);
|
|
185
|
-
}
|
|
186
|
-
if (arm.rubric !== undefined) {
|
|
187
|
-
if (typeof arm.rubric !== "object" || arm.rubric === null) {
|
|
188
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.rubric`, "rubric must be an object", scenarioName);
|
|
189
|
-
}
|
|
190
|
-
else if (arm.rubric.judge !== undefined) {
|
|
191
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.rubric.judge`, "arm rubric cannot include judge. Put judge on the scenario", scenarioName);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
validateRubric(issues, suitePath, scenarioName, arm.rubric, `${fieldPrefix}.rubric`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function validateCompare(issues, suitePath, scenario) {
|
|
199
|
-
if (scenario.compare === undefined) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (typeof scenario.compare !== "object" || scenario.compare === null) {
|
|
203
|
-
pushIssue(issues, suitePath, "compare", "compare must be an object with a and b, or arms", scenario.name);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const compare = scenario.compare;
|
|
207
|
-
const hasLegacyArms = compare.a !== undefined || compare.b !== undefined;
|
|
208
|
-
const hasNamedArms = compare.arms !== undefined;
|
|
209
|
-
if (hasLegacyArms && hasNamedArms) {
|
|
210
|
-
pushIssue(issues, suitePath, "compare", "compare must use a and b, or arms, not both", scenario.name);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (hasNamedArms) {
|
|
214
|
-
validateNamedCompareArms(issues, suitePath, scenario.name, compare);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
if (typeof compare.a !== "object" || compare.a === null) {
|
|
218
|
-
pushIssue(issues, suitePath, "compare.a", "compare.a must be an object", scenario.name);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
validateCompareArmFields(issues, suitePath, scenario.name, "compare.a", compare.a);
|
|
222
|
-
}
|
|
223
|
-
if (typeof compare.b !== "object" || compare.b === null) {
|
|
224
|
-
pushIssue(issues, suitePath, "compare.b", "compare.b must be an object", scenario.name);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
validateCompareArmFields(issues, suitePath, scenario.name, "compare.b", compare.b);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const armIds = resolveCompareArms(compare)
|
|
231
|
-
.map((entry) => entry.id)
|
|
232
|
-
.filter(Boolean);
|
|
233
|
-
const raw = compare;
|
|
234
|
-
for (const removed of ["faster", "cheaper"]) {
|
|
235
|
-
if (removed in raw)
|
|
236
|
-
pushIssue(issues, suitePath, `compare.${removed}`, `compare.${removed} was removed. Use compare.gates`, scenario.name);
|
|
237
|
-
}
|
|
238
|
-
validateCompareGates(issues, suitePath, scenario.name, compare.gates, armIds);
|
|
239
|
-
if (compare.judgeMetrics !== undefined) {
|
|
240
|
-
if (!Array.isArray(compare.judgeMetrics) || compare.judgeMetrics.length === 0) {
|
|
241
|
-
pushIssue(issues, suitePath, "compare.judgeMetrics", "judgeMetrics must be a non-empty array", scenario.name);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
const ids = new Set();
|
|
245
|
-
for (const [index, metric] of compare.judgeMetrics.entries()) {
|
|
246
|
-
if (!metric ||
|
|
247
|
-
typeof metric.id !== "string" ||
|
|
248
|
-
!metric.id.trim() ||
|
|
249
|
-
typeof metric.question !== "string" ||
|
|
250
|
-
!metric.question.trim())
|
|
251
|
-
pushIssue(issues, suitePath, `compare.judgeMetrics[${index}]`, "judge metric needs an id and a question", scenario.name);
|
|
252
|
-
else if (ids.has(metric.id))
|
|
253
|
-
pushIssue(issues, suitePath, `compare.judgeMetrics[${index}].id`, "judge metric id must be unique", scenario.name);
|
|
254
|
-
else
|
|
255
|
-
ids.add(metric.id);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const judgeMetricIds = new Set(compare.judgeMetrics?.map((metric) => metric.id) ?? []);
|
|
260
|
-
for (const [index, gate] of (compare.gates ?? []).entries()) {
|
|
261
|
-
if (typeof gate.metric === "string" &&
|
|
262
|
-
gate.metric.startsWith("judge:") &&
|
|
263
|
-
!judgeMetricIds.has(gate.metric.slice("judge:".length))) {
|
|
264
|
-
pushIssue(issues, suitePath, `compare.gates[${index}].metric`, "judge gate must name a compare.judgeMetrics id", scenario.name);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
function validateNamedCompareArms(issues, suitePath, scenarioName, compare) {
|
|
269
|
-
if (!Array.isArray(compare.arms) || compare.arms.length < 2) {
|
|
270
|
-
pushIssue(issues, suitePath, "compare.arms", "compare.arms must be an array of two or more arms", scenarioName);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const seen = new Set();
|
|
274
|
-
for (const [index, arm] of compare.arms.entries()) {
|
|
275
|
-
const fieldPrefix = `compare.arms[${index}]`;
|
|
276
|
-
if (typeof arm !== "object" || arm === null) {
|
|
277
|
-
pushIssue(issues, suitePath, fieldPrefix, "arm must be an object", scenarioName);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const id = parseCompareArmId(arm.id);
|
|
281
|
-
if (!id) {
|
|
282
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.id`, "id must be a lowercase slug such as skel-clean", scenarioName);
|
|
283
|
-
}
|
|
284
|
-
else if (seen.has(id)) {
|
|
285
|
-
pushIssue(issues, suitePath, `${fieldPrefix}.id`, `duplicate arm id ${id}`, scenarioName);
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
seen.add(id);
|
|
289
|
-
}
|
|
290
|
-
validateCompareArmFields(issues, suitePath, scenarioName, fieldPrefix, arm);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
function validateCompareGates(issues, suitePath, scenarioName, value, armIds) {
|
|
294
|
-
if (value === undefined)
|
|
295
|
-
return;
|
|
296
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
297
|
-
pushIssue(issues, suitePath, "compare.gates", "gates must be a non-empty array", scenarioName);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const metrics = new Set(["outcome", "turns", "tokens", "tools", "durationMs"]);
|
|
301
|
-
const operators = new Set(["equal", "lessThan", "atMost", "atLeast", "greaterThan"]);
|
|
302
|
-
for (const [index, item] of value.entries()) {
|
|
303
|
-
const field = `compare.gates[${index}]`;
|
|
304
|
-
if (!item || typeof item !== "object") {
|
|
305
|
-
pushIssue(issues, suitePath, field, "gate must be an object", scenarioName);
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
const gate = item;
|
|
309
|
-
if (typeof gate.metric !== "string" ||
|
|
310
|
-
(!metrics.has(gate.metric) && !gate.metric.startsWith("judge:")))
|
|
311
|
-
pushIssue(issues, suitePath, `${field}.metric`, "metric must name a supported comparison value", scenarioName);
|
|
312
|
-
if ("winner" in gate) {
|
|
313
|
-
if (!armIds.includes(gate.winner))
|
|
314
|
-
pushIssue(issues, suitePath, `${field}.winner`, `must be one of ${armIds.join(", ")}`, scenarioName);
|
|
315
|
-
if (!armIds.includes(gate.loser))
|
|
316
|
-
pushIssue(issues, suitePath, `${field}.loser`, `must be one of ${armIds.join(", ")}`, scenarioName);
|
|
317
|
-
if (gate.winner === gate.loser)
|
|
318
|
-
pushIssue(issues, suitePath, field, "winner and loser must be different arms", scenarioName);
|
|
319
|
-
}
|
|
320
|
-
else if ("arm" in gate) {
|
|
321
|
-
if (!armIds.includes(gate.arm))
|
|
322
|
-
pushIssue(issues, suitePath, `${field}.arm`, `must be one of ${armIds.join(", ")}`, scenarioName);
|
|
323
|
-
if (!operators.has(gate.operator))
|
|
324
|
-
pushIssue(issues, suitePath, `${field}.operator`, "operator is not supported", scenarioName);
|
|
325
|
-
if (typeof gate.value !== "number" && gate.value !== "pass" && gate.value !== "fail")
|
|
326
|
-
pushIssue(issues, suitePath, `${field}.value`, "value must be a number, pass, or fail", scenarioName);
|
|
327
|
-
}
|
|
328
|
-
else
|
|
329
|
-
pushIssue(issues, suitePath, field, "gate needs winner and loser, or arm, operator, and value", scenarioName);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
function validateHosts(issues, suitePath, suite) {
|
|
333
|
-
if (suite.hosts === undefined) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (!Array.isArray(suite.hosts) || suite.hosts.length === 0) {
|
|
337
|
-
pushIssue(issues, suitePath, "hosts", "hosts must be a non-empty array of host slugs");
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const seen = new Set();
|
|
341
|
-
for (const host of suite.hosts) {
|
|
342
|
-
if (!isKnownHostId(host)) {
|
|
343
|
-
pushIssue(issues, suitePath, "hosts", unknownHostMessage(host));
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (seen.has(host)) {
|
|
347
|
-
pushIssue(issues, suitePath, "hosts", `duplicate host ${host}`);
|
|
348
|
-
}
|
|
349
|
-
seen.add(host);
|
|
350
|
-
}
|
|
351
|
-
if (suite.defaults?.host !== undefined &&
|
|
352
|
-
isKnownHostId(suite.defaults.host) &&
|
|
353
|
-
!suite.hosts.includes(suite.defaults.host)) {
|
|
354
|
-
pushIssue(issues, suitePath, "defaults.host", `defaults.host ${suite.defaults.host} must be listed in hosts`);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function validateDefaults(issues, suitePath, suite) {
|
|
358
|
-
const defaults = suite.defaults;
|
|
359
|
-
if (!defaults) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (defaults.host !== undefined && !isKnownHostId(defaults.host)) {
|
|
363
|
-
pushIssue(issues, suitePath, "defaults.host", unknownHostMessage(defaults.host));
|
|
364
|
-
}
|
|
365
|
-
if (defaults.contextMode !== undefined && !VALID_CONTEXT_MODES.has(defaults.contextMode)) {
|
|
366
|
-
pushIssue(issues, suitePath, "defaults.contextMode", `contextMode must be host-native|harness-preamble, got ${JSON.stringify(defaults.contextMode)}`);
|
|
367
|
-
}
|
|
368
|
-
if (defaults.profile !== undefined && !VALID_PROFILES.has(defaults.profile)) {
|
|
369
|
-
pushIssue(issues, suitePath, "defaults.profile", `profile must be shared|cursor|claude|skeleton, got ${JSON.stringify(defaults.profile)}`);
|
|
370
|
-
}
|
|
371
|
-
if (defaults.contextSources !== undefined && !Array.isArray(defaults.contextSources)) {
|
|
372
|
-
pushIssue(issues, suitePath, "defaults.contextSources", "contextSources must be an array of strings");
|
|
373
|
-
}
|
|
374
|
-
if (defaults.workspace !== undefined) {
|
|
375
|
-
const parsed = parseScenarioWorkspace(defaults.workspace);
|
|
376
|
-
if (!parsed.ok) {
|
|
377
|
-
pushIssue(issues, suitePath, "defaults.workspace", parsed.message);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (defaults.skills !== undefined && !isValidSkillSetting(defaults.skills)) {
|
|
381
|
-
pushIssue(issues, suitePath, "defaults.skills", `skills must be "none" or repo-relative SKILL.md / skill-folder paths, got ${JSON.stringify(defaults.skills)}`);
|
|
382
|
-
}
|
|
383
|
-
if (defaults.allowUserSkills !== undefined && typeof defaults.allowUserSkills !== "boolean") {
|
|
384
|
-
pushIssue(issues, suitePath, "defaults.allowUserSkills", `allowUserSkills must be a boolean, got ${JSON.stringify(defaults.allowUserSkills)}`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
function mcpScriptPaths(suite, scenario) {
|
|
388
|
-
const servers = { ...suite.defaults?.mcpServers, ...scenario.mcpServers };
|
|
389
|
-
const paths = [];
|
|
390
|
-
for (const config of Object.values(servers)) {
|
|
391
|
-
if (!("command" in config) || !config.args) {
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
for (const arg of config.args) {
|
|
395
|
-
if (arg.startsWith("-")) {
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
if (/\.(mjs|cjs|js)$/.test(arg) || arg.includes("/")) {
|
|
399
|
-
paths.push(arg);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return paths;
|
|
404
|
-
}
|
|
405
|
-
/** Semantic validation beyond structural loadSuiteFile checks. */
|
|
406
|
-
export function validateSuiteFile(suitePath, suite) {
|
|
407
|
-
const issues = [];
|
|
408
|
-
validateHosts(issues, suitePath, suite);
|
|
409
|
-
validateDefaults(issues, suitePath, suite);
|
|
410
|
-
for (const scenario of suite.scenarios) {
|
|
411
|
-
validateScenario(issues, suitePath, scenario);
|
|
412
|
-
const effectiveScenarios = scenario.compare
|
|
413
|
-
? resolveCompareArms(scenario.compare).map((entry) => applyCompareArm(scenario, entry.id))
|
|
414
|
-
: [scenario];
|
|
415
|
-
for (const effective of effectiveScenarios) {
|
|
416
|
-
const mode = effective.contextMode ?? suite.defaults?.contextMode ?? "harness-preamble";
|
|
417
|
-
if (mode !== "host-native")
|
|
418
|
-
continue;
|
|
419
|
-
const contextSources = [
|
|
420
|
-
...(suite.defaults?.contextSources ?? []),
|
|
421
|
-
...(effective.contextSources ?? []),
|
|
422
|
-
].filter((source) => typeof source === "string" && source.trim().length > 0);
|
|
423
|
-
if (contextSources.length > 0) {
|
|
424
|
-
pushIssue(issues, suitePath, "contextMode", "host-native contextMode cannot use contextSources because they are injected into the prompt", scenario.name);
|
|
425
|
-
}
|
|
426
|
-
const skills = effective.skills ?? suite.defaults?.skills;
|
|
427
|
-
if (injectsSkillCatalog(skills)) {
|
|
428
|
-
pushIssue(issues, suitePath, "contextMode", "host-native contextMode cannot use skills because agent-test injects a synthetic catalog", scenario.name);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return issues;
|
|
433
|
-
}
|
|
434
|
-
export async function validateSuitePaths(suitePaths, options) {
|
|
435
|
-
const issues = [];
|
|
436
|
-
let scenariosChecked = 0;
|
|
437
|
-
const repoRoot = options?.repoRoot;
|
|
438
|
-
for (const suitePath of suitePaths) {
|
|
439
|
-
let suite;
|
|
440
|
-
try {
|
|
441
|
-
suite = await loadSuiteFile(suitePath, { rubricsDir: options?.rubricsDir });
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
pushIssue(issues, suitePath, "file", error instanceof Error ? error.message : "failed to load suite file");
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
issues.push(...validateSuiteFile(suitePath, suite));
|
|
448
|
-
scenariosChecked += suite.scenarios.length;
|
|
449
|
-
if (options?.validatePaths && repoRoot) {
|
|
450
|
-
for (const scenario of suite.scenarios) {
|
|
451
|
-
const workspaceChecks = scenario.compare
|
|
452
|
-
? resolveCompareArms(scenario.compare).map((entry) => ({
|
|
453
|
-
field: `compare.${entry.id}.workspace`,
|
|
454
|
-
rel: resolveScenarioWorkspaceRel(suite, applyCompareArm(scenario, entry.id)),
|
|
455
|
-
}))
|
|
456
|
-
: [{ field: "workspace", rel: resolveScenarioWorkspaceRel(suite, scenario) }];
|
|
457
|
-
for (const check of workspaceChecks) {
|
|
458
|
-
const workspaceRel = check.rel;
|
|
459
|
-
if (!workspaceRel) {
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
const workspacePath = resolve(repoRoot, workspaceRel);
|
|
463
|
-
try {
|
|
464
|
-
const info = await stat(workspacePath);
|
|
465
|
-
if (!info.isDirectory()) {
|
|
466
|
-
pushIssue(issues, suitePath, check.field, `workspace must be a directory: ${workspaceRel}`, scenario.name);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
catch {
|
|
470
|
-
pushIssue(issues, suitePath, check.field, `workspace not found: ${workspaceRel}`, scenario.name);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
const skillTargets = scenario.compare
|
|
474
|
-
? resolveCompareArms(scenario.compare).map((entry) => ({
|
|
475
|
-
field: `compare.${entry.id}.skills`,
|
|
476
|
-
scenario: applyCompareArm(scenario, entry.id),
|
|
477
|
-
}))
|
|
478
|
-
: [{ field: "skills", scenario }];
|
|
479
|
-
for (const target of skillTargets) {
|
|
480
|
-
const skills = target.scenario.skills ?? suite.defaults?.skills;
|
|
481
|
-
if (skills === undefined || !isValidSkillSetting(skills)) {
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
const skillWorkspaceRel = resolveScenarioWorkspaceRel(suite, target.scenario);
|
|
485
|
-
const skillRoot = skillWorkspaceRel ? resolve(repoRoot, skillWorkspaceRel) : repoRoot;
|
|
486
|
-
for (const rel of skillPathsFromSetting(skills)) {
|
|
487
|
-
const manifest = skillManifestRelPath(rel);
|
|
488
|
-
try {
|
|
489
|
-
await access(resolve(skillRoot, manifest));
|
|
490
|
-
}
|
|
491
|
-
catch {
|
|
492
|
-
pushIssue(issues, suitePath, target.field, `skill path not found: ${manifest}`, scenario.name);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
for (const scriptPath of mcpScriptPaths(suite, scenario)) {
|
|
497
|
-
try {
|
|
498
|
-
await access(resolve(repoRoot, scriptPath));
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
pushIssue(issues, suitePath, "mcpServers", `MCP script not found: ${scriptPath}`, scenario.name);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
if (scenario.seedPatch) {
|
|
505
|
-
const patchPath = resolve(repoRoot, scenario.seedPatch);
|
|
506
|
-
try {
|
|
507
|
-
await access(patchPath);
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
pushIssue(issues, suitePath, "seedPatch", `seed patch not found: ${scenario.seedPatch}`, scenario.name);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return {
|
|
517
|
-
ok: issues.length === 0,
|
|
518
|
-
issues,
|
|
519
|
-
suitesChecked: suitePaths.length,
|
|
520
|
-
scenariosChecked,
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
export function formatValidationReport(report) {
|
|
524
|
-
if (report.ok) {
|
|
525
|
-
return `Validated ${report.suitesChecked} suite(s), ${report.scenariosChecked} scenario(s): OK`;
|
|
526
|
-
}
|
|
527
|
-
const lines = report.issues.map((issue) => {
|
|
528
|
-
const where = issue.scenario ? `${issue.suitePath} · ${issue.scenario}` : issue.suitePath;
|
|
529
|
-
return `${where} · ${issue.field}: ${issue.message}`;
|
|
530
|
-
});
|
|
531
|
-
return [
|
|
532
|
-
`Validation failed (${report.issues.length} issue(s)):`,
|
|
533
|
-
...lines.map((l) => ` - ${l}`),
|
|
534
|
-
].join("\n");
|
|
535
|
-
}
|
|
536
|
-
/** Scenario workspace wins over suite defaults. Caller HEAD when omit or `"."`. */
|
|
537
|
-
export function resolveScenarioWorkspaceRel(suite, scenario) {
|
|
538
|
-
const raw = scenario.workspace !== undefined ? scenario.workspace : suite.defaults?.workspace;
|
|
539
|
-
const parsed = parseScenarioWorkspace(raw);
|
|
540
|
-
return parsed.ok ? parsed.rel : undefined;
|
|
541
|
-
}
|
|
542
|
-
//# sourceMappingURL=validate-suite.js.map
|