vigiles 4.0.1 → 4.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/adapter-conformance.js +1 -1
- package/dist/adapter-registry.d.ts +45 -1
- package/dist/adapter-registry.js +78 -3
- package/dist/check.d.ts +132 -0
- package/dist/check.js +318 -0
- package/dist/cli.js +255 -73
- package/dist/core/compile.d.ts +1 -1
- package/dist/core/compile.js +1 -1
- package/dist/core/compose.d.ts +1 -1
- package/dist/core/compose.js +1 -1
- package/dist/core/generate-schema.d.ts +1 -1
- package/dist/core/generate-schema.js +4 -4
- package/dist/core/linters.js +2 -2
- package/dist/core/orphans.js +57 -14
- package/dist/core/proofs.js +1 -1
- package/dist/core/refs.d.ts +1 -1
- package/dist/core/refs.js +2 -2
- package/dist/core/sidecar.d.ts +1 -1
- package/dist/core/sidecar.js +1 -1
- package/dist/core/spec.d.ts +1 -1
- package/dist/core/spec.js +1 -1
- package/dist/core/types.d.ts +11 -1
- package/dist/core/validate.js +2 -2
- package/dist/e2e.d.ts +10 -13
- package/dist/e2e.js +10 -17
- package/dist/eval.d.ts +217 -2
- package/dist/eval.js +428 -18
- package/dist/harness-assert.d.ts +3 -0
- package/dist/harness-assert.js +16 -0
- package/dist/harness-test.d.ts +46 -0
- package/dist/harness-test.js +102 -0
- package/dist/integration.d.ts +8 -0
- package/dist/integration.js +10 -0
- package/dist/jest.d.ts +3 -1
- package/dist/jest.js +3 -2
- package/dist/run-hook.d.ts +22 -0
- package/dist/run-hook.js +28 -0
- package/dist/scan.d.ts +1 -1
- package/dist/scan.js +1 -1
- package/dist/setup-plan.d.ts +16 -1
- package/dist/setup-plan.js +38 -1
- package/dist/skill-harness.d.ts +25 -0
- package/dist/skill-harness.js +40 -0
- package/dist/test-coverage.js +8 -1
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +7 -0
- package/dist/unit.d.ts +4 -2
- package/dist/unit.js +7 -1
- package/dist/vitest.d.mts +3 -1
- package/hooks/refs-nudge.sh +1 -1
- package/package.json +3 -2
- package/skills/edit-spec/SKILL.md +21 -10
- package/skills/linter-docs/SKILL.md +23 -0
- package/skills/migrate-to-spec/SKILL.md +1 -1
- package/skills/strengthen/SKILL.md +1 -2
- package/skills/generate-rule/SKILL.md +0 -64
package/dist/core/linters.js
CHANGED
|
@@ -461,7 +461,7 @@ function isEslintPluginRule(ruleName, basePath) {
|
|
|
461
461
|
/**
|
|
462
462
|
* Enumerate all rules for a CLI-based linter so `tryCliCheck` can emit
|
|
463
463
|
* closest-match suggestions on typos. Result is cached per (linter,
|
|
464
|
-
* basePath) so each linter's discovery CLI runs at most once per
|
|
464
|
+
* basePath) so each linter's discovery CLI runs at most once per lint.
|
|
465
465
|
*/
|
|
466
466
|
const CLI_RULE_SET_CACHE = new Map();
|
|
467
467
|
function getCliRuleSet(linterName, basePath) {
|
|
@@ -561,7 +561,7 @@ function getCliRuleSet(linterName, basePath) {
|
|
|
561
561
|
// The `vigiles/<id>` namespace lets specs declare mechanical checks that
|
|
562
562
|
// vigiles itself runs (orphan docs, integrity, etc.) without delegating to
|
|
563
563
|
// an external linter. Existence is verified at compile time against this
|
|
564
|
-
// fixed catalog; the actual check runs at
|
|
564
|
+
// fixed catalog; the actual check runs at lint time.
|
|
565
565
|
// ---------------------------------------------------------------------------
|
|
566
566
|
const VIGILES_INTERNAL_RULES = new Set(["orphan-docs"]);
|
|
567
567
|
/** @internal */ function tryVigilesInternal(ctx) {
|
package/dist/core/orphans.js
CHANGED
|
@@ -28,6 +28,13 @@ const DEFAULT_IGNORE = [
|
|
|
28
28
|
".vigiles/**",
|
|
29
29
|
".git/**",
|
|
30
30
|
];
|
|
31
|
+
/**
|
|
32
|
+
* A doc carrying this marker opts out of orphan detection — the inline escape
|
|
33
|
+
* hatch, mirroring `vigiles-disable require-spec` and `vigiles:ignore-test`.
|
|
34
|
+
* Use it for an intentionally-unreferenced doc (a changelog, a top-level index)
|
|
35
|
+
* that nothing else links to but is not rot.
|
|
36
|
+
*/
|
|
37
|
+
const DISABLE_RE = /<!--\s*vigiles-disable\s+orphan-docs\s*-->/;
|
|
31
38
|
// Match markdown links ](path.md) or ](path.md#anchor)
|
|
32
39
|
const LINK_RE = /\]\(([^)\s]+\.md)(?:#[^)]*)?\)/g;
|
|
33
40
|
// Match backtick code spans wrapping a path ending in .md
|
|
@@ -35,6 +42,27 @@ const BACKTICK_RE = /`([^`\s]+\.md)`/g;
|
|
|
35
42
|
function normalizePath(p) {
|
|
36
43
|
return p.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
37
44
|
}
|
|
45
|
+
/** True when a doc opts out of orphan detection via the inline disable marker. */
|
|
46
|
+
function isOrphanExempt(absPath) {
|
|
47
|
+
try {
|
|
48
|
+
return DISABLE_RE.test((0, node_fs_1.readFileSync)(absPath, "utf-8"));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false; // unreadable — treat like any other doc
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Discover docs under `include`, dropping any that carry the inline opt-out. */
|
|
55
|
+
function collectDocs(basePath, include, ignore) {
|
|
56
|
+
const docs = new Set();
|
|
57
|
+
for (const pattern of include) {
|
|
58
|
+
for (const p of (0, glob_1.globSync)(pattern, { cwd: basePath, ignore: [...ignore] })) {
|
|
59
|
+
if (isOrphanExempt((0, node_path_1.resolve)(basePath, p)))
|
|
60
|
+
continue;
|
|
61
|
+
docs.add(normalizePath(p));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return docs;
|
|
65
|
+
}
|
|
38
66
|
function extractRefs(content) {
|
|
39
67
|
const refs = [];
|
|
40
68
|
for (const m of content.matchAll(LINK_RE))
|
|
@@ -43,6 +71,23 @@ function extractRefs(content) {
|
|
|
43
71
|
refs.push(normalizePath(m[1]));
|
|
44
72
|
return refs;
|
|
45
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Repo-root-relative targets a reference could mean, from a given source file.
|
|
76
|
+
* Markdown links are conventionally **file-relative** (a `[x](foo.md)` in
|
|
77
|
+
* `research/README.md` points at `research/foo.md`, and `../docs/x.md` walks up),
|
|
78
|
+
* but docs also write **root-relative** paths (`research/foo.md` from anywhere).
|
|
79
|
+
* We credit both so a real link is never miscounted as an orphan.
|
|
80
|
+
*/
|
|
81
|
+
function refTargets(sourcePath, ref) {
|
|
82
|
+
const targets = new Set([ref]); // root-relative reading
|
|
83
|
+
const dir = sourcePath.includes("/")
|
|
84
|
+
? sourcePath.slice(0, sourcePath.lastIndexOf("/"))
|
|
85
|
+
: "";
|
|
86
|
+
// file-relative reading: resolve against the source's directory.
|
|
87
|
+
const resolved = normalizePath(node_path_1.posix.normalize(dir ? `${dir}/${ref}` : ref));
|
|
88
|
+
targets.add(resolved);
|
|
89
|
+
return [...targets];
|
|
90
|
+
}
|
|
46
91
|
// ---------------------------------------------------------------------------
|
|
47
92
|
// Public API
|
|
48
93
|
// ---------------------------------------------------------------------------
|
|
@@ -63,12 +108,7 @@ function findOrphanDocs(options = {}) {
|
|
|
63
108
|
const include = options.include ?? DEFAULT_INCLUDE;
|
|
64
109
|
const userExclude = options.exclude ?? [];
|
|
65
110
|
const ignore = [...DEFAULT_IGNORE, ...userExclude];
|
|
66
|
-
const allDocs =
|
|
67
|
-
for (const pattern of include) {
|
|
68
|
-
const found = (0, glob_1.globSync)(pattern, { cwd: basePath, ignore });
|
|
69
|
-
for (const p of found)
|
|
70
|
-
allDocs.add(normalizePath(p));
|
|
71
|
-
}
|
|
111
|
+
const allDocs = collectDocs(basePath, include, ignore);
|
|
72
112
|
const allMarkdown = (0, glob_1.globSync)("**/*.md", {
|
|
73
113
|
cwd: basePath,
|
|
74
114
|
ignore: [...DEFAULT_IGNORE],
|
|
@@ -83,15 +123,17 @@ function findOrphanDocs(options = {}) {
|
|
|
83
123
|
catch {
|
|
84
124
|
continue;
|
|
85
125
|
}
|
|
86
|
-
for (const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
126
|
+
for (const rawRef of extractRefs(content)) {
|
|
127
|
+
for (const target of refTargets(source, rawRef)) {
|
|
128
|
+
if (target === source)
|
|
129
|
+
continue;
|
|
130
|
+
let sources = referencedBy.get(target);
|
|
131
|
+
if (!sources) {
|
|
132
|
+
sources = new Set();
|
|
133
|
+
referencedBy.set(target, sources);
|
|
134
|
+
}
|
|
135
|
+
sources.add(source);
|
|
93
136
|
}
|
|
94
|
-
sources.add(source);
|
|
95
137
|
}
|
|
96
138
|
}
|
|
97
139
|
const orphans = [];
|
|
@@ -119,6 +161,7 @@ function formatOrphanReport(report) {
|
|
|
119
161
|
];
|
|
120
162
|
for (const o of report.orphans)
|
|
121
163
|
lines.push(` ${o}`);
|
|
164
|
+
lines.push(" Fix: link each from another .md (README, a spec's Key Files, or a doc).", " To silence: add `<!-- vigiles-disable orphan-docs -->` to the doc, or", " exclude it via .vigilesrc.json → `orphans.exclude` (or narrow `orphans.include`).");
|
|
122
165
|
return lines.join("\n");
|
|
123
166
|
}
|
|
124
167
|
//# sourceMappingURL=orphans.js.map
|
package/dist/core/proofs.js
CHANGED
|
@@ -188,7 +188,7 @@ function findSimilarRules(rules, threshold = 0.5) {
|
|
|
188
188
|
*
|
|
189
189
|
* Throws a structured error on unknown rule kinds so that the caller (e.g.
|
|
190
190
|
* runProofSuite) can surface a clear proof failure rather than letting an
|
|
191
|
-
* `undefined` propagate into compressedSize and crash the
|
|
191
|
+
* `undefined` propagate into compressedSize and crash the computation.
|
|
192
192
|
*/
|
|
193
193
|
function ruleToText(rule) {
|
|
194
194
|
switch (rule._kind) {
|
package/dist/core/refs.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare function symbolRefs(markdown: string): SymbolRef[];
|
|
|
29
29
|
export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[];
|
|
30
30
|
/**
|
|
31
31
|
* Whether a span is a **linter-rule reference** that ought to be marked
|
|
32
|
-
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the
|
|
32
|
+
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the lint can verify the
|
|
33
33
|
* rule exists AND is enabled. High-signal only: a slash-scoped name with no file
|
|
34
34
|
* extension. A function-call form `` `foo(args)` `` is reduced to its callee.
|
|
35
35
|
*
|
package/dist/core/refs.js
CHANGED
|
@@ -109,7 +109,7 @@ const IGNORE_FILE = /<!--\s*vigiles:ignore-file\s*-->/;
|
|
|
109
109
|
const IGNORE_LINE = /<!--\s*vigiles:ignore\s*-->/;
|
|
110
110
|
/**
|
|
111
111
|
* Whether a span is a **linter-rule reference** that ought to be marked
|
|
112
|
-
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the
|
|
112
|
+
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the lint can verify the
|
|
113
113
|
* rule exists AND is enabled. High-signal only: a slash-scoped name with no file
|
|
114
114
|
* extension. A function-call form `` `foo(args)` `` is reduced to its callee.
|
|
115
115
|
*
|
|
@@ -152,7 +152,7 @@ function collectRefIssues(markdown, basePath) {
|
|
|
152
152
|
for (const u of unmarkedCodeRefs(markdown)) {
|
|
153
153
|
out.push(`line ${String(u.line)}: \`${u.text}\` is an unmarked linter-rule ` +
|
|
154
154
|
`reference — mark it as \`enforce("${u.text}")\` (typed spec) or ` +
|
|
155
|
-
`\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so
|
|
155
|
+
`\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so lint can verify ` +
|
|
156
156
|
`it exists and is enabled, or add <!-- vigiles:ignore --> if it is prose`);
|
|
157
157
|
}
|
|
158
158
|
return out;
|
package/dist/core/sidecar.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Used by the post-session audit to know which targets exist and which
|
|
5
5
|
* spec source / inputs each one tracks. The compile pipeline writes
|
|
6
6
|
* these whenever a spec is built; readers (currently only session.ts)
|
|
7
|
-
* consume them at
|
|
7
|
+
* consume them at verification time.
|
|
8
8
|
*
|
|
9
9
|
* This module is the ONLY place sidecars live now — the freshness rule
|
|
10
10
|
* doesn't depend on them anymore.
|
package/dist/core/sidecar.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Used by the post-session audit to know which targets exist and which
|
|
6
6
|
* spec source / inputs each one tracks. The compile pipeline writes
|
|
7
7
|
* these whenever a spec is built; readers (currently only session.ts)
|
|
8
|
-
* consume them at
|
|
8
|
+
* consume them at verification time.
|
|
9
9
|
*
|
|
10
10
|
* This module is the ONLY place sidecars live now — the freshness rule
|
|
11
11
|
* doesn't depend on them anymore.
|
package/dist/core/spec.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export declare function cmd(command: NoInfer<StrictCmd>): CmdRef;
|
|
|
156
156
|
* Reference a symbol defined in a file — verified at compile time that the
|
|
157
157
|
* named file exists AND defines the named symbol (via ast-grep, cross-language).
|
|
158
158
|
* Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
|
|
159
|
-
* `
|
|
159
|
+
* `lint` / `refs-hook` re-verify the same reference.
|
|
160
160
|
*/
|
|
161
161
|
export declare function symbol(file: NoInfer<StrictFile>, name: string): SymbolRef;
|
|
162
162
|
/**
|
package/dist/core/spec.js
CHANGED
|
@@ -93,7 +93,7 @@ function cmd(command) {
|
|
|
93
93
|
* Reference a symbol defined in a file — verified at compile time that the
|
|
94
94
|
* named file exists AND defines the named symbol (via ast-grep, cross-language).
|
|
95
95
|
* Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
|
|
96
|
-
* `
|
|
96
|
+
* `lint` / `refs-hook` re-verify the same reference.
|
|
97
97
|
*/
|
|
98
98
|
function symbol(file, name) {
|
|
99
99
|
return { _ref: "symbol", file: file, symbol: name };
|
package/dist/core/types.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export interface RulesConfig {
|
|
|
97
97
|
"untested-surface"?: RuleWithOptions<TestCoverageConfig>;
|
|
98
98
|
/**
|
|
99
99
|
* Nudge (or block) when an instruction file has code-shaped references that
|
|
100
|
-
* aren't expressed as vigiles marks (so the
|
|
100
|
+
* aren't expressed as vigiles marks (so the lint can't verify them), or a
|
|
101
101
|
* `vigiles:symbol` mark that points at a missing symbol. Drives the
|
|
102
102
|
* PostToolUse refs-hook: "warn" (default) → a non-blocking nudge, "error" →
|
|
103
103
|
* block the edit, false → off.
|
|
@@ -127,6 +127,16 @@ export interface VigilesConfig {
|
|
|
127
127
|
}>;
|
|
128
128
|
/** Orphan-docs check configuration. Include/exclude globs, tsconfig-style. */
|
|
129
129
|
orphans?: OrphansConfig;
|
|
130
|
+
/**
|
|
131
|
+
* The harness(es) this repo targets — selects the compile dialect / skill
|
|
132
|
+
* frontmatter profile / instruction-file shape, instead of sniffing the cwd.
|
|
133
|
+
* A single name (`"codex"`) for the common single-harness repo, or an array
|
|
134
|
+
* (`["claude-code", "codex"]`) declaring the supported set. Written by
|
|
135
|
+
* `vigiles init`. Omitted → the CLI auto-detects (backwards-compatible).
|
|
136
|
+
* Canonical adapter names; `"claude"` is accepted as an alias for
|
|
137
|
+
* `"claude-code"`. See research/multi-harness-compile.md.
|
|
138
|
+
*/
|
|
139
|
+
harness?: string | string[];
|
|
130
140
|
}
|
|
131
141
|
/** Valid marker types for rule detection. */
|
|
132
142
|
export type MarkerType = "headings" | "checkboxes";
|
package/dist/core/validate.js
CHANGED
|
@@ -157,9 +157,9 @@ function validate(content, { ruleMarkers, rules: rulesConfig, filePath, dialect
|
|
|
157
157
|
const specPath = filePath + ".spec.ts";
|
|
158
158
|
// Inline mode counts as a spec — any parseable
|
|
159
159
|
// `<!-- vigiles:enforce ... -->` comment means the file is
|
|
160
|
-
// verified on `vigiles
|
|
160
|
+
// verified on `vigiles lint` even without a .spec.ts sibling.
|
|
161
161
|
// Delegate to the real parser so a malformed marker can't
|
|
162
|
-
// satisfy require-spec with a rule that
|
|
162
|
+
// satisfy require-spec with a rule that lint can't verify.
|
|
163
163
|
const hasInline = (0, inline_js_1.hasInlineRules)(content) || (0, frontmatter_js_1.hasFrontmatterRules)(content);
|
|
164
164
|
if (!(0, node_fs_1.existsSync)(specPath) && !hasInline) {
|
|
165
165
|
const msg = {
|
package/dist/e2e.d.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `vigiles/e2e` —
|
|
3
|
-
* network, but a definite pass/fail).
|
|
2
|
+
* `vigiles/e2e` — DEPRECATED back-compat alias for [`vigiles/integration`](./integration.ts).
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* There is no separate "e2e" tier: real **egress** is a *capability* of the
|
|
5
|
+
* harness/integration scope (`egressRoutes()` + `runHook`'s `egress: { allow }`),
|
|
6
|
+
* not a different kind of test — the old `e2e` barrel added exactly one symbol
|
|
7
|
+
* over `integration`, which is the definition of a non-tier. It now lives on
|
|
8
|
+
* `vigiles/integration`; this entry re-exports it unchanged so existing imports
|
|
9
|
+
* keep working. See `research/testing-api-design.md` Part 4 (two scopes, not
|
|
10
|
+
* four tiers). Prefer `vigiles/integration`.
|
|
11
11
|
*
|
|
12
|
-
* NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`)
|
|
13
|
-
*
|
|
14
|
-
* via `vigiles eval` on `*.eval.mjs`. Import them from
|
|
15
|
-
* [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
|
|
12
|
+
* NOT here: **evals** (`runEval` / `measure` / `measureTriggerRate` / `judge`) —
|
|
13
|
+
* those are non-deterministic measurement (`vigiles/eval`), a different axis.
|
|
16
14
|
*/
|
|
17
15
|
export * from "./integration.js";
|
|
18
|
-
export { egressRoutes } from "./run-hook.js";
|
|
19
16
|
//# sourceMappingURL=e2e.d.ts.map
|
package/dist/e2e.js
CHANGED
|
@@ -14,26 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.egressRoutes = void 0;
|
|
18
17
|
/**
|
|
19
|
-
* `vigiles/e2e` —
|
|
20
|
-
* network, but a definite pass/fail).
|
|
18
|
+
* `vigiles/e2e` — DEPRECATED back-compat alias for [`vigiles/integration`](./integration.ts).
|
|
21
19
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* There is no separate "e2e" tier: real **egress** is a *capability* of the
|
|
21
|
+
* harness/integration scope (`egressRoutes()` + `runHook`'s `egress: { allow }`),
|
|
22
|
+
* not a different kind of test — the old `e2e` barrel added exactly one symbol
|
|
23
|
+
* over `integration`, which is the definition of a non-tier. It now lives on
|
|
24
|
+
* `vigiles/integration`; this entry re-exports it unchanged so existing imports
|
|
25
|
+
* keep working. See `research/testing-api-design.md` Part 4 (two scopes, not
|
|
26
|
+
* four tiers). Prefer `vigiles/integration`.
|
|
28
27
|
*
|
|
29
|
-
* NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`)
|
|
30
|
-
*
|
|
31
|
-
* via `vigiles eval` on `*.eval.mjs`. Import them from
|
|
32
|
-
* [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
|
|
28
|
+
* NOT here: **evals** (`runEval` / `measure` / `measureTriggerRate` / `judge`) —
|
|
29
|
+
* those are non-deterministic measurement (`vigiles/eval`), a different axis.
|
|
33
30
|
*/
|
|
34
31
|
__exportStar(require("./integration.js"), exports);
|
|
35
|
-
// The real-egress capability probe (the egress-using runHook is already re-exported
|
|
36
|
-
// via the integration→unit chain).
|
|
37
|
-
var run_hook_js_1 = require("./run-hook.js");
|
|
38
|
-
Object.defineProperty(exports, "egressRoutes", { enumerable: true, get: function () { return run_hook_js_1.egressRoutes; } });
|
|
39
32
|
//# sourceMappingURL=e2e.js.map
|
package/dist/eval.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ToolCall, type Trace } from "./harness-test.js";
|
|
2
2
|
import { type CacheMode } from "./eval-cache.js";
|
|
3
|
+
import type { Check, CheckJSON } from "./check.js";
|
|
4
|
+
import { type Comparison } from "./stats.js";
|
|
3
5
|
/** One arm of the comparison: fixture overrides + settings (hooks) for this arm. */
|
|
4
6
|
export interface EvalArm {
|
|
5
7
|
/** Files written on top of the base fixture for this arm. */
|
|
@@ -172,6 +174,127 @@ export type AgentRunner = (args: AgentRunArgs) => Promise<RunOut>;
|
|
|
172
174
|
* {@link runEvalWith} with the real agent runner.
|
|
173
175
|
*/
|
|
174
176
|
export declare function runEval<M extends Metrics>(spec: EvalSpec<M>): Promise<EvalReport>;
|
|
177
|
+
/** A task run N times, scored against a `Trace` check vocabulary. */
|
|
178
|
+
export interface MeasureSpec {
|
|
179
|
+
/** Base fixture files written for every run (path → contents). */
|
|
180
|
+
readonly fixture?: Record<string, string>;
|
|
181
|
+
/** `.claude/settings.json` (hooks/permissions) for the run. */
|
|
182
|
+
readonly settings?: unknown;
|
|
183
|
+
/** A real plugin/repo to load (materialized) — see `EvalArm.plugin`. */
|
|
184
|
+
readonly plugin?: string;
|
|
185
|
+
/** A complete plugin dir to install natively (`--plugin-dir`) so skills activate. */
|
|
186
|
+
readonly pluginDir?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Stub each skill BODY in `pluginDir` (frontmatter/trigger surface kept) before
|
|
189
|
+
* the run — for checks about whether a skill FIRES (`skill()`), not what it
|
|
190
|
+
* produces. A selected skill stops at selection instead of running its (often
|
|
191
|
+
* expensive) procedure, so a description/firing run costs a fraction of the
|
|
192
|
+
* tokens. Do NOT combine with `judged`/quality checks: the body is gone, so
|
|
193
|
+
* there's nothing to grade. Requires `pluginDir`. See {@link stubSkillBody}.
|
|
194
|
+
*/
|
|
195
|
+
readonly stubSkillBodies?: boolean;
|
|
196
|
+
/** The task prompt given to the agent. */
|
|
197
|
+
readonly task: string;
|
|
198
|
+
/**
|
|
199
|
+
* The checks to score across the trials. Any check over the run is accepted:
|
|
200
|
+
* `Trace` checks (`tool`/`skill`/`output`/`mcp`/`judged`) and resource checks
|
|
201
|
+
* (`cost`/`latency`/`tokens`, which read the eval-only `usage`) — all fit
|
|
202
|
+
* `Check<RunContext>`.
|
|
203
|
+
*/
|
|
204
|
+
readonly checks: readonly Check<RunContext>[];
|
|
205
|
+
/** Trials. Default 5. */
|
|
206
|
+
readonly trials?: number;
|
|
207
|
+
/** Model alias. Default "sonnet" — measure on the model your users run. */
|
|
208
|
+
readonly model?: string;
|
|
209
|
+
/** Tools the agent may use. */
|
|
210
|
+
readonly allowedTools?: readonly string[];
|
|
211
|
+
/** Per-run timeout ms. */
|
|
212
|
+
readonly timeoutMs?: number;
|
|
213
|
+
/** Seconds between runs. */
|
|
214
|
+
readonly spacingSec?: number;
|
|
215
|
+
}
|
|
216
|
+
/** One check's measured rate across the trials. */
|
|
217
|
+
export interface CheckRate {
|
|
218
|
+
readonly check: CheckJSON;
|
|
219
|
+
/** Fraction of trials the check passed (0..1). */
|
|
220
|
+
readonly rate: number;
|
|
221
|
+
/** Standard error of the rate. */
|
|
222
|
+
readonly se: number;
|
|
223
|
+
/** pass^k — 1 iff the check passed on EVERY trial. */
|
|
224
|
+
readonly passK: number;
|
|
225
|
+
/** Trials observed. */
|
|
226
|
+
readonly n: number;
|
|
227
|
+
}
|
|
228
|
+
export interface CheckReport {
|
|
229
|
+
readonly n: number;
|
|
230
|
+
readonly perCheck: readonly CheckRate[];
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Score a check vocabulary across trials — the scored counterpart to
|
|
234
|
+
* `assertChecks` (strict). Each check yields a `rate ± se` and `pass^k` over `n`
|
|
235
|
+
* runs. Reuses the tested `runEvalWith` aggregation (one arm), so the loop,
|
|
236
|
+
* cache, concurrency, and stats come for free. Exported with an injectable
|
|
237
|
+
* `runner` so the orchestration is unit-testable without a model.
|
|
238
|
+
*/
|
|
239
|
+
export declare function measureWith(spec: MeasureSpec, runner: AgentRunner): Promise<CheckReport>;
|
|
240
|
+
/** Score a check vocabulary across trials against the real `claude` CLI. */
|
|
241
|
+
export declare function measure(spec: MeasureSpec): Promise<CheckReport>;
|
|
242
|
+
/** A task scored against checks across NAMED arms (the harness variable on/off). */
|
|
243
|
+
export interface ArmsMeasureSpec {
|
|
244
|
+
readonly fixture?: Record<string, string>;
|
|
245
|
+
/** The arms to compare (settings / plugin / pluginDir per arm). */
|
|
246
|
+
readonly arms: Record<string, EvalArm>;
|
|
247
|
+
readonly task: string;
|
|
248
|
+
readonly checks: readonly Check<RunContext>[];
|
|
249
|
+
/**
|
|
250
|
+
* Stub each arm's skill BODIES (frontmatter kept) before the run — the A/B
|
|
251
|
+
* counterpart to {@link MeasureSpec.stubSkillBodies}. For firing comparisons
|
|
252
|
+
* (does description variant A fire more than B?), every arm that sets
|
|
253
|
+
* `pluginDir` is repackaged with bodies stripped so each run stops at
|
|
254
|
+
* selection — a fraction of the tokens. Arms without a `pluginDir` are left
|
|
255
|
+
* untouched. Don't combine with `judged`/quality checks. See {@link stubSkillBody}.
|
|
256
|
+
*/
|
|
257
|
+
readonly stubSkillBodies?: boolean;
|
|
258
|
+
readonly trials?: number;
|
|
259
|
+
readonly model?: string;
|
|
260
|
+
readonly allowedTools?: readonly string[];
|
|
261
|
+
readonly timeoutMs?: number;
|
|
262
|
+
readonly spacingSec?: number;
|
|
263
|
+
}
|
|
264
|
+
/** Per-arm {@link CheckReport}s — `arms[name].perCheck[i]` aligns across arms. */
|
|
265
|
+
export interface ArmsCheckReport {
|
|
266
|
+
readonly arms: Record<string, CheckReport>;
|
|
267
|
+
}
|
|
268
|
+
/** Score checks across arms (injectable runner). Reuses `runEvalWith`. */
|
|
269
|
+
export declare function measureArmsWith(spec: ArmsMeasureSpec, runner: AgentRunner): Promise<ArmsCheckReport>;
|
|
270
|
+
/** Score checks across arms against the real `claude` CLI. */
|
|
271
|
+
export declare function measureArms(spec: ArmsMeasureSpec): Promise<ArmsCheckReport>;
|
|
272
|
+
/**
|
|
273
|
+
* Welch significance on one check's rate between two arms (`arm` vs `baseline`),
|
|
274
|
+
* by index in `perCheck`. So "the gated arm resolves the skill significantly more
|
|
275
|
+
* than vanilla" is a p-value, not a vibe. Reuses `welchTTest` from stats.ts.
|
|
276
|
+
*/
|
|
277
|
+
export declare function compareCheck(report: ArmsCheckReport, baseline: string, arm: string, checkIndex: number): Comparison;
|
|
278
|
+
/** Format a {@link CheckReport}: one line per check with its rate ± se and pass^k. */
|
|
279
|
+
export declare function formatCheckReport(report: CheckReport): string;
|
|
280
|
+
/**
|
|
281
|
+
* The scored gate (Phase 4): throw if any check's measured rate is below `min` —
|
|
282
|
+
* the `measure` counterpart to `assertChecks` (strict). Reads the rate, not a
|
|
283
|
+
* single run, so it never trips on one noisy trial.
|
|
284
|
+
*/
|
|
285
|
+
export declare function assertRates(report: CheckReport, opts: {
|
|
286
|
+
min: number;
|
|
287
|
+
}): void;
|
|
288
|
+
/**
|
|
289
|
+
* Serialize a {@link CheckReport} to JUnit XML (Phase 4) — each check a
|
|
290
|
+
* `<testcase>`, failing when its rate is below `min`. Because a check is *data*,
|
|
291
|
+
* this falls out for free: CI test reporters, regression baselines, and a
|
|
292
|
+
* promptfoo bridge all consume the same shape.
|
|
293
|
+
*/
|
|
294
|
+
export declare function checkReportToJUnit(report: CheckReport, opts?: {
|
|
295
|
+
min?: number;
|
|
296
|
+
name?: string;
|
|
297
|
+
}): string;
|
|
175
298
|
/** Parse per-run cost/latency/tokens from a stream — pure, model-free. */
|
|
176
299
|
export declare function parseUsage(stdout: string): EvalUsage;
|
|
177
300
|
/** Aggregate per-run metrics: mean for numbers, fraction-true (0..1) for booleans. */
|
|
@@ -209,8 +332,19 @@ export declare function formatEvalReport(report: EvalReport): string;
|
|
|
209
332
|
* (reuse the bare predicates, e.g. `(t) => skillResolved(t, "x:y")`).
|
|
210
333
|
*/
|
|
211
334
|
export interface TriggerRateSpec {
|
|
212
|
-
/**
|
|
213
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Plugin dir installed natively (`--plugin-dir`) so its skills/commands
|
|
337
|
+
* activate. Provide this OR {@link skillsDir}, not both.
|
|
338
|
+
*/
|
|
339
|
+
readonly pluginDir?: string;
|
|
340
|
+
/**
|
|
341
|
+
* A directory of LOOSE skills (`<skillsDir>/<name>/SKILL.md`, e.g. a repo's
|
|
342
|
+
* `.claude/skills`) to trigger-test directly. vigiles packages them into a
|
|
343
|
+
* throwaway `--plugin-dir` for you and removes it afterward — the one-liner
|
|
344
|
+
* for repo-local skills that aren't a published plugin. Provide this OR
|
|
345
|
+
* {@link pluginDir}, not both.
|
|
346
|
+
*/
|
|
347
|
+
readonly skillsDir?: string;
|
|
214
348
|
/** The varied prompts to test the trigger against. */
|
|
215
349
|
readonly prompts: readonly string[];
|
|
216
350
|
/**
|
|
@@ -223,6 +357,30 @@ export interface TriggerRateSpec {
|
|
|
223
357
|
readonly irrelevantPrompts?: readonly string[];
|
|
224
358
|
/** Did the behaviour fire on this run? e.g. `(t) => skillResolved(t, "x:y")`. */
|
|
225
359
|
readonly fired: (trace: Trace) => boolean;
|
|
360
|
+
/**
|
|
361
|
+
* Replace each skill's BODY with a no-op stub (keeping its frontmatter — name +
|
|
362
|
+
* description) before running. Trigger-rate is decided by the frontmatter alone
|
|
363
|
+
* (the model selects a skill before its body loads), so stubbing the body can't
|
|
364
|
+
* change what's measured but stops the run from executing an expensive
|
|
365
|
+
* procedure once the skill fires — far cheaper, faster, side-effect-free. All
|
|
366
|
+
* skills' descriptions stay present, so the selection competition is faithful.
|
|
367
|
+
* Default false (off) for now; recommended `true` for trigger evals. See
|
|
368
|
+
* {@link stubSkillBody}.
|
|
369
|
+
*/
|
|
370
|
+
readonly stubSkillBodies?: boolean;
|
|
371
|
+
/**
|
|
372
|
+
* Minimum number of prompts each set (relevant + irrelevant) must have. A
|
|
373
|
+
* handful of prompts can't tell a real recall/precision rate from noise, so
|
|
374
|
+
* the run is rejected before it spends a token. Default 10; lower it
|
|
375
|
+
* deliberately for a genuinely narrow skill.
|
|
376
|
+
*/
|
|
377
|
+
readonly minPrompts?: number;
|
|
378
|
+
/**
|
|
379
|
+
* Reject the run when two prompts in a set are closer than this in NCD
|
|
380
|
+
* (gzip-based distance, 0..1; 0 = identical) — near-duplicate prompts inflate
|
|
381
|
+
* a rate without testing varied phrasings. Default 0.3 (the rule-dup threshold).
|
|
382
|
+
*/
|
|
383
|
+
readonly minDistance?: number;
|
|
226
384
|
/** Trials per prompt. Default 1. */
|
|
227
385
|
readonly trials?: number;
|
|
228
386
|
/** Model alias. Default "haiku". */
|
|
@@ -263,6 +421,63 @@ export interface TriggerRateReport {
|
|
|
263
421
|
/** Per-prompt stats for the irrelevant set. Present with irrelevant prompts. */
|
|
264
422
|
readonly perIrrelevant?: readonly PromptTriggerStat[];
|
|
265
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Package loose `<skillsDir>/<name>/SKILL.md` skills into a throwaway plugin dir
|
|
426
|
+
* that `claude --plugin-dir` accepts — so repo-local skills (e.g. `.claude/skills`)
|
|
427
|
+
* can be trigger-tested without hand-rolling a `plugin.json`. Writes a minimal
|
|
428
|
+
* `.claude-plugin/plugin.json` and copies each `<name>/` (recursively, so
|
|
429
|
+
* `references/` etc. come along) under `skills/<name>/`. Returns the temp plugin
|
|
430
|
+
* dir; the caller removes it (`measureTriggerRate` does). Throws if the directory
|
|
431
|
+
* is missing or holds no `<name>/SKILL.md`.
|
|
432
|
+
*/
|
|
433
|
+
export declare function packageSkillsDir(skillsDir: string, opts?: {
|
|
434
|
+
name?: string;
|
|
435
|
+
stub?: boolean;
|
|
436
|
+
}): string;
|
|
437
|
+
/**
|
|
438
|
+
* Rewrite a SKILL.md to keep its YAML frontmatter (the trigger surface — name +
|
|
439
|
+
* description) but replace the body with a no-op stub. Trigger-rate is a property
|
|
440
|
+
* of the frontmatter ONLY: the model picks a skill from its name + description
|
|
441
|
+
* before the body is ever loaded, so the body is causally downstream of selection
|
|
442
|
+
* and irrelevant to whether the skill fires. Stubbing it lets a trigger run stop
|
|
443
|
+
* AT selection instead of executing an expensive multi-step procedure — cheaper,
|
|
444
|
+
* faster, and side-effect-free, without changing what's measured. Pure.
|
|
445
|
+
*/
|
|
446
|
+
export declare function stubSkillBody(skillMd: string): string;
|
|
447
|
+
/**
|
|
448
|
+
* Build a throwaway plugin dir mirroring `pluginDir`'s skills with their BODIES
|
|
449
|
+
* stripped (frontmatter kept) — the trigger surface a description/firing check
|
|
450
|
+
* needs, without paying to run each skill's procedure. Keeps the original plugin
|
|
451
|
+
* NAME so `<name>:<skill>` ids still match. The caller removes the returned dir.
|
|
452
|
+
* See {@link stubSkillBody} for why the body is irrelevant to selection.
|
|
453
|
+
*/
|
|
454
|
+
export declare function stubbedPluginDir(pluginDir: string): string;
|
|
455
|
+
/**
|
|
456
|
+
* Distance between two prompts in ~0..1 (0 = identical, higher = more
|
|
457
|
+
* different) via Normalized Compression Distance over the normalized text.
|
|
458
|
+
* Reuses {@link ncd} from the proof engine.
|
|
459
|
+
*/
|
|
460
|
+
export declare function promptDistance(a: string, b: string): number;
|
|
461
|
+
export interface PromptDiversityIssue {
|
|
462
|
+
readonly kind: "too-few" | "too-similar";
|
|
463
|
+
readonly message: string;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Deterministically check a prompt set is big and varied enough to measure a
|
|
467
|
+
* trigger rate: at least `minPrompts` entries, and no two closer than
|
|
468
|
+
* `minDistance` in NCD. Pure — no model. `label` names the set in messages.
|
|
469
|
+
*/
|
|
470
|
+
export declare function checkPromptDiversity(prompts: readonly string[], opts?: {
|
|
471
|
+
minPrompts?: number;
|
|
472
|
+
minDistance?: number;
|
|
473
|
+
label?: string;
|
|
474
|
+
}): PromptDiversityIssue[];
|
|
475
|
+
/** Throw if a prompt set isn't big/varied enough. See {@link checkPromptDiversity}. */
|
|
476
|
+
export declare function assertPromptDiversity(prompts: readonly string[], opts?: {
|
|
477
|
+
minPrompts?: number;
|
|
478
|
+
minDistance?: number;
|
|
479
|
+
label?: string;
|
|
480
|
+
}): void;
|
|
266
481
|
/**
|
|
267
482
|
* Trigger-rate orchestration — every prompt × trial via `runner`, the `fired`
|
|
268
483
|
* predicate evaluated per run and aggregated into an overall + per-prompt rate.
|