vigiles 33.0.0 → 33.2.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/dist/adapter-conformance.js +14 -10
- package/dist/adapter.d.ts +2 -0
- package/dist/adapters/claude-code/layout.js +3 -0
- package/dist/adapters/claude-code/output-style.d.ts +3 -0
- package/dist/adapters/claude-code/output-style.js +81 -0
- package/dist/adapters/claude-code/replies.d.ts +14 -0
- package/dist/adapters/claude-code/replies.js +78 -0
- package/dist/audit-report.d.ts +5 -0
- package/dist/audit-report.js +29 -20
- package/dist/audit-report.template.html +25 -25
- package/dist/audit-score.js +2 -2
- package/dist/check.d.ts +20 -0
- package/dist/check.js +87 -2
- package/dist/cli-main.js +25 -13
- package/dist/core/config-schema.d.ts +9 -0
- package/dist/core/config-schema.js +1 -0
- package/dist/core/eval-driver.d.ts +10 -0
- package/dist/core/frame.d.ts +1 -1
- package/dist/core/frontmatter-read.d.ts +31 -0
- package/dist/core/frontmatter-read.js +4 -2
- package/dist/core/harness-driver.d.ts +2 -0
- package/dist/core/layout.d.ts +7 -0
- package/dist/core/output-style.d.ts +84 -0
- package/dist/core/output-style.js +90 -0
- package/dist/core/rule-meta.d.ts +1 -1
- package/dist/core/rule-meta.js +8 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/coverage-evidence.d.ts +42 -0
- package/dist/coverage-evidence.js +39 -0
- package/dist/eval.d.ts +26 -3
- package/dist/eval.js +54 -5
- package/dist/harness-test.d.ts +30 -1
- package/dist/harness-test.js +65 -15
- package/dist/optimize.js +1 -5
- package/dist/output-style-arms.d.ts +27 -0
- package/dist/output-style-arms.js +46 -0
- package/dist/scan-files.js +1 -1
- package/dist/scan.d.ts +6 -0
- package/dist/scan.js +14 -4
- package/dist/score-core.d.ts +3 -0
- package/dist/score-core.js +5 -1
- package/dist/setup-plan.d.ts +1 -1
- package/dist/setup-plan.js +1 -0
- package/dist/test-coverage-files.d.ts +3 -1
- package/dist/test-coverage-files.js +20 -6
- package/dist/test-coverage.d.ts +18 -1
- package/dist/test-coverage.js +64 -14
- package/dist/test.d.ts +3 -1
- package/dist/test.js +8 -2
- package/dist/vigilesrc.schema.json +134 -0
- package/package.json +6 -3
- package/packages/eslint-config/README.md +17 -0
- package/packages/eslint-config/index.mjs +344 -0
|
@@ -12,6 +12,9 @@ exports.matchesSurfaceGlob = matchesSurfaceGlob;
|
|
|
12
12
|
exports.countEvidence = countEvidence;
|
|
13
13
|
exports.formatEvidence = formatEvidence;
|
|
14
14
|
exports.declaredSurfaceName = declaredSurfaceName;
|
|
15
|
+
exports.outputStyleSurface = outputStyleSurface;
|
|
16
|
+
exports.outputStyleCount = outputStyleCount;
|
|
17
|
+
exports.inventoryCounts = inventoryCounts;
|
|
15
18
|
/**
|
|
16
19
|
* HOW a surface was decided to be covered — the provenance of every coverage
|
|
17
20
|
* decision.
|
|
@@ -390,4 +393,40 @@ function declaredSurfaceName(content) {
|
|
|
390
393
|
const name = (0, frontmatter_read_js_1.frontmatterScalar)((0, frontmatter_read_js_1.readFrontmatter)(content), "name");
|
|
391
394
|
return name !== undefined && name.trim() ? name.trim() : null;
|
|
392
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* One output style as a coverage surface — shared by the disk detector and the
|
|
398
|
+
* browser twin, so the two name and exempt a style the same way.
|
|
399
|
+
*
|
|
400
|
+
* Named by FILE (`status-block.md` → `status-block`), not by the name a
|
|
401
|
+
* harness selects it by: the surface name is what a colocated test is spelled
|
|
402
|
+
* after, and a declared name may hold spaces or colons no file name should.
|
|
403
|
+
*/
|
|
404
|
+
function outputStyleSurface(path, content) {
|
|
405
|
+
return {
|
|
406
|
+
kind: "output-style",
|
|
407
|
+
path,
|
|
408
|
+
name: (0, posix_path_js_1.basename)(path).replace(/\.[^.]+$/, ""),
|
|
409
|
+
tokens: [path],
|
|
410
|
+
ignored: content.includes("vigiles:ignore-test"),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* How many output styles a scan found, or that the harness has none. Shared by
|
|
415
|
+
* the disk and browser scan engines, so the inventory reads the one discovery
|
|
416
|
+
* pass both already ran. Exempt styles (`vigiles:ignore-test`) are counted: the
|
|
417
|
+
* marker waives the test, not the style, and a style-only repository with an
|
|
418
|
+
* exempt style is not an empty machine.
|
|
419
|
+
*/
|
|
420
|
+
function outputStyleCount(layouts, tier, exempt) {
|
|
421
|
+
if (layouts.every((l) => l.outputStyles === undefined))
|
|
422
|
+
return "not-supported";
|
|
423
|
+
return [...tier.covered, ...tier.untested, ...exempt].filter((s) => s.kind === "output-style").length;
|
|
424
|
+
}
|
|
425
|
+
/** The inventory counts both scan engines report the same way. */
|
|
426
|
+
function inventoryCounts(files, isCommand, layouts, coverage) {
|
|
427
|
+
return {
|
|
428
|
+
commands: Object.keys(files).filter(isCommand).length,
|
|
429
|
+
outputStyles: outputStyleCount(layouts, coverage.harness, coverage.exemptSurfaces),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
393
432
|
//# sourceMappingURL=coverage-evidence.js.map
|
package/dist/eval.d.ts
CHANGED
|
@@ -119,7 +119,11 @@ export interface EvalSpec<M extends Metrics> {
|
|
|
119
119
|
* the cache, and never read from an env var. Omit for the harness default.
|
|
120
120
|
*/
|
|
121
121
|
readonly effort?: string | number;
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Tools pre-approved for the run (`--allowedTools`, a permission allowlist);
|
|
124
|
+
* the default tools stay available either way. `[]` approves none.
|
|
125
|
+
* Default: Read Edit Write Bash.
|
|
126
|
+
*/
|
|
123
127
|
readonly allowedTools?: readonly string[];
|
|
124
128
|
/** Per-run timeout ms. Default 240000. */
|
|
125
129
|
readonly timeoutMs?: number;
|
|
@@ -309,6 +313,22 @@ export declare function withEffortGuard(runner: AgentRunner): AgentRunner;
|
|
|
309
313
|
* inline there could not be asserted at all. Mirrors `buildCodexArgs`.
|
|
310
314
|
*/
|
|
311
315
|
export declare function buildAgentArgs(a: AgentRunArgs): string[];
|
|
316
|
+
/**
|
|
317
|
+
* Why a run never reached the model, or null when it did. A run that exited
|
|
318
|
+
* non-zero with no `assistant` event and no `result` event asked the model
|
|
319
|
+
* nothing — a usage error in the command line, a missing binary — and scored as
|
|
320
|
+
* a trial it reads as a miss on every check that needs output and a pass on
|
|
321
|
+
* every bound it cannot break. A run that reached the model and then broke is a
|
|
322
|
+
* scored outcome, not this. Rate-limited runs are left to the retry loop.
|
|
323
|
+
*/
|
|
324
|
+
export declare function startFailure(out: Readonly<RunOut>): string | null;
|
|
325
|
+
/**
|
|
326
|
+
* Wrap a runner so a run that never reached the model THROWS instead of being
|
|
327
|
+
* scored — the same choice, for the same reason, as {@link withEffortGuard}:
|
|
328
|
+
* the cause is usually deterministic, so every trial would fail it. Not `async`,
|
|
329
|
+
* for the reason that guard gives.
|
|
330
|
+
*/
|
|
331
|
+
export declare function withStartGuard(runner: AgentRunner): AgentRunner;
|
|
312
332
|
/**
|
|
313
333
|
* The real `claude`-spawning runner (composition root). Exported so other
|
|
314
334
|
* real-model entries (e.g. the `audit` trigger tier) bind the same runner.
|
|
@@ -391,7 +411,7 @@ export interface MeasureSpec {
|
|
|
391
411
|
* the cache, and never read from an env var. Omit for the harness default.
|
|
392
412
|
*/
|
|
393
413
|
readonly effort?: string | number;
|
|
394
|
-
/** Tools the
|
|
414
|
+
/** Tools pre-approved for the run (`--allowedTools`); `[]` approves none. */
|
|
395
415
|
readonly allowedTools?: readonly string[];
|
|
396
416
|
/** Per-run timeout ms. */
|
|
397
417
|
readonly timeoutMs?: number;
|
|
@@ -744,7 +764,10 @@ export interface TriggerRateSpec {
|
|
|
744
764
|
* false-negative recall. Default `"sonnet"`. Lower it deliberately for a cheap run.
|
|
745
765
|
*/
|
|
746
766
|
readonly minModel?: string;
|
|
747
|
-
/**
|
|
767
|
+
/**
|
|
768
|
+
* Tools pre-approved for the run (`--allowedTools`); `[]` approves none.
|
|
769
|
+
* Default: Read Edit Write Bash Skill.
|
|
770
|
+
*/
|
|
748
771
|
readonly allowedTools?: readonly string[];
|
|
749
772
|
/** Per-run timeout ms. Default 240000. */
|
|
750
773
|
readonly timeoutMs?: number;
|
package/dist/eval.js
CHANGED
|
@@ -6,6 +6,8 @@ exports.pinEffortEnv = pinEffortEnv;
|
|
|
6
6
|
exports.effortRejection = effortRejection;
|
|
7
7
|
exports.withEffortGuard = withEffortGuard;
|
|
8
8
|
exports.buildAgentArgs = buildAgentArgs;
|
|
9
|
+
exports.startFailure = startFailure;
|
|
10
|
+
exports.withStartGuard = withStartGuard;
|
|
9
11
|
exports.unregisteredSkillFiles = unregisteredSkillFiles;
|
|
10
12
|
exports.runEval = runEval;
|
|
11
13
|
exports.measureWith = measureWith;
|
|
@@ -215,10 +217,41 @@ function buildAgentArgs(a) {
|
|
|
215
217
|
? ["--plugin-dir", (0, node_path_1.resolve)(a.pluginDir)]
|
|
216
218
|
: []),
|
|
217
219
|
...(a.hasSettings ? ["--settings", "settings.json"] : []),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
+
// A permission allowlist: an empty one approves nothing, so the flag is
|
|
221
|
+
// left out (a bare `--allowedTools` exits before any model turn).
|
|
222
|
+
...(a.tools.length === 0 ? [] : ["--allowedTools", ...a.tools]),
|
|
220
223
|
];
|
|
221
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Why a run never reached the model, or null when it did. A run that exited
|
|
227
|
+
* non-zero with no `assistant` event and no `result` event asked the model
|
|
228
|
+
* nothing — a usage error in the command line, a missing binary — and scored as
|
|
229
|
+
* a trial it reads as a miss on every check that needs output and a pass on
|
|
230
|
+
* every bound it cannot break. A run that reached the model and then broke is a
|
|
231
|
+
* scored outcome, not this. Rate-limited runs are left to the retry loop.
|
|
232
|
+
*/
|
|
233
|
+
function startFailure(out) {
|
|
234
|
+
if (out.code === 0 || (0, harness_test_js_1.parseResultEvent)(out.stdout) !== null)
|
|
235
|
+
return null;
|
|
236
|
+
if ((0, harness_test_js_1.reachedModel)(out.stdout) || isRateLimited(out))
|
|
237
|
+
return null;
|
|
238
|
+
const said = (out.stderr ?? "").trim().split("\n").slice(-5).join("\n");
|
|
239
|
+
return `the harness exited ${String(out.code)} before calling the model: ${said || "(no stderr)"}`;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Wrap a runner so a run that never reached the model THROWS instead of being
|
|
243
|
+
* scored — the same choice, for the same reason, as {@link withEffortGuard}:
|
|
244
|
+
* the cause is usually deterministic, so every trial would fail it. Not `async`,
|
|
245
|
+
* for the reason that guard gives.
|
|
246
|
+
*/
|
|
247
|
+
function withStartGuard(runner) {
|
|
248
|
+
return (a) => runner(a).then((out) => {
|
|
249
|
+
const failure = startFailure(out);
|
|
250
|
+
if (failure !== null)
|
|
251
|
+
throw new Error(failure);
|
|
252
|
+
return out;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
222
255
|
/**
|
|
223
256
|
* The real `claude`-spawning runner (composition root). Exported so other
|
|
224
257
|
* real-model entries (e.g. the `audit` trigger tier) bind the same runner.
|
|
@@ -228,7 +261,7 @@ function buildAgentArgs(a) {
|
|
|
228
261
|
* not yet written, is covered by construction. Guarding each call site instead is
|
|
229
262
|
* the shape that left four of five compilers unprotected in #173.
|
|
230
263
|
*/
|
|
231
|
-
exports.spawnAgent = withEffortGuard(spawnAgentRaw);
|
|
264
|
+
exports.spawnAgent = withEffortGuard(withStartGuard(spawnAgentRaw));
|
|
232
265
|
/* v8 ignore start -- real claude subprocess; exercised by bench/, not the unit gate */
|
|
233
266
|
/** The unguarded spawn itself; wrapped by {@link spawnAgent}, never bound raw. */
|
|
234
267
|
function spawnAgentRaw(a) {
|
|
@@ -572,6 +605,7 @@ function parseClaudeRun(out) {
|
|
|
572
605
|
toolCalls: (0, harness_test_js_1.parseToolCalls)(out.stdout),
|
|
573
606
|
hooks: (0, harness_test_js_1.parseHooks)(out.stdout),
|
|
574
607
|
subagents: (0, harness_test_js_1.parseSubagents)(out.stdout),
|
|
608
|
+
replies: (0, harness_test_js_1.parseReplies)(out.stdout),
|
|
575
609
|
usage: usageFrom(result),
|
|
576
610
|
};
|
|
577
611
|
}
|
|
@@ -592,6 +626,7 @@ function makeContext(cwd, out, parse = parseClaudeRun) {
|
|
|
592
626
|
hooks: p.hooks,
|
|
593
627
|
output: p.output,
|
|
594
628
|
subagents: p.subagents,
|
|
629
|
+
...(p.replies === undefined ? {} : { replies: p.replies }),
|
|
595
630
|
usage: p.usage,
|
|
596
631
|
// The eval tier drives the real API (no mock between the agent and the model),
|
|
597
632
|
// so the requests can't be captured here — modelRequests is harness-tier only.
|
|
@@ -755,7 +790,10 @@ async function runWithCache(runArgs, keyParts, runner, cfg) {
|
|
|
755
790
|
trialIndex: keyParts.trialIndex,
|
|
756
791
|
});
|
|
757
792
|
const hit = (0, eval_cache_js_1.readCache)(cfg.cacheDir, key);
|
|
758
|
-
|
|
793
|
+
// A run that never reached the model is not replayed: the real runner now
|
|
794
|
+
// throws on one, and an entry cached before that guard existed would
|
|
795
|
+
// otherwise be scored as a trial on every later run.
|
|
796
|
+
if (hit && startFailure(hit.out) === null) {
|
|
759
797
|
(0, eval_cache_js_1.restoreDir)(runArgs.cwd, hit.files);
|
|
760
798
|
return hit.out;
|
|
761
799
|
}
|
|
@@ -1122,14 +1160,25 @@ async function runWithRetry(runArgs, runner, retries, baseMs) {
|
|
|
1122
1160
|
/** Map `worker` over `items` with at most `concurrency` in flight, order preserved. */
|
|
1123
1161
|
async function runPool(items, concurrency, worker) {
|
|
1124
1162
|
const results = new Array(items.length);
|
|
1163
|
+
// A worker that throws rejects the whole pool; the others must stop taking
|
|
1164
|
+
// items then, or they keep starting (and paying for) work nobody will read.
|
|
1165
|
+
const stop = new AbortController();
|
|
1125
1166
|
let next = 0;
|
|
1126
1167
|
const drain = async () => {
|
|
1127
1168
|
for (;;) {
|
|
1169
|
+
if (stop.signal.aborted)
|
|
1170
|
+
return;
|
|
1128
1171
|
const i = next++;
|
|
1129
1172
|
const item = items[i];
|
|
1130
1173
|
if (i >= items.length || item === undefined)
|
|
1131
1174
|
return;
|
|
1132
|
-
|
|
1175
|
+
try {
|
|
1176
|
+
results[i] = await worker(item);
|
|
1177
|
+
}
|
|
1178
|
+
catch (e) {
|
|
1179
|
+
stop.abort();
|
|
1180
|
+
throw e;
|
|
1181
|
+
}
|
|
1133
1182
|
}
|
|
1134
1183
|
};
|
|
1135
1184
|
const workers = Math.max(1, Math.min(concurrency, items.length || 1));
|
package/dist/harness-test.d.ts
CHANGED
|
@@ -2,10 +2,14 @@ import type { HarnessAdapter } from "./core/adapter.js";
|
|
|
2
2
|
import type { HarnessTestDriver, ToolCall, HookFire, ParsedRun, ModelTurn } from "./core/harness-driver.js";
|
|
3
3
|
import type { Trace, SubagentTrace } from "./core/eval-driver.js";
|
|
4
4
|
export type { Trace, SubagentTrace } from "./core/eval-driver.js";
|
|
5
|
+
import { parseReplies, reachedModel } from "./adapters/claude-code/replies.js";
|
|
6
|
+
/** Re-exported for the eval tier's parser, which reads the same stream. */
|
|
7
|
+
export { parseReplies, reachedModel };
|
|
5
8
|
import { type SandboxMode } from "./sandbox.js";
|
|
6
9
|
export { scriptModel } from "./mock-model.js";
|
|
7
10
|
export type { ModelTurn, ModelRequest, ToolCall, HookFire, HarnessTestDriver, } from "./core/harness-driver.js";
|
|
8
|
-
|
|
11
|
+
import { loadPlugin, resolveHarness } from "./adapters/claude-code/plugin-loader.js";
|
|
12
|
+
export { loadPlugin, resolveHarness };
|
|
9
13
|
export { decideSandbox, specTrusted, sandboxAvailable, type SandboxMode, } from "./sandbox.js";
|
|
10
14
|
export interface HarnessTestSpec {
|
|
11
15
|
/** Fixture files to write in a fresh temp working dir (path → contents). */
|
|
@@ -28,6 +32,21 @@ export interface HarnessTestSpec {
|
|
|
28
32
|
* `plugin` still layer on top. Resolved to an absolute path.
|
|
29
33
|
*/
|
|
30
34
|
readonly pluginDir?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Path to an OUTPUT STYLE file to switch on for this run. The style is
|
|
37
|
+
* written where the harness keeps styles, and selected by the name the
|
|
38
|
+
* harness reads from the file — never by a name you type, because a setting
|
|
39
|
+
* that misses the name loads no style and raises no error.
|
|
40
|
+
*
|
|
41
|
+
* The run is then checked: if no model request carried the style,
|
|
42
|
+
* `runHarnessTest` THROWS instead of returning a trace, so a style that never
|
|
43
|
+
* loaded cannot pass a test. Refused up front on a harness without output
|
|
44
|
+
* styles, or when the fixture's files or settings already decide.
|
|
45
|
+
*
|
|
46
|
+
* What it proves is delivery. A scripted model does not read the style, so
|
|
47
|
+
* nothing in this tier shows that a model FOLLOWS it.
|
|
48
|
+
*/
|
|
49
|
+
readonly outputStyle?: string;
|
|
31
50
|
/** The scripted model turns the agent will take. */
|
|
32
51
|
readonly model: readonly ModelTurn[];
|
|
33
52
|
/** The user prompt. Default: "go". */
|
|
@@ -156,4 +175,14 @@ export declare function runHarnessTest(spec: HarnessTestSpec, opts?: RunHarnessT
|
|
|
156
175
|
export declare function runHarness(spec: HarnessTestSpec, opts?: RunHarnessTestOptions & {
|
|
157
176
|
model?: "mock" | "real";
|
|
158
177
|
}): Promise<HarnessTestResult>;
|
|
178
|
+
/**
|
|
179
|
+
* The files and settings a run with `outputStyle` writes — the style at its
|
|
180
|
+
* place, selected by the name the harness reads from it. The same fixture
|
|
181
|
+
* `runHarnessTest({ outputStyle })` runs, so an eval arm built from it gets
|
|
182
|
+
* exactly what that run proved reaches the model.
|
|
183
|
+
*/
|
|
184
|
+
export declare function outputStyleFixture(outputStyle: string, adapter?: HarnessAdapter): {
|
|
185
|
+
readonly files: Readonly<Record<string, string>>;
|
|
186
|
+
readonly settings: unknown;
|
|
187
|
+
};
|
|
159
188
|
//# sourceMappingURL=harness-test.d.ts.map
|
package/dist/harness-test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.claudeCodeDriver = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.resolveHarness = exports.loadPlugin = exports.scriptModel = void 0;
|
|
3
|
+
exports.claudeCodeDriver = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.resolveHarness = exports.loadPlugin = exports.scriptModel = exports.reachedModel = exports.parseReplies = void 0;
|
|
4
4
|
exports.parseToolCalls = parseToolCalls;
|
|
5
5
|
exports.parseSubagents = parseSubagents;
|
|
6
6
|
exports.parseResultEvent = parseResultEvent;
|
|
@@ -11,6 +11,7 @@ exports.parseClaudeRun = parseClaudeRun;
|
|
|
11
11
|
exports.claudeAvailable = claudeAvailable;
|
|
12
12
|
exports.runHarnessTest = runHarnessTest;
|
|
13
13
|
exports.runHarness = runHarness;
|
|
14
|
+
exports.outputStyleFixture = outputStyleFixture;
|
|
14
15
|
/**
|
|
15
16
|
* vigiles — deterministic Claude Code harness testing.
|
|
16
17
|
*
|
|
@@ -49,13 +50,17 @@ const check_count_js_1 = require("./check-count.js");
|
|
|
49
50
|
const coverage_probe_js_1 = require("./coverage-probe.js");
|
|
50
51
|
const runtime_js_1 = require("./adapters/claude-code/runtime.js");
|
|
51
52
|
const mock_model_js_1 = require("./mock-model.js");
|
|
52
|
-
const
|
|
53
|
+
const replies_js_1 = require("./adapters/claude-code/replies.js");
|
|
54
|
+
Object.defineProperty(exports, "parseReplies", { enumerable: true, get: function () { return replies_js_1.parseReplies; } });
|
|
55
|
+
Object.defineProperty(exports, "reachedModel", { enumerable: true, get: function () { return replies_js_1.reachedModel; } });
|
|
56
|
+
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
57
|
+
const output_style_js_1 = require("./core/output-style.js");
|
|
53
58
|
const sandbox_js_1 = require("./sandbox.js");
|
|
54
59
|
var mock_model_js_2 = require("./mock-model.js");
|
|
55
60
|
Object.defineProperty(exports, "scriptModel", { enumerable: true, get: function () { return mock_model_js_2.scriptModel; } });
|
|
56
|
-
|
|
57
|
-
Object.defineProperty(exports, "loadPlugin", { enumerable: true, get: function () { return
|
|
58
|
-
Object.defineProperty(exports, "resolveHarness", { enumerable: true, get: function () { return
|
|
61
|
+
const plugin_loader_js_1 = require("./adapters/claude-code/plugin-loader.js");
|
|
62
|
+
Object.defineProperty(exports, "loadPlugin", { enumerable: true, get: function () { return plugin_loader_js_1.loadPlugin; } });
|
|
63
|
+
Object.defineProperty(exports, "resolveHarness", { enumerable: true, get: function () { return plugin_loader_js_1.resolveHarness; } });
|
|
59
64
|
var sandbox_js_2 = require("./sandbox.js");
|
|
60
65
|
Object.defineProperty(exports, "decideSandbox", { enumerable: true, get: function () { return sandbox_js_2.decideSandbox; } });
|
|
61
66
|
Object.defineProperty(exports, "specTrusted", { enumerable: true, get: function () { return sandbox_js_2.specTrusted; } });
|
|
@@ -285,8 +290,9 @@ function buildClaudeArgs(spec, hasSettings) {
|
|
|
285
290
|
? ["--plugin-dir", (0, node_path_1.resolve)(spec.pluginDir)]
|
|
286
291
|
: []),
|
|
287
292
|
...(hasSettings ? ["--settings", "settings.json"] : []),
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
// A permission allowlist: an empty one approves nothing, so the flag is
|
|
294
|
+
// left out (a bare `--allowedTools` exits before any model turn).
|
|
295
|
+
...(tools.length === 0 ? [] : ["--allowedTools", ...tools]),
|
|
290
296
|
];
|
|
291
297
|
}
|
|
292
298
|
/** Build the `claude` argv from the driver context — wraps `buildClaudeArgs`. */
|
|
@@ -305,6 +311,7 @@ function parseClaudeRun(stdout) {
|
|
|
305
311
|
toolCalls: parseToolCalls(stdout),
|
|
306
312
|
hooks: parseHooks(stdout),
|
|
307
313
|
output: parseOutput(stdout),
|
|
314
|
+
replies: (0, replies_js_1.parseReplies)(stdout),
|
|
308
315
|
};
|
|
309
316
|
}
|
|
310
317
|
/* v8 ignore start -- spawns the real claude CLI + filesystem; exercised by the
|
|
@@ -399,6 +406,7 @@ function makeResult(cwd, out, parsed, turns, modelRequests) {
|
|
|
399
406
|
toolCalls: parsed.toolCalls,
|
|
400
407
|
hooks: parsed.hooks,
|
|
401
408
|
output: parsed.output,
|
|
409
|
+
...(parsed.replies === undefined ? {} : { replies: parsed.replies }),
|
|
402
410
|
modelRequests,
|
|
403
411
|
subagents: parseSubagents(out.stdout),
|
|
404
412
|
file: (p) => {
|
|
@@ -443,12 +451,8 @@ async function runHarnessTest(spec, opts = {}) {
|
|
|
443
451
|
if (decision.action === "sandbox" && !isClaudeCode) {
|
|
444
452
|
throw new Error(`sandbox not supported for ${driver.runtime.name}: confined execution is Claude Code only. Pass sandbox: false to run ${driver.runtime.name} unconfined (you audited the code, or trust the outer container).`);
|
|
445
453
|
}
|
|
454
|
+
const { files, settings, check } = fixtureFor(spec, opts.adapter);
|
|
446
455
|
const cwd = (0, tmp_root_js_1.makeTmpDir)("harness");
|
|
447
|
-
const { files, settings } = (0, plugin_loader_js_1.resolveHarness)({
|
|
448
|
-
plugin: spec.plugin,
|
|
449
|
-
settings: spec.settings,
|
|
450
|
-
files: spec.files,
|
|
451
|
-
});
|
|
452
456
|
writeFixture(cwd, files, settings);
|
|
453
457
|
const tools = spec.allowedTools ?? ["Read", "Edit", "Write", "Bash"];
|
|
454
458
|
const timeoutMs = spec.timeoutMs ?? 60000;
|
|
@@ -481,7 +485,7 @@ async function runHarnessTest(spec, opts = {}) {
|
|
|
481
485
|
const unconsumed = (0, mock_model_js_1.scriptUnconsumedWarning)(count, sideChannelCount);
|
|
482
486
|
if (unconsumed !== undefined)
|
|
483
487
|
console.error(unconsumed);
|
|
484
|
-
return makeResult(cwd, out, parseClaudeRun(out.stdout), count, out.requests);
|
|
488
|
+
return check(makeResult(cwd, out, parseClaudeRun(out.stdout), count, out.requests));
|
|
485
489
|
}
|
|
486
490
|
// Direct path: mock runs in this process; the agent reaches it over localhost.
|
|
487
491
|
// Start the mock first so its URL feeds the driver's mock-wiring args.
|
|
@@ -496,9 +500,9 @@ async function runHarnessTest(spec, opts = {}) {
|
|
|
496
500
|
const unconsumed = (0, mock_model_js_1.scriptUnconsumedWarning)(mock.count, mock.sideChannelCount ?? 0);
|
|
497
501
|
if (unconsumed !== undefined)
|
|
498
502
|
console.error(unconsumed);
|
|
499
|
-
return makeResult(cwd, out, driver.parseRun(out.stdout), mock.count, [
|
|
503
|
+
return check(makeResult(cwd, out, driver.parseRun(out.stdout), mock.count, [
|
|
500
504
|
...mock.requests,
|
|
501
|
-
]);
|
|
505
|
+
]));
|
|
502
506
|
}
|
|
503
507
|
finally {
|
|
504
508
|
await mock.close();
|
|
@@ -527,6 +531,52 @@ async function runHarness(spec, opts = {}) {
|
|
|
527
531
|
}
|
|
528
532
|
return runHarnessTest(spec, opts);
|
|
529
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* The fixture to write, and the check its result must pass. Without
|
|
536
|
+
* `outputStyle` the check returns the result as it is.
|
|
537
|
+
*/
|
|
538
|
+
function fixtureFor(spec, adapter) {
|
|
539
|
+
// One layout for both phases: the plugin is loaded the way the selected
|
|
540
|
+
// harness lays it out, the same layout the style is then planned against.
|
|
541
|
+
const { layout } = adapter ?? adapter_registry_js_1.defaultAdapter;
|
|
542
|
+
const resolved = (0, plugin_loader_js_1.resolveHarness)({ plugin: spec.plugin, settings: spec.settings, files: spec.files }, layout);
|
|
543
|
+
if (spec.outputStyle === undefined)
|
|
544
|
+
return { ...resolved, check: (result) => result };
|
|
545
|
+
const rules = layout.outputStyles;
|
|
546
|
+
if (rules === undefined)
|
|
547
|
+
throw new Error("outputStyle: this harness has no output styles");
|
|
548
|
+
const plan = (0, output_style_js_1.planStyleRun)(layout, { path: spec.outputStyle, text: readStyleFile(spec.outputStyle) }, resolved);
|
|
549
|
+
if (plan.kind === "refused")
|
|
550
|
+
throw new Error(`outputStyle: ${plan.reason}`);
|
|
551
|
+
return {
|
|
552
|
+
files: plan.files,
|
|
553
|
+
settings: plan.settings,
|
|
554
|
+
check: (result) => {
|
|
555
|
+
if ((0, output_style_js_1.styleReached)(rules, plan.style, result.modelRequests))
|
|
556
|
+
return result;
|
|
557
|
+
throw new Error(`outputStyle: "${plan.style.name ?? ""}" never reached the model — ` +
|
|
558
|
+
`no request carried it, so the harness did not load it. ` +
|
|
559
|
+
`Work dir kept for inspection: ${result.cwd}`);
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* The files and settings a run with `outputStyle` writes — the style at its
|
|
565
|
+
* place, selected by the name the harness reads from it. The same fixture
|
|
566
|
+
* `runHarnessTest({ outputStyle })` runs, so an eval arm built from it gets
|
|
567
|
+
* exactly what that run proved reaches the model.
|
|
568
|
+
*/
|
|
569
|
+
function outputStyleFixture(outputStyle, adapter) {
|
|
570
|
+
const { files, settings } = fixtureFor({ outputStyle, model: [] }, adapter);
|
|
571
|
+
return { files, settings };
|
|
572
|
+
}
|
|
573
|
+
/** The style file's text, or a clear error naming the path. */
|
|
574
|
+
function readStyleFile(path) {
|
|
575
|
+
const abs = (0, node_path_1.resolve)(path);
|
|
576
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
577
|
+
throw new Error(`outputStyle: no such file: ${path}`);
|
|
578
|
+
return (0, node_fs_1.readFileSync)(abs, "utf-8");
|
|
579
|
+
}
|
|
530
580
|
/** Pull the pillar-2 driver off an adapter, asserting it supports testing. */
|
|
531
581
|
async function requireDriver(adapter) {
|
|
532
582
|
(0, adapter_conformance_js_1.assertHarnessTestable)(adapter);
|
package/dist/optimize.js
CHANGED
|
@@ -36,10 +36,6 @@ const score_explainer_js_1 = require("./score-explainer.js");
|
|
|
36
36
|
function actionFor(e) {
|
|
37
37
|
return e.symptom === "wrong-skill-fires" ? "differentiate" : "fix";
|
|
38
38
|
}
|
|
39
|
-
function isEmptyMachine(r) {
|
|
40
|
-
const surfaces = r.skills.length + r.agents.length + r.hooks.length + r.commands;
|
|
41
|
-
return surfaces === 0 && !r.mcp;
|
|
42
|
-
}
|
|
43
39
|
/**
|
|
44
40
|
* Turn a scan report into a prioritized optimization plan: the structural-health
|
|
45
41
|
* score + a typed recommendation per deterministic finding (`likely` dead-ends
|
|
@@ -60,7 +56,7 @@ function optimize(report) {
|
|
|
60
56
|
score,
|
|
61
57
|
grade: (0, score_core_js_1.gradeFor)(score),
|
|
62
58
|
recommendations,
|
|
63
|
-
empty: isEmptyMachine(report),
|
|
59
|
+
empty: (0, score_core_js_1.isEmptyMachine)(report),
|
|
64
60
|
};
|
|
65
61
|
}
|
|
66
62
|
const ACTION_LABEL = {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HarnessAdapter } from "./core/adapter.js";
|
|
2
|
+
import type { EvalArm } from "./eval.js";
|
|
3
|
+
/** The style on and the style off, ready for `paid_measureArms({ arms })`. */
|
|
4
|
+
export interface OutputStyleArms {
|
|
5
|
+
/** The style file at its place in the work dir, selected in the settings. */
|
|
6
|
+
readonly with: EvalArm;
|
|
7
|
+
/** Every style switched off, including one the user set for all projects. */
|
|
8
|
+
readonly without: EvalArm;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build the with/without arms for the style at `path` (a file in the shape the
|
|
12
|
+
* harness loads, e.g. a Claude Code `.md` style), for the harness the paid tier
|
|
13
|
+
* drives. Spends nothing: the one run it makes uses the scripted model. Throws,
|
|
14
|
+
* before any eval, when the harness has no output styles, cannot tell the
|
|
15
|
+
* style's name, or the style does not reach the model.
|
|
16
|
+
*
|
|
17
|
+
* @experimental One real-model run behind it; how arms are built may change
|
|
18
|
+
* once a style can be authored as a compiled spec with its checks attached.
|
|
19
|
+
*/
|
|
20
|
+
export declare function experimental_outputStyleArms(path: string): Promise<OutputStyleArms>;
|
|
21
|
+
/**
|
|
22
|
+
* {@link experimental_outputStyleArms} on a given adapter. Not public: the paid
|
|
23
|
+
* tier drives only the default harness, so arms built for another one would be
|
|
24
|
+
* run by a harness they were not checked on.
|
|
25
|
+
*/
|
|
26
|
+
export declare function outputStyleArmsWith(path: string, adapter: HarnessAdapter): Promise<OutputStyleArms>;
|
|
27
|
+
//# sourceMappingURL=output-style-arms.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.experimental_outputStyleArms = experimental_outputStyleArms;
|
|
4
|
+
exports.outputStyleArmsWith = outputStyleArmsWith;
|
|
5
|
+
/**
|
|
6
|
+
* The two arms of a paid output-style eval — the style on, the style off — built
|
|
7
|
+
* from the style FILE, after one free run has shown the style reaches the model.
|
|
8
|
+
*
|
|
9
|
+
* Three silent failures this closes. A hand-typed arm name that misses the
|
|
10
|
+
* style's name loads no style and raises no error, so the "with" arm is the
|
|
11
|
+
* "without" arm and the eval compares a run with itself. A bare "without" arm
|
|
12
|
+
* still loads a style the user switched on for every project, which is the same
|
|
13
|
+
* comparison from the other side. And the paid tier cannot check delivery at
|
|
14
|
+
* all: it drives the real API, so no request is captured. The free run here
|
|
15
|
+
* uses the scripted model, where requests ARE captured, with the same fixture
|
|
16
|
+
* the paid arm gets — so a style that would not load stops the eval before the
|
|
17
|
+
* first billed trial.
|
|
18
|
+
*/
|
|
19
|
+
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
20
|
+
const harness_test_js_1 = require("./harness-test.js");
|
|
21
|
+
/**
|
|
22
|
+
* Build the with/without arms for the style at `path` (a file in the shape the
|
|
23
|
+
* harness loads, e.g. a Claude Code `.md` style), for the harness the paid tier
|
|
24
|
+
* drives. Spends nothing: the one run it makes uses the scripted model. Throws,
|
|
25
|
+
* before any eval, when the harness has no output styles, cannot tell the
|
|
26
|
+
* style's name, or the style does not reach the model.
|
|
27
|
+
*
|
|
28
|
+
* @experimental One real-model run behind it; how arms are built may change
|
|
29
|
+
* once a style can be authored as a compiled spec with its checks attached.
|
|
30
|
+
*/
|
|
31
|
+
async function experimental_outputStyleArms(path) {
|
|
32
|
+
return outputStyleArmsWith(path, adapter_registry_js_1.defaultAdapter);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* {@link experimental_outputStyleArms} on a given adapter. Not public: the paid
|
|
36
|
+
* tier drives only the default harness, so arms built for another one would be
|
|
37
|
+
* run by a harness they were not checked on.
|
|
38
|
+
*/
|
|
39
|
+
async function outputStyleArmsWith(path, adapter) {
|
|
40
|
+
const preflight = await (0, harness_test_js_1.runHarnessTest)({ outputStyle: path, prompt: "hi", model: [{ text: "ok" }] }, { adapter });
|
|
41
|
+
preflight.cleanup();
|
|
42
|
+
const { files, settings } = (0, harness_test_js_1.outputStyleFixture)(path, adapter);
|
|
43
|
+
const none = adapter.layout.outputStyles?.selectNone ?? {};
|
|
44
|
+
return { with: { files, settings }, without: { settings: none } };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=output-style-arms.js.map
|
package/dist/scan-files.js
CHANGED
|
@@ -539,7 +539,7 @@ function scanFiles(files, layout = layout_js_1.claudeCodeLayout, dialect = diale
|
|
|
539
539
|
hooks,
|
|
540
540
|
inlineHooks: inline,
|
|
541
541
|
manualHookCount: manual,
|
|
542
|
-
|
|
542
|
+
...(0, coverage_evidence_js_1.inventoryCounts)(loaded.files, cls.isCommand, [lay], coverage),
|
|
543
543
|
// A declared server set counts even when the loader emitted no warning —
|
|
544
544
|
// otherwise a plugin whose servers come from the Agent Plugins `mcp.json`
|
|
545
545
|
// reports "MCP servers: no" while the report lists an MCP finding.
|
package/dist/scan.d.ts
CHANGED
|
@@ -211,6 +211,12 @@ export interface ScanReport {
|
|
|
211
211
|
*/
|
|
212
212
|
readonly manualHookCount: number;
|
|
213
213
|
readonly commands: number;
|
|
214
|
+
/**
|
|
215
|
+
* Output styles found, exempt ones aside, or `"not-supported"` when the
|
|
216
|
+
* harness has none — the inventory side of `untested-output-style`. Optional
|
|
217
|
+
* so a hand-built report predating the field still type-checks.
|
|
218
|
+
*/
|
|
219
|
+
readonly outputStyles?: number | "not-supported";
|
|
214
220
|
readonly mcp: boolean;
|
|
215
221
|
/**
|
|
216
222
|
* Intra-plugin file references (hook scripts, skill bodies) pointing at files
|
package/dist/scan.js
CHANGED
|
@@ -258,6 +258,7 @@ function scanPlugin(dir, layout, dialect, opts = {}) {
|
|
|
258
258
|
const coverage = (0, test_coverage_js_1.findUntestedSurfaces)({
|
|
259
259
|
basePath: dir,
|
|
260
260
|
layout: lay,
|
|
261
|
+
harnessLayouts: declared.map((h) => h.layout),
|
|
261
262
|
// The SAME `.vigilesrc.json#exclude`. Untested-surface discovery is a
|
|
262
263
|
// second walk over the same trees, so leaving it out would have excluded a
|
|
263
264
|
// skill from the GRADE while still naming it in "Untested surfaces: 1" — a
|
|
@@ -276,7 +277,7 @@ function scanPlugin(dir, layout, dialect, opts = {}) {
|
|
|
276
277
|
hooks,
|
|
277
278
|
inlineHooks: inline,
|
|
278
279
|
manualHookCount: manual,
|
|
279
|
-
|
|
280
|
+
...(0, coverage_evidence_js_1.inventoryCounts)(loaded.files, cls.isCommand, declared.map((h) => h.layout), coverage),
|
|
280
281
|
// A declared server set counts even when the loader emitted no warning —
|
|
281
282
|
// otherwise a plugin whose servers come from the Agent Plugins `mcp.json`
|
|
282
283
|
// reports "MCP servers: no" while the report lists an MCP finding.
|
|
@@ -655,6 +656,16 @@ function instructionWeightLines(w) {
|
|
|
655
656
|
...tail,
|
|
656
657
|
];
|
|
657
658
|
}
|
|
659
|
+
/** The commands and output-style lines of a scan's facts; a harness without styles says so. */
|
|
660
|
+
function inventoryFacts(r) {
|
|
661
|
+
const commands = r.commands > 0 ? [`Commands: ${String(r.commands)}`] : [];
|
|
662
|
+
if (r.outputStyles === "not-supported")
|
|
663
|
+
return [...commands, "Output styles: n/a (this harness has none)"];
|
|
664
|
+
const styles = r.outputStyles ?? 0;
|
|
665
|
+
return styles > 0
|
|
666
|
+
? [...commands, `Output styles: ${String(styles)}`]
|
|
667
|
+
: commands;
|
|
668
|
+
}
|
|
658
669
|
function formatScanReport(r) {
|
|
659
670
|
const out = [`Scan: ${r.dir}`, ""];
|
|
660
671
|
if (r.instructions) {
|
|
@@ -715,15 +726,14 @@ function formatScanReport(r) {
|
|
|
715
726
|
out.push(...section("Ineffective hook guards (false confidence)", r.hookBlockFindings.map((h) => ` ✗ [${h.event}] ${h.scriptPath ?? "(inline)"}: ${h.message}`)));
|
|
716
727
|
out.push(...section("Hook matchers that don't fire as written", r.hookMatcherFindings.map((m) => ` ✗ ${m.message}`)));
|
|
717
728
|
const facts = [];
|
|
718
|
-
|
|
719
|
-
facts.push(`Commands: ${String(r.commands)}`);
|
|
729
|
+
facts.push(...inventoryFacts(r));
|
|
720
730
|
facts.push(`MCP servers: ${r.mcp ? "yes" : "no"}`);
|
|
721
731
|
facts.push(`Untested surfaces: ${String(r.untested)}`);
|
|
722
732
|
// The two tiers, named separately — a surface with a deterministic harness and
|
|
723
733
|
// no eval is NOT the same position as one with neither. Shown only when the
|
|
724
734
|
// producer supplied the split (a hand-built report may not have).
|
|
725
735
|
if (r.untestedHarness !== undefined && r.unevaluated !== undefined) {
|
|
726
|
-
facts.push(` no harness: ${String(r.untestedHarness)} ·
|
|
736
|
+
facts.push(` no harness: ${String(r.untestedHarness)} · never measured with a real model: ${String(r.unevaluated)}`);
|
|
727
737
|
}
|
|
728
738
|
// …and HOW the rest passed. Without this the count is unfalsifiable from the
|
|
729
739
|
// outside: "28 covered" and "28 names that appear in some file" print the same.
|
package/dist/score-core.d.ts
CHANGED
|
@@ -180,6 +180,9 @@ export declare function reportDeductions(r: ScanReport): Deduction[];
|
|
|
180
180
|
* surface of any kind", because it reads as "this plugin is broken" — a
|
|
181
181
|
* different statement from "this plugin has no skills or subagents".
|
|
182
182
|
*
|
|
183
|
+
* An output style counts too: a repo can ship only a style, and "nothing to
|
|
184
|
+
* audit" for it would hide the untested-output-style finding.
|
|
185
|
+
*
|
|
183
186
|
* Hooks count in BOTH forms: `hooks[]` is the script-backed ones, `inlineHooks`
|
|
184
187
|
* the shell one-liners that carry no script file to path-check. The second is
|
|
185
188
|
* still a gate that runs.
|
package/dist/score-core.js
CHANGED
|
@@ -332,6 +332,9 @@ function reportDeductions(r) {
|
|
|
332
332
|
* surface of any kind", because it reads as "this plugin is broken" — a
|
|
333
333
|
* different statement from "this plugin has no skills or subagents".
|
|
334
334
|
*
|
|
335
|
+
* An output style counts too: a repo can ship only a style, and "nothing to
|
|
336
|
+
* audit" for it would hide the untested-output-style finding.
|
|
337
|
+
*
|
|
335
338
|
* Hooks count in BOTH forms: `hooks[]` is the script-backed ones, `inlineHooks`
|
|
336
339
|
* the shell one-liners that carry no script file to path-check. The second is
|
|
337
340
|
* still a gate that runs.
|
|
@@ -341,7 +344,8 @@ function isEmptyMachine(r) {
|
|
|
341
344
|
r.agents.length +
|
|
342
345
|
r.hooks.length +
|
|
343
346
|
r.inlineHooks +
|
|
344
|
-
r.commands
|
|
347
|
+
r.commands +
|
|
348
|
+
(typeof r.outputStyles === "number" ? r.outputStyles : 0);
|
|
345
349
|
return surfaces === 0 && !r.mcp;
|
|
346
350
|
}
|
|
347
351
|
/**
|
package/dist/setup-plan.d.ts
CHANGED
|
@@ -118,7 +118,7 @@ export declare const STRUCTURAL_RULES: readonly ["subagent-tool-contract", "suba
|
|
|
118
118
|
* here: they're acknowledged-noisy recommendations we never gate on (see
|
|
119
119
|
* research/install-enforcement-dx.md).
|
|
120
120
|
*/
|
|
121
|
-
export declare const WORKFLOW_RULES: readonly ["require-instructions-spec", "untested-skill", "untested-subagent", "untested-hook"];
|
|
121
|
+
export declare const WORKFLOW_RULES: readonly ["require-instructions-spec", "untested-skill", "untested-subagent", "untested-hook", "untested-output-style"];
|
|
122
122
|
/**
|
|
123
123
|
* The **`nudge`** group — recommendations / acknowledged-noisy checks that NEVER
|
|
124
124
|
* gate (not even under `--strict`): `frontmatter-valid` (js-yaml is stricter than
|
package/dist/setup-plan.js
CHANGED