vigiles 2.5.0 → 3.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/.claude-plugin/plugin.json +9 -0
- package/README.md +73 -434
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
- package/dist/adapters/claude-code/egress-proxy.js +60 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
- package/dist/adapters/claude-code/eval-baseline.js +173 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/adapters/claude-code/run-hook.d.ts +204 -0
- package/dist/adapters/claude-code/run-hook.js +418 -0
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claude-code.js +32 -0
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +60 -11
- package/dist/harness-assert.js +111 -9
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +35 -16
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +103 -0
- package/skills/generate-rule/SKILL.md +64 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-hook.d.ts +0 -77
- package/dist/run-hook.js +0 -80
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessRuntime — the runtime/transport PORT (hexagonal transport axis). The
|
|
3
|
+
* facts the test tiers need to actually drive a harness: the agent binary to
|
|
4
|
+
* spawn, and the env a no-key mock model is reached through (the base-URL var,
|
|
5
|
+
* the API-key var, and a dummy key the mock ignores). These were hard-coded
|
|
6
|
+
* `"claude"` / `ANTHROPIC_*` literals in `harness-test.ts` and `eval.ts`; behind
|
|
7
|
+
* this interface a second harness (Codex) supplies its own `HarnessRuntime`
|
|
8
|
+
* (a different binary + its model's env) and the runners spawn it the same way.
|
|
9
|
+
*
|
|
10
|
+
* The Claude Code implementation is `claudeCodeRuntime` in
|
|
11
|
+
* `src/adapters/claude-code/runtime.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export interface HarnessRuntime {
|
|
14
|
+
/** Stable identifier, e.g. "claude-code". */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/** The CLI binary that runs the agent, e.g. "claude". */
|
|
17
|
+
readonly agentBinary: string;
|
|
18
|
+
/** Env var pointing the client at the mock model, e.g. "ANTHROPIC_BASE_URL". */
|
|
19
|
+
readonly modelBaseUrlEnv: string;
|
|
20
|
+
/** Env var carrying the (dummy) API key, e.g. "ANTHROPIC_API_KEY". */
|
|
21
|
+
readonly modelApiKeyEnv: string;
|
|
22
|
+
/** A dummy key value the mock ignores — avoids needing real auth. */
|
|
23
|
+
readonly mockApiKey: string;
|
|
24
|
+
/**
|
|
25
|
+
* How to point the spawned binary at a mock model served at `baseUrl` — the
|
|
26
|
+
* args to add to the binary's argv and the env to spawn it with. For Claude
|
|
27
|
+
* Code this is env-only (`ANTHROPIC_BASE_URL` + a dummy key, no args); for
|
|
28
|
+
* Codex it is the keyless `-c model_providers.mock.*` flag recipe plus a
|
|
29
|
+
* dummy-key env. Behind one method so the runner wires either harness the
|
|
30
|
+
* same way, without knowing which transport axis (env var vs config flags) a
|
|
31
|
+
* given harness uses.
|
|
32
|
+
*/
|
|
33
|
+
wireMock(baseUrl: string): {
|
|
34
|
+
readonly args: readonly string[];
|
|
35
|
+
readonly env: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HarnessDialect } from "./dialect.js";
|
|
1
2
|
/** A parsed rule from a markdown instruction file. */
|
|
2
3
|
export interface ParsedRule {
|
|
3
4
|
title: string;
|
|
@@ -68,6 +69,21 @@ export interface OrphansConfig {
|
|
|
68
69
|
*/
|
|
69
70
|
exclude?: readonly string[];
|
|
70
71
|
}
|
|
72
|
+
/** Options for the untested-surface check. */
|
|
73
|
+
export interface TestCoverageConfig {
|
|
74
|
+
/** Scan skills. Default true. */
|
|
75
|
+
skills?: boolean;
|
|
76
|
+
/** Scan subagents. Default true. */
|
|
77
|
+
agents?: boolean;
|
|
78
|
+
/** Scan hook scripts referenced from plugin.json / settings.json. Default true. */
|
|
79
|
+
hooks?: boolean;
|
|
80
|
+
/** Require a test for user-invoked (disable-model-invocation) skills. Default false. */
|
|
81
|
+
includeUserInvokedSkills?: boolean;
|
|
82
|
+
/** Globs of test files that count as coverage. */
|
|
83
|
+
testGlobs?: readonly string[];
|
|
84
|
+
/** Extra ignore globs. */
|
|
85
|
+
exclude?: readonly string[];
|
|
86
|
+
}
|
|
71
87
|
export interface RulesConfig {
|
|
72
88
|
/** Require .spec.ts for CLAUDE.md / AGENTS.md. Default: "warn". */
|
|
73
89
|
"require-spec"?: RuleSeverity;
|
|
@@ -77,6 +93,16 @@ export interface RulesConfig {
|
|
|
77
93
|
integrity?: RuleSeverity;
|
|
78
94
|
/** Enforce minimum spec coverage thresholds. Default: false. ESLint-style: ["warn", { scripts: 50 }]. */
|
|
79
95
|
coverage?: RuleWithOptions<CoverageThresholds>;
|
|
96
|
+
/** Flag skills/agents/hooks with no test or eval. Default: "warn". */
|
|
97
|
+
"untested-surface"?: RuleWithOptions<TestCoverageConfig>;
|
|
98
|
+
/**
|
|
99
|
+
* Nudge (or block) when an instruction file has code-shaped references that
|
|
100
|
+
* aren't expressed as vigiles marks (so the audit can't verify them), or a
|
|
101
|
+
* `vigiles:symbol` mark that points at a missing symbol. Drives the
|
|
102
|
+
* PostToolUse refs-hook: "warn" (default) → a non-blocking nudge, "error" →
|
|
103
|
+
* block the edit, false → off.
|
|
104
|
+
*/
|
|
105
|
+
"unmarked-refs"?: RuleSeverity;
|
|
80
106
|
}
|
|
81
107
|
/** Extract severity from a rule value (handles both simple and tuple forms). */
|
|
82
108
|
export declare function ruleSeverity<T>(rule: RuleWithOptions<T> | undefined): RuleSeverity;
|
|
@@ -113,6 +139,9 @@ export interface ValidateOptions {
|
|
|
113
139
|
ruleMarkers?: MarkerType[];
|
|
114
140
|
rules?: RulesConfig;
|
|
115
141
|
filePath?: string;
|
|
142
|
+
/** Injected harness dialect; its instructionTargets define recognized
|
|
143
|
+
* instruction filenames. Omitted → the validator's built-in default set. */
|
|
144
|
+
dialect?: HarnessDialect;
|
|
116
145
|
}
|
|
117
146
|
/** Options for validatePaths(). */
|
|
118
147
|
export interface ValidatePathsOptions {
|
|
@@ -3,7 +3,7 @@ export type { ParsedRule, ValidationError, ValidationResult, ReadResult, FileRes
|
|
|
3
3
|
export declare function findInstructionFiles(cwd?: string, configFiles?: string[]): string[];
|
|
4
4
|
export declare function loadConfig(): VigilesConfig;
|
|
5
5
|
export declare function parseRules(content: string, { ruleMarkers }?: ParseOptions): ParsedRule[];
|
|
6
|
-
export declare function validate(content: string, { ruleMarkers, rules: rulesConfig, filePath }?: ValidateOptions): ValidationResult;
|
|
6
|
+
export declare function validate(content: string, { ruleMarkers, rules: rulesConfig, filePath, dialect }?: ValidateOptions): ValidationResult;
|
|
7
7
|
export declare function readInstructionFile(filePath: string, options?: ReadOptions): ReadResult;
|
|
8
8
|
export declare function expandGlobs(patterns: string[]): string[];
|
|
9
9
|
export declare function validatePaths(paths: string[], { followSymlinks, ruleMarkers, rules: rulesConfig, }?: ValidatePathsOptions): ValidatePathsResult;
|
|
@@ -24,12 +24,19 @@ const VALID_MARKERS = ["headings", "checkboxes"];
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
// Default config
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
27
|
+
// The instruction filenames vigiles recognizes when no dialect is injected — a
|
|
28
|
+
// validator-level default, not a harness dialect (the concrete dialects live in
|
|
29
|
+
// the adapters; an injected ValidateOptions.dialect overrides this).
|
|
30
|
+
const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
31
|
+
// The default instruction file to validate when no config names one.
|
|
32
|
+
const DEFAULT_FILES = [INSTRUCTION_FILES[0]];
|
|
28
33
|
const DEFAULT_RULES = {
|
|
29
34
|
"require-spec": "warn",
|
|
30
35
|
"require-skill-spec": "warn",
|
|
31
36
|
integrity: "warn",
|
|
32
37
|
coverage: false,
|
|
38
|
+
"untested-surface": "warn",
|
|
39
|
+
"unmarked-refs": "warn",
|
|
33
40
|
};
|
|
34
41
|
const DEFAULT_CONFIG = {
|
|
35
42
|
ruleMarkers: ["headings", "checkboxes"],
|
|
@@ -129,7 +136,7 @@ function parseRules(content, { ruleMarkers } = {}) {
|
|
|
129
136
|
// ---------------------------------------------------------------------------
|
|
130
137
|
// Core validation
|
|
131
138
|
// ---------------------------------------------------------------------------
|
|
132
|
-
function validate(content, { ruleMarkers, rules: rulesConfig, filePath } = {}) {
|
|
139
|
+
function validate(content, { ruleMarkers, rules: rulesConfig, filePath, dialect } = {}) {
|
|
133
140
|
const activeRules = rulesConfig ?? DEFAULT_RULES;
|
|
134
141
|
const parsedRules = parseRules(content, { ruleMarkers });
|
|
135
142
|
const enforced = parsedRules.filter((r) => r.enforcement === "enforced").length;
|
|
@@ -141,7 +148,8 @@ function validate(content, { ruleMarkers, rules: rulesConfig, filePath } = {}) {
|
|
|
141
148
|
const disableComment = /<!--\s*vigiles-disable\s+require-spec\s*-->/;
|
|
142
149
|
if (filePath) {
|
|
143
150
|
const basename = (0, node_path_1.basename)(filePath);
|
|
144
|
-
const
|
|
151
|
+
const recognized = dialect?.instructionTargets ?? INSTRUCTION_FILES;
|
|
152
|
+
const isInstruction = recognized.includes(basename);
|
|
145
153
|
const isSkill = basename === "SKILL.md";
|
|
146
154
|
// --- require-spec (CLAUDE.md / AGENTS.md) ---
|
|
147
155
|
const specSeverity = activeRules["require-spec"];
|
|
@@ -259,7 +267,30 @@ function expandGlobs(patterns) {
|
|
|
259
267
|
function validatePaths(paths, { followSymlinks = false, ruleMarkers, rules: rulesConfig, } = {}) {
|
|
260
268
|
const fileResults = [];
|
|
261
269
|
let allValid = true;
|
|
270
|
+
// Maps a real (symlink-resolved) path → the first path validated for it, so a
|
|
271
|
+
// symlinked/synced CLAUDE.md⇄AGENTS.md mirror is validated ONCE on the real
|
|
272
|
+
// file instead of double-firing require-spec on the mirror's name (sync-tool-
|
|
273
|
+
// compatibility.md req 7). Recorded only on a successful validation, so a
|
|
274
|
+
// symlink seen first (and skipped) never shadows its real target.
|
|
275
|
+
const seenReal = new Map();
|
|
262
276
|
for (const filePath of paths) {
|
|
277
|
+
let real;
|
|
278
|
+
try {
|
|
279
|
+
real = (0, node_fs_1.realpathSync)(filePath);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
real = (0, node_path_1.resolve)(filePath);
|
|
283
|
+
}
|
|
284
|
+
const prior = seenReal.get(real);
|
|
285
|
+
if (prior !== undefined) {
|
|
286
|
+
fileResults.push({
|
|
287
|
+
path: filePath,
|
|
288
|
+
skipped: true,
|
|
289
|
+
reason: `mirror of ${prior} (same file via symlink/sync) — validated once`,
|
|
290
|
+
result: null,
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
263
294
|
const { content, skipped, reason } = readInstructionFile(filePath, {
|
|
264
295
|
followSymlinks,
|
|
265
296
|
});
|
|
@@ -274,10 +305,16 @@ function validatePaths(paths, { followSymlinks = false, ruleMarkers, rules: rule
|
|
|
274
305
|
allValid = false;
|
|
275
306
|
continue;
|
|
276
307
|
}
|
|
308
|
+
// Attribute require-spec/integrity to the REAL file when this path is a
|
|
309
|
+
// symlink, so a symlinked AGENTS.md resolves to CLAUDE.md's spec rather than
|
|
310
|
+
// a nonexistent AGENTS.md.spec.ts. Non-symlinks keep the original path
|
|
311
|
+
// verbatim (behaviour-preserving).
|
|
312
|
+
const attributePath = real !== (0, node_path_1.resolve)(filePath) ? real : filePath;
|
|
313
|
+
seenReal.set(real, filePath);
|
|
277
314
|
const result = validate(content, {
|
|
278
315
|
ruleMarkers,
|
|
279
316
|
rules: rulesConfig,
|
|
280
|
-
filePath,
|
|
317
|
+
filePath: attributePath,
|
|
281
318
|
});
|
|
282
319
|
fileResults.push({ path: filePath, skipped: false, reason: null, result });
|
|
283
320
|
if (!result.valid)
|
package/dist/e2e.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vigiles/e2e` — the **deterministic** end-to-end tier (real sandbox + real
|
|
3
|
+
* network, but a definite pass/fail).
|
|
4
|
+
*
|
|
5
|
+
* Re-exports everything in [`vigiles/integration`](./integration.ts) and adds the
|
|
6
|
+
* real-egress capability: `egressRoutes()` (probe whether allowlisted egress can
|
|
7
|
+
* actually route) and — by re-export — `runHook` used with `egress: { allow }`
|
|
8
|
+
* (allowlisted real outbound). Capability contract: needs a **routable rootless
|
|
9
|
+
* sandbox + real network**, and each test self-skips via `egressRoutes()` where
|
|
10
|
+
* that's unavailable. Still a **verification** tier — you assert pass/fail.
|
|
11
|
+
*
|
|
12
|
+
* NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`). Those are a
|
|
13
|
+
* different axis — **non-deterministic measurement** (real model, mean ± se), run
|
|
14
|
+
* via `vigiles eval` on `*.eval.mjs`. Import them from
|
|
15
|
+
* [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
|
|
16
|
+
*/
|
|
17
|
+
export * from "./integration.js";
|
|
18
|
+
export { egressRoutes } from "./adapters/claude-code/run-hook.js";
|
|
19
|
+
//# sourceMappingURL=e2e.d.ts.map
|
package/dist/e2e.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.egressRoutes = void 0;
|
|
18
|
+
/**
|
|
19
|
+
* `vigiles/e2e` — the **deterministic** end-to-end tier (real sandbox + real
|
|
20
|
+
* network, but a definite pass/fail).
|
|
21
|
+
*
|
|
22
|
+
* Re-exports everything in [`vigiles/integration`](./integration.ts) and adds the
|
|
23
|
+
* real-egress capability: `egressRoutes()` (probe whether allowlisted egress can
|
|
24
|
+
* actually route) and — by re-export — `runHook` used with `egress: { allow }`
|
|
25
|
+
* (allowlisted real outbound). Capability contract: needs a **routable rootless
|
|
26
|
+
* sandbox + real network**, and each test self-skips via `egressRoutes()` where
|
|
27
|
+
* that's unavailable. Still a **verification** tier — you assert pass/fail.
|
|
28
|
+
*
|
|
29
|
+
* NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`). Those are a
|
|
30
|
+
* different axis — **non-deterministic measurement** (real model, mean ± se), run
|
|
31
|
+
* via `vigiles eval` on `*.eval.mjs`. Import them from
|
|
32
|
+
* [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
|
|
33
|
+
*/
|
|
34
|
+
__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("./adapters/claude-code/run-hook.js");
|
|
38
|
+
Object.defineProperty(exports, "egressRoutes", { enumerable: true, get: function () { return run_hook_js_1.egressRoutes; } });
|
|
39
|
+
//# sourceMappingURL=e2e.js.map
|
package/dist/harness-assert.d.ts
CHANGED
|
@@ -13,19 +13,32 @@
|
|
|
13
13
|
* `expect(...).toHaveCreated(...)` sugar. The signature is identical for
|
|
14
14
|
* vitest and jest, so the same object supports both.
|
|
15
15
|
*/
|
|
16
|
-
import { type HarnessTestSpec, type HarnessTestResult, type ToolCall, type Trace } from "./harness-test.js";
|
|
17
|
-
import type { EvalReport, TriggerRateReport } from "./eval.js";
|
|
18
|
-
import type { HookRunResult } from "./run-hook.js";
|
|
19
|
-
import type { OutputContract } from "./spec.js";
|
|
20
|
-
import { type ParsedAgentResult } from "./agent-result.js";
|
|
21
|
-
|
|
22
|
-
export
|
|
16
|
+
import { type HarnessTestSpec, type HarnessTestResult, type ToolCall, type Trace } from "./adapters/claude-code/harness-test.js";
|
|
17
|
+
import type { EvalReport, TriggerRateReport } from "./adapters/claude-code/eval.js";
|
|
18
|
+
import type { HookRunResult, EgressAttempt } from "./adapters/claude-code/run-hook.js";
|
|
19
|
+
import type { OutputContract } from "./core/spec.js";
|
|
20
|
+
import { type ParsedAgentResult } from "./adapters/claude-code/agent-result.js";
|
|
21
|
+
import { type BaselineFile, type DiffOptions } from "./adapters/claude-code/eval-baseline.js";
|
|
22
|
+
export { compareArms } from "./adapters/claude-code/stats.js";
|
|
23
|
+
export type { Comparison } from "./adapters/claude-code/stats.js";
|
|
24
|
+
export { diffReports, toBaselineFile, parseBaselineFile, readBaseline, writeBaseline, formatBaselineDiff, diffToJUnit, } from "./adapters/claude-code/eval-baseline.js";
|
|
25
|
+
export type { BaselineFile, BaselineDiff, MetricDiff, DiffStatus, DiffOptions, } from "./adapters/claude-code/eval-baseline.js";
|
|
23
26
|
/**
|
|
24
27
|
* Run a harness test, hand the result to `fn`, and always clean up the sandbox.
|
|
25
28
|
* Returns whatever `fn` returns. Use this instead of calling `cleanup()` by
|
|
26
29
|
* hand — it survives assertion failures.
|
|
27
30
|
*/
|
|
28
31
|
export declare function withHarness<T>(spec: HarnessTestSpec, fn: (r: HarnessTestResult) => T | Promise<T>): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Mark the current `*.harness.*` / `*.eval.*` SCRIPT as SKIPPED and exit, so
|
|
34
|
+
* `vigiles test` / `vigiles eval` report a loud `⊘ SKIPPED` instead of a silent
|
|
35
|
+
* `✓` — e.g. the deterministic tier when the `claude` CLI isn't installed. A skip
|
|
36
|
+
* never fails the run. Exit 77 is the runner's `SKIP_EXIT_CODE` (run-scripts.ts).
|
|
37
|
+
*
|
|
38
|
+
* For standalone CLI-fallback scripts only — inside a runner (vitest / jest /
|
|
39
|
+
* node:test) use that runner's own skip, not a process exit.
|
|
40
|
+
*/
|
|
41
|
+
export declare function skip(reason?: string): never;
|
|
29
42
|
/** Assert the sandbox contains `path` (a hook/agent side-effect file). */
|
|
30
43
|
export declare function assertCreated(r: HarnessTestResult, path: string): void;
|
|
31
44
|
/** Assert the sandbox does NOT contain `path` (e.g. a blocked action's output). */
|
|
@@ -36,6 +49,28 @@ export declare function assertServedTurns(r: HarnessTestResult, n: number): void
|
|
|
36
49
|
export declare function assertHookBlocked(r: HookRunResult): void;
|
|
37
50
|
/** Assert a `runHook` result allowed (did not block). */
|
|
38
51
|
export declare function assertHookAllowed(r: HookRunResult): void;
|
|
52
|
+
/** Anything carrying recorded egress attempts (a runHook recordEgress result). */
|
|
53
|
+
interface HasEgress {
|
|
54
|
+
readonly egress: readonly EgressAttempt[];
|
|
55
|
+
}
|
|
56
|
+
/** The `host:port` strings a run attempted, e.g. `["registry.npmjs.org:443"]`. */
|
|
57
|
+
export declare function egressHosts(r: HasEgress): string[];
|
|
58
|
+
/** Assert the confined run made NO network egress attempt at all. */
|
|
59
|
+
export declare function assertNoEgress(r: HasEgress): void;
|
|
60
|
+
/**
|
|
61
|
+
* Assert every egress attempt went to an allowed host. `allowed` matches a host
|
|
62
|
+
* (exact string or regex), or a specific `host:port`. Any attempt outside the
|
|
63
|
+
* allowlist fails, naming the offender — exfil / unexpected-registry detection.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assertEgressOnly(r: HasEgress, allowed: ReadonlyArray<string | RegExp>): void;
|
|
66
|
+
/** Anything carrying recorded file writes (a confined runHook result). */
|
|
67
|
+
interface HasWrites {
|
|
68
|
+
readonly filesWritten: readonly string[];
|
|
69
|
+
}
|
|
70
|
+
/** Assert the run wrote NO file matching `pattern` (substring or regex). */
|
|
71
|
+
export declare function assertNoWrite(r: HasWrites, pattern: string | RegExp): void;
|
|
72
|
+
/** Assert every file the run wrote matches one of `allowed` (substring or regex). */
|
|
73
|
+
export declare function assertWroteOnly(r: HasWrites, allowed: ReadonlyArray<string | RegExp>): void;
|
|
39
74
|
/**
|
|
40
75
|
* Assert the worker's output is a success result, and return its `value`. With a
|
|
41
76
|
* `contract`, the value is validated against the success shape (a wrong/missing
|
|
@@ -208,12 +243,26 @@ export declare function assertImproves(report: EvalReport, opts: {
|
|
|
208
243
|
alpha?: number;
|
|
209
244
|
}): void;
|
|
210
245
|
/**
|
|
211
|
-
* Assert
|
|
212
|
-
*
|
|
213
|
-
*
|
|
246
|
+
* Assert the current run has not *regressed* against a committed baseline — the
|
|
247
|
+
* CI gate (Phase C). A regression is an arm×metric that moved **significantly in
|
|
248
|
+
* the bad direction** vs. `baseline` (Welch t-test, so sampling noise doesn't
|
|
249
|
+
* trip it; see `src/eval-baseline.ts`). Higher is better by default; list
|
|
250
|
+
* `lowerIsBetter` metrics (cost/latency) to flip them. Load the baseline with
|
|
251
|
+
* `readBaseline(path)` and record a fresh one with `writeBaseline(path, reports)`.
|
|
252
|
+
*/
|
|
253
|
+
export declare function assertNoRegression(current: EvalReport | readonly EvalReport[], baseline: BaselineFile, opts?: DiffOptions): void;
|
|
254
|
+
/**
|
|
255
|
+
* Assert a skill/behaviour triggered reliably — the gate for a skill's
|
|
256
|
+
* *activation*, over a {@link TriggerRateReport} from `measureTriggerRate`.
|
|
257
|
+
* `min` gates recall (fires when it should). With irrelevant prompts measured,
|
|
258
|
+
* `maxFalsePositive` gates the precision side (does NOT fire when it shouldn't)
|
|
259
|
+
* and `minPrecision` gates `firedRight / firedTotal` — so a too-broad
|
|
260
|
+
* description that hijacks unrelated work fails, not just a too-narrow one.
|
|
214
261
|
*/
|
|
215
262
|
export declare function assertTriggerRate(report: TriggerRateReport, opts: {
|
|
216
|
-
min
|
|
263
|
+
min?: number;
|
|
264
|
+
maxFalsePositive?: number;
|
|
265
|
+
minPrecision?: number;
|
|
217
266
|
}): void;
|
|
218
267
|
interface MatcherOutput {
|
|
219
268
|
pass: boolean;
|
package/dist/harness-assert.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.vigilesMatchers = exports.compareArms = void 0;
|
|
3
|
+
exports.vigilesMatchers = exports.diffToJUnit = exports.formatBaselineDiff = exports.writeBaseline = exports.readBaseline = exports.parseBaselineFile = exports.toBaselineFile = exports.diffReports = exports.compareArms = void 0;
|
|
4
4
|
exports.withHarness = withHarness;
|
|
5
|
+
exports.skip = skip;
|
|
5
6
|
exports.assertCreated = assertCreated;
|
|
6
7
|
exports.assertNotCreated = assertNotCreated;
|
|
7
8
|
exports.assertServedTurns = assertServedTurns;
|
|
8
9
|
exports.assertHookBlocked = assertHookBlocked;
|
|
9
10
|
exports.assertHookAllowed = assertHookAllowed;
|
|
11
|
+
exports.egressHosts = egressHosts;
|
|
12
|
+
exports.assertNoEgress = assertNoEgress;
|
|
13
|
+
exports.assertEgressOnly = assertEgressOnly;
|
|
14
|
+
exports.assertNoWrite = assertNoWrite;
|
|
15
|
+
exports.assertWroteOnly = assertWroteOnly;
|
|
10
16
|
exports.assertAgentOk = assertAgentOk;
|
|
11
17
|
exports.assertAgentErr = assertAgentErr;
|
|
12
18
|
exports.assertAgentResult = assertAgentResult;
|
|
@@ -34,6 +40,7 @@ exports.improvement = improvement;
|
|
|
34
40
|
exports.significantlyBeats = significantlyBeats;
|
|
35
41
|
exports.assertSignificant = assertSignificant;
|
|
36
42
|
exports.assertImproves = assertImproves;
|
|
43
|
+
exports.assertNoRegression = assertNoRegression;
|
|
37
44
|
exports.assertTriggerRate = assertTriggerRate;
|
|
38
45
|
/**
|
|
39
46
|
* vigiles — runner-agnostic helpers for harness tests / evals.
|
|
@@ -50,13 +57,22 @@ exports.assertTriggerRate = assertTriggerRate;
|
|
|
50
57
|
* `expect(...).toHaveCreated(...)` sugar. The signature is identical for
|
|
51
58
|
* vitest and jest, so the same object supports both.
|
|
52
59
|
*/
|
|
53
|
-
const harness_test_js_1 = require("./harness-test.js");
|
|
54
|
-
const agent_result_js_1 = require("./agent-result.js");
|
|
55
|
-
const stats_js_1 = require("./stats.js");
|
|
60
|
+
const harness_test_js_1 = require("./adapters/claude-code/harness-test.js");
|
|
61
|
+
const agent_result_js_1 = require("./adapters/claude-code/agent-result.js");
|
|
62
|
+
const stats_js_1 = require("./adapters/claude-code/stats.js");
|
|
63
|
+
const eval_baseline_js_1 = require("./adapters/claude-code/eval-baseline.js");
|
|
56
64
|
// Re-export the significance primitives so the whole eval-analysis surface lives
|
|
57
65
|
// behind `vigiles/harness-assert` (no separate entry point).
|
|
58
|
-
var stats_js_2 = require("./stats.js");
|
|
66
|
+
var stats_js_2 = require("./adapters/claude-code/stats.js");
|
|
59
67
|
Object.defineProperty(exports, "compareArms", { enumerable: true, get: function () { return stats_js_2.compareArms; } });
|
|
68
|
+
var eval_baseline_js_2 = require("./adapters/claude-code/eval-baseline.js");
|
|
69
|
+
Object.defineProperty(exports, "diffReports", { enumerable: true, get: function () { return eval_baseline_js_2.diffReports; } });
|
|
70
|
+
Object.defineProperty(exports, "toBaselineFile", { enumerable: true, get: function () { return eval_baseline_js_2.toBaselineFile; } });
|
|
71
|
+
Object.defineProperty(exports, "parseBaselineFile", { enumerable: true, get: function () { return eval_baseline_js_2.parseBaselineFile; } });
|
|
72
|
+
Object.defineProperty(exports, "readBaseline", { enumerable: true, get: function () { return eval_baseline_js_2.readBaseline; } });
|
|
73
|
+
Object.defineProperty(exports, "writeBaseline", { enumerable: true, get: function () { return eval_baseline_js_2.writeBaseline; } });
|
|
74
|
+
Object.defineProperty(exports, "formatBaselineDiff", { enumerable: true, get: function () { return eval_baseline_js_2.formatBaselineDiff; } });
|
|
75
|
+
Object.defineProperty(exports, "diffToJUnit", { enumerable: true, get: function () { return eval_baseline_js_2.diffToJUnit; } });
|
|
60
76
|
/**
|
|
61
77
|
* Run a harness test, hand the result to `fn`, and always clean up the sandbox.
|
|
62
78
|
* Returns whatever `fn` returns. Use this instead of calling `cleanup()` by
|
|
@@ -77,6 +93,21 @@ async function withHarness(spec, fn) {
|
|
|
77
93
|
function fail(message) {
|
|
78
94
|
throw new Error(message);
|
|
79
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Mark the current `*.harness.*` / `*.eval.*` SCRIPT as SKIPPED and exit, so
|
|
98
|
+
* `vigiles test` / `vigiles eval` report a loud `⊘ SKIPPED` instead of a silent
|
|
99
|
+
* `✓` — e.g. the deterministic tier when the `claude` CLI isn't installed. A skip
|
|
100
|
+
* never fails the run. Exit 77 is the runner's `SKIP_EXIT_CODE` (run-scripts.ts).
|
|
101
|
+
*
|
|
102
|
+
* For standalone CLI-fallback scripts only — inside a runner (vitest / jest /
|
|
103
|
+
* node:test) use that runner's own skip, not a process exit.
|
|
104
|
+
*/
|
|
105
|
+
/* v8 ignore start -- process.exit can't be exercised in-process (tested via a child in cli.test.ts / run-scripts.test.ts) */
|
|
106
|
+
function skip(reason) {
|
|
107
|
+
console.log(`SKIPPED${reason ? `: ${reason}` : ""}`);
|
|
108
|
+
process.exit(77);
|
|
109
|
+
}
|
|
110
|
+
/* v8 ignore stop */
|
|
80
111
|
/** Assert the sandbox contains `path` (a hook/agent side-effect file). */
|
|
81
112
|
function assertCreated(r, path) {
|
|
82
113
|
if (r.file(path) === null)
|
|
@@ -105,6 +136,46 @@ function assertHookAllowed(r) {
|
|
|
105
136
|
fail(`expected the hook to allow, but it blocked (exit ${String(r.exitCode)}, decision ${String(r.decision)})`);
|
|
106
137
|
}
|
|
107
138
|
}
|
|
139
|
+
const hostPort = (e) => `${e.host}:${String(e.port)}`;
|
|
140
|
+
/** The `host:port` strings a run attempted, e.g. `["registry.npmjs.org:443"]`. */
|
|
141
|
+
function egressHosts(r) {
|
|
142
|
+
return r.egress.map(hostPort);
|
|
143
|
+
}
|
|
144
|
+
/** Assert the confined run made NO network egress attempt at all. */
|
|
145
|
+
function assertNoEgress(r) {
|
|
146
|
+
if (r.egress.length > 0) {
|
|
147
|
+
fail(`expected no egress, but it tried to reach: ${egressHosts(r).join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Assert every egress attempt went to an allowed host. `allowed` matches a host
|
|
152
|
+
* (exact string or regex), or a specific `host:port`. Any attempt outside the
|
|
153
|
+
* allowlist fails, naming the offender — exfil / unexpected-registry detection.
|
|
154
|
+
*/
|
|
155
|
+
function assertEgressOnly(r, allowed) {
|
|
156
|
+
const ok = (e) => allowed.some((a) => typeof a === "string"
|
|
157
|
+
? a === e.host || a === hostPort(e)
|
|
158
|
+
: a.test(e.host) || a.test(hostPort(e)));
|
|
159
|
+
const bad = r.egress.filter((e) => !ok(e));
|
|
160
|
+
if (bad.length > 0) {
|
|
161
|
+
fail(`egress to non-allowlisted host(s): ${bad.map(hostPort).join(", ")}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const matches = (f, p) => typeof p === "string" ? f.includes(p) : p.test(f);
|
|
165
|
+
/** Assert the run wrote NO file matching `pattern` (substring or regex). */
|
|
166
|
+
function assertNoWrite(r, pattern) {
|
|
167
|
+
const bad = r.filesWritten.filter((f) => matches(f, pattern));
|
|
168
|
+
if (bad.length > 0) {
|
|
169
|
+
fail(`expected no write matching ${String(pattern)}, but wrote: ${bad.join(", ")}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Assert every file the run wrote matches one of `allowed` (substring or regex). */
|
|
173
|
+
function assertWroteOnly(r, allowed) {
|
|
174
|
+
const bad = r.filesWritten.filter((f) => !allowed.some((a) => matches(f, a)));
|
|
175
|
+
if (bad.length > 0) {
|
|
176
|
+
fail(`run wrote file(s) outside the allowlist: ${bad.join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
108
179
|
// --- subagent railway outcome (parse the worker's result block) ------------
|
|
109
180
|
//
|
|
110
181
|
// A subagent with a result() contract ends its turn with a vigiles:ok/err block.
|
|
@@ -431,14 +502,45 @@ function assertImproves(report, opts) {
|
|
|
431
502
|
}
|
|
432
503
|
}
|
|
433
504
|
/**
|
|
434
|
-
* Assert
|
|
435
|
-
*
|
|
436
|
-
*
|
|
505
|
+
* Assert the current run has not *regressed* against a committed baseline — the
|
|
506
|
+
* CI gate (Phase C). A regression is an arm×metric that moved **significantly in
|
|
507
|
+
* the bad direction** vs. `baseline` (Welch t-test, so sampling noise doesn't
|
|
508
|
+
* trip it; see `src/eval-baseline.ts`). Higher is better by default; list
|
|
509
|
+
* `lowerIsBetter` metrics (cost/latency) to flip them. Load the baseline with
|
|
510
|
+
* `readBaseline(path)` and record a fresh one with `writeBaseline(path, reports)`.
|
|
511
|
+
*/
|
|
512
|
+
function assertNoRegression(current, baseline, opts) {
|
|
513
|
+
const reports = Array.isArray(current)
|
|
514
|
+
? current
|
|
515
|
+
: [current];
|
|
516
|
+
const diff = (0, eval_baseline_js_1.diffReports)(baseline, reports, opts);
|
|
517
|
+
if (!diff.passed) {
|
|
518
|
+
const detail = diff.regressions
|
|
519
|
+
.map((r) => `${r.report}/${r.arm}/${r.metric} Δ=${r.comparison.delta.toFixed(3)} p=${r.comparison.pValue.toFixed(3)}`)
|
|
520
|
+
.join("; ");
|
|
521
|
+
fail(`regression vs baseline: ${detail}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Assert a skill/behaviour triggered reliably — the gate for a skill's
|
|
526
|
+
* *activation*, over a {@link TriggerRateReport} from `measureTriggerRate`.
|
|
527
|
+
* `min` gates recall (fires when it should). With irrelevant prompts measured,
|
|
528
|
+
* `maxFalsePositive` gates the precision side (does NOT fire when it shouldn't)
|
|
529
|
+
* and `minPrecision` gates `firedRight / firedTotal` — so a too-broad
|
|
530
|
+
* description that hijacks unrelated work fails, not just a too-narrow one.
|
|
437
531
|
*/
|
|
438
532
|
function assertTriggerRate(report, opts) {
|
|
439
|
-
if (report.rate < opts.min) {
|
|
533
|
+
if (opts.min !== undefined && report.rate < opts.min) {
|
|
440
534
|
fail(`expected a trigger rate ≥ ${String(opts.min)}, got ${report.rate.toFixed(2)} (${String(report.n)} runs)`);
|
|
441
535
|
}
|
|
536
|
+
if (opts.maxFalsePositive !== undefined &&
|
|
537
|
+
(report.falsePositiveRate ?? 0) > opts.maxFalsePositive) {
|
|
538
|
+
fail(`expected a false-positive rate ≤ ${String(opts.maxFalsePositive)}, got ${(report.falsePositiveRate ?? 0).toFixed(2)}`);
|
|
539
|
+
}
|
|
540
|
+
if (opts.minPrecision !== undefined &&
|
|
541
|
+
(report.precision ?? 0) < opts.minPrecision) {
|
|
542
|
+
fail(`expected precision ≥ ${String(opts.minPrecision)}, got ${report.precision === undefined ? "n/a (nothing fired)" : report.precision.toFixed(2)}`);
|
|
543
|
+
}
|
|
442
544
|
}
|
|
443
545
|
/**
|
|
444
546
|
* Custom matchers compatible with both vitest and jest. Register once:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vigiles/integration` — the **deterministic, assembled-machine** tier.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports everything in [`vigiles/unit`](./unit.ts) and adds the runners that
|
|
5
|
+
* drive the real `claude` CLI against a **scripted mock model** plus structural
|
|
6
|
+
* assembly: `runHarnessTest` / `withHarness` (real hooks fire, outcome
|
|
7
|
+
* reproducible), `scriptModel` (the mock), and `loadPlugin` / `resolveHarness`
|
|
8
|
+
* (the plugin loader). Capability contract: needs the **`claude` binary and
|
|
9
|
+
* bubblewrap**, but **no API key and no network**. A `*.integration.test.ts`
|
|
10
|
+
* imports from here.
|
|
11
|
+
*/
|
|
12
|
+
export * from "./unit.js";
|
|
13
|
+
export * from "./adapters/claude-code/harness-test.js";
|
|
14
|
+
export * from "./adapters/claude-code/mock-model.js";
|
|
15
|
+
export * from "./adapters/claude-code/plugin-loader.js";
|
|
16
|
+
//# sourceMappingURL=integration.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* `vigiles/integration` — the **deterministic, assembled-machine** tier.
|
|
19
|
+
*
|
|
20
|
+
* Re-exports everything in [`vigiles/unit`](./unit.ts) and adds the runners that
|
|
21
|
+
* drive the real `claude` CLI against a **scripted mock model** plus structural
|
|
22
|
+
* assembly: `runHarnessTest` / `withHarness` (real hooks fire, outcome
|
|
23
|
+
* reproducible), `scriptModel` (the mock), and `loadPlugin` / `resolveHarness`
|
|
24
|
+
* (the plugin loader). Capability contract: needs the **`claude` binary and
|
|
25
|
+
* bubblewrap**, but **no API key and no network**. A `*.integration.test.ts`
|
|
26
|
+
* imports from here.
|
|
27
|
+
*/
|
|
28
|
+
__exportStar(require("./unit.js"), exports);
|
|
29
|
+
__exportStar(require("./adapters/claude-code/harness-test.js"), exports);
|
|
30
|
+
__exportStar(require("./adapters/claude-code/mock-model.js"), exports);
|
|
31
|
+
__exportStar(require("./adapters/claude-code/plugin-loader.js"), exports);
|
|
32
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin health leaderboard — rank many plugins by the deterministic structural
|
|
3
|
+
* signals `scan` already computes (research/divergent-bets.md #9, the engine).
|
|
4
|
+
*
|
|
5
|
+
* This is the no-model half: a defensible health score from concrete facts
|
|
6
|
+
* (missing hook scripts, skills that can't trigger, agents with no tool
|
|
7
|
+
* contract, untested surfaces). It deliberately does NOT score on the loader's
|
|
8
|
+
* free-text warnings — those include doc-mention false positives (see
|
|
9
|
+
* src/scan.ts), and a ranking that penalizes a prose mention would be unfair.
|
|
10
|
+
* The behavioural columns (real trigger-rate, observed egress, safety) need a
|
|
11
|
+
* model and stack on top later; this part runs anywhere in CI for free.
|
|
12
|
+
*/
|
|
13
|
+
import { type ScanReport } from "./scan.js";
|
|
14
|
+
export interface PluginScore {
|
|
15
|
+
readonly dir: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** 0–100 structural-health score (100 = no structural issues found). */
|
|
18
|
+
readonly score: number;
|
|
19
|
+
readonly grade: "A" | "B" | "C" | "D" | "F";
|
|
20
|
+
/** Human-readable deductions, worst first. */
|
|
21
|
+
readonly issues: readonly string[];
|
|
22
|
+
readonly report: ScanReport;
|
|
23
|
+
}
|
|
24
|
+
/** Deterministic structural-health score for one scanned plugin. */
|
|
25
|
+
export declare function scoreReport(r: ScanReport): {
|
|
26
|
+
score: number;
|
|
27
|
+
issues: string[];
|
|
28
|
+
};
|
|
29
|
+
/** Scan + score each directory, ranked best-first (ties broken by name). */
|
|
30
|
+
export declare function rankPlugins(dirs: readonly string[]): PluginScore[];
|
|
31
|
+
/** Format a ranked leaderboard as human-readable text. */
|
|
32
|
+
export declare function formatLeaderboard(scores: readonly PluginScore[]): string;
|
|
33
|
+
//# sourceMappingURL=leaderboard.d.ts.map
|