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/harness-test.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.claudeCodeDriver = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.resolveHarness = exports.loadPlugin = exports.scriptModel = void 0;
|
|
4
4
|
exports.parseToolCalls = parseToolCalls;
|
|
5
|
+
exports.parseSubagents = parseSubagents;
|
|
5
6
|
exports.parseResultEvent = parseResultEvent;
|
|
6
7
|
exports.parseOutput = parseOutput;
|
|
7
8
|
exports.parseHooks = parseHooks;
|
|
@@ -9,6 +10,7 @@ exports.buildClaudeArgs = buildClaudeArgs;
|
|
|
9
10
|
exports.parseClaudeRun = parseClaudeRun;
|
|
10
11
|
exports.claudeAvailable = claudeAvailable;
|
|
11
12
|
exports.runHarnessTest = runHarnessTest;
|
|
13
|
+
exports.runHarness = runHarness;
|
|
12
14
|
/**
|
|
13
15
|
* vigiles — deterministic Claude Code harness testing.
|
|
14
16
|
*
|
|
@@ -114,6 +116,82 @@ function parseToolCalls(streamJson) {
|
|
|
114
116
|
isError: results.get(u.id)?.isError ?? false,
|
|
115
117
|
}));
|
|
116
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Recover sub-agent runs as nested traces. A subagent-dispatch tool call (the
|
|
121
|
+
* `Agent` tool on the live CLI — older docs say `Task` — carrying an
|
|
122
|
+
* `input.subagent_type`) spawns a subagent whose own events the CLI tags with a
|
|
123
|
+
* top-level `parent_tool_use_id` = the dispatch tool-use id. We group those
|
|
124
|
+
* tagged tool calls under their dispatch, keyed by `subagent_type`. **Schema
|
|
125
|
+
* verified against real claude output** (`parent_tool_use_id` sibling of
|
|
126
|
+
* `message`, `subagent_type` in the dispatch input; tool named `Agent`) — the
|
|
127
|
+
* same `message.content` line shape `parseToolCalls` consumes, and we match the
|
|
128
|
+
* input field NOT the tool name so a future rename can't break it. Pure; empty
|
|
129
|
+
* for a harness that doesn't emit `parent_tool_use_id` (e.g. Codex).
|
|
130
|
+
*/
|
|
131
|
+
function parseSubagents(streamJson) {
|
|
132
|
+
const tasks = new Map(); // dispatch id → subagent name
|
|
133
|
+
const byParent = new Map();
|
|
134
|
+
const groupFor = (parent) => {
|
|
135
|
+
let g = byParent.get(parent);
|
|
136
|
+
if (!g) {
|
|
137
|
+
g = { uses: [], results: new Map() };
|
|
138
|
+
byParent.set(parent, g);
|
|
139
|
+
}
|
|
140
|
+
return g;
|
|
141
|
+
};
|
|
142
|
+
for (const line of streamJson.split("\n")) {
|
|
143
|
+
if (!line.trim())
|
|
144
|
+
continue;
|
|
145
|
+
let evt;
|
|
146
|
+
try {
|
|
147
|
+
evt = JSON.parse(line);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const content = evt.message?.content;
|
|
153
|
+
if (!Array.isArray(content))
|
|
154
|
+
continue;
|
|
155
|
+
const parent = typeof evt.parent_tool_use_id === "string"
|
|
156
|
+
? evt.parent_tool_use_id
|
|
157
|
+
: undefined;
|
|
158
|
+
for (const b of content) {
|
|
159
|
+
if (b.type === "tool_use" && typeof b.name === "string") {
|
|
160
|
+
const id = typeof b.id === "string" ? b.id : "";
|
|
161
|
+
if (!parent) {
|
|
162
|
+
// A subagent dispatch is any top-level tool_use whose input carries a
|
|
163
|
+
// `subagent_type` — the dispatch tool is named "Agent" on the live CLI
|
|
164
|
+
// (older docs say "Task"), so match the input field, NOT the tool name,
|
|
165
|
+
// to survive the rename. Confirmed against real claude output.
|
|
166
|
+
const sub = b.input?.subagent_type;
|
|
167
|
+
if (typeof sub === "string")
|
|
168
|
+
tasks.set(id, sub);
|
|
169
|
+
}
|
|
170
|
+
if (parent)
|
|
171
|
+
groupFor(parent).uses.push({ id, name: b.name, input: b.input });
|
|
172
|
+
}
|
|
173
|
+
else if (b.type === "tool_result" && parent) {
|
|
174
|
+
const id = typeof b.tool_use_id === "string" ? b.tool_use_id : "";
|
|
175
|
+
groupFor(parent).results.set(id, {
|
|
176
|
+
text: contentText(b.content),
|
|
177
|
+
isError: b.is_error === true,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const out = [];
|
|
183
|
+
for (const [taskId, name] of tasks) {
|
|
184
|
+
const g = byParent.get(taskId);
|
|
185
|
+
const toolCalls = (g?.uses ?? []).map((u) => ({
|
|
186
|
+
name: u.name,
|
|
187
|
+
input: u.input,
|
|
188
|
+
resultText: g?.results.get(u.id)?.text ?? "",
|
|
189
|
+
isError: g?.results.get(u.id)?.isError ?? false,
|
|
190
|
+
}));
|
|
191
|
+
out.push({ name, toolCalls });
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
117
195
|
/**
|
|
118
196
|
* The terminal `result` event — present in BOTH `--output-format` shapes (a
|
|
119
197
|
* `{type:"result", …}` line in stream-json, the single object in `json`), or
|
|
@@ -297,6 +375,7 @@ function makeResult(cwd, out, parsed, turns, modelRequests) {
|
|
|
297
375
|
hooks: parsed.hooks,
|
|
298
376
|
output: parsed.output,
|
|
299
377
|
modelRequests,
|
|
378
|
+
subagents: parseSubagents(out.stdout),
|
|
300
379
|
file: (p) => {
|
|
301
380
|
const f = (0, node_path_1.resolve)(cwd, p);
|
|
302
381
|
return (0, node_fs_1.existsSync)(f) ? (0, node_fs_1.readFileSync)(f, "utf-8") : null;
|
|
@@ -380,6 +459,29 @@ async function runHarnessTest(spec, opts = {}) {
|
|
|
380
459
|
await mock.close();
|
|
381
460
|
}
|
|
382
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* `runHarness` — the harness-scope entry of the revamped API (Phase 2 of
|
|
464
|
+
* `research/testing-api-design.md`). The harness has two execution scopes, `hook`
|
|
465
|
+
* (`runHook`) and `harness` (the whole assembled agent); today's `integration` /
|
|
466
|
+
* `e2e` / `eval` are all the **harness** scope under realness flags. This entry is
|
|
467
|
+
* the **deterministic** harness run (`model: "mock"`, the default) — the
|
|
468
|
+
* workhorse you gate every commit, with no key. A **real-model** harness run is
|
|
469
|
+
* non-deterministic by definition, so you don't *assert* a single one — you
|
|
470
|
+
* `measure()` it across trials (the eval scope). `egress` is a capability of this
|
|
471
|
+
* scope (the e2e tier), not a separate tier.
|
|
472
|
+
*
|
|
473
|
+
* Behaviour is identical to `runHarnessTest` (which it wraps); the new name +
|
|
474
|
+
* `model` flag make the scope/realness explicit and steer real-model runs to the
|
|
475
|
+
* right tool.
|
|
476
|
+
*/
|
|
477
|
+
async function runHarness(spec, opts = {}) {
|
|
478
|
+
if (opts.model === "real") {
|
|
479
|
+
throw new Error("runHarness runs the harness DETERMINISTICALLY (model: 'mock'). A real-model " +
|
|
480
|
+
"harness run is non-deterministic, so a single one can't be asserted — " +
|
|
481
|
+
"measure it across trials with `measure()` / `runEval` (the eval scope) instead.");
|
|
482
|
+
}
|
|
483
|
+
return runHarnessTest(spec, opts);
|
|
484
|
+
}
|
|
383
485
|
/** Pull the pillar-2 driver off an adapter, asserting it supports testing. */
|
|
384
486
|
function requireDriver(adapter) {
|
|
385
487
|
(0, adapter_conformance_js_1.assertHarnessTestable)(adapter);
|
package/dist/integration.d.ts
CHANGED
|
@@ -8,9 +8,17 @@
|
|
|
8
8
|
* (the plugin loader). Capability contract: needs the **`claude` binary and
|
|
9
9
|
* bubblewrap**, but **no API key and no network**. A `*.integration.test.ts`
|
|
10
10
|
* imports from here.
|
|
11
|
+
*
|
|
12
|
+
* Real **egress** is a CAPABILITY of this scope (the former `e2e` tier), not a
|
|
13
|
+
* separate tier: `egressRoutes()` probes whether allowlisted egress can route,
|
|
14
|
+
* and `runHook` takes `egress: { allow }` for allowlisted real outbound — gated
|
|
15
|
+
* by a routable sandbox + real network (a test self-skips via `egressRoutes()`).
|
|
16
|
+
* `vigiles/e2e` remains as a thin back-compat alias. See
|
|
17
|
+
* `research/testing-api-design.md` Part 4.
|
|
11
18
|
*/
|
|
12
19
|
export * from "./unit.js";
|
|
13
20
|
export * from "./harness-test.js";
|
|
14
21
|
export * from "./mock-model.js";
|
|
15
22
|
export type { LoadedPlugin } from "./plugin-loader.js";
|
|
23
|
+
export { egressRoutes } from "./run-hook.js";
|
|
16
24
|
//# sourceMappingURL=integration.d.ts.map
|
package/dist/integration.js
CHANGED
|
@@ -14,6 +14,7 @@ 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;
|
|
17
18
|
/**
|
|
18
19
|
* `vigiles/integration` — the **deterministic, assembled-machine** tier.
|
|
19
20
|
*
|
|
@@ -24,8 +25,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
24
25
|
* (the plugin loader). Capability contract: needs the **`claude` binary and
|
|
25
26
|
* bubblewrap**, but **no API key and no network**. A `*.integration.test.ts`
|
|
26
27
|
* imports from here.
|
|
28
|
+
*
|
|
29
|
+
* Real **egress** is a CAPABILITY of this scope (the former `e2e` tier), not a
|
|
30
|
+
* separate tier: `egressRoutes()` probes whether allowlisted egress can route,
|
|
31
|
+
* and `runHook` takes `egress: { allow }` for allowlisted real outbound — gated
|
|
32
|
+
* by a routable sandbox + real network (a test self-skips via `egressRoutes()`).
|
|
33
|
+
* `vigiles/e2e` remains as a thin back-compat alias. See
|
|
34
|
+
* `research/testing-api-design.md` Part 4.
|
|
27
35
|
*/
|
|
28
36
|
__exportStar(require("./unit.js"), exports);
|
|
29
37
|
__exportStar(require("./harness-test.js"), exports);
|
|
30
38
|
__exportStar(require("./mock-model.js"), exports);
|
|
39
|
+
var run_hook_js_1 = require("./run-hook.js");
|
|
40
|
+
Object.defineProperty(exports, "egressRoutes", { enumerable: true, get: function () { return run_hook_js_1.egressRoutes; } });
|
|
31
41
|
//# sourceMappingURL=integration.js.map
|
package/dist/jest.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Check } from "./check.js";
|
|
1
2
|
declare module "@jest/expect" {
|
|
2
3
|
interface Matchers<R> {
|
|
3
4
|
toHaveCreated(path: string): R;
|
|
4
5
|
toBlock(): R;
|
|
5
6
|
toBeatBaseline(baseline: string, arm: string, metric: string, by?: number): R;
|
|
7
|
+
toPass(check: Check<any>): R;
|
|
8
|
+
toPassAll(checks: readonly Check<any>[]): R;
|
|
6
9
|
}
|
|
7
10
|
}
|
|
8
|
-
export {};
|
|
9
11
|
//# sourceMappingURL=jest.d.ts.map
|
package/dist/jest.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
/* eslint-disable max-params --
|
|
4
|
-
The matcher signatures mirror the runtime vigilesMatchers (positional args)
|
|
3
|
+
/* eslint-disable max-params, @typescript-eslint/no-explicit-any --
|
|
4
|
+
The matcher signatures mirror the runtime vigilesMatchers (positional args);
|
|
5
|
+
`Check<any>` matches the runtime generic for the type-only augmentation. */
|
|
5
6
|
/**
|
|
6
7
|
* vigiles — jest integration (opt-in).
|
|
7
8
|
*
|
package/dist/run-hook.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import type { HookProtocol } from "./core/hook-protocol.js";
|
|
2
2
|
import { type SandboxMode, type EgressAttempt } from "./sandbox.js";
|
|
3
3
|
export type { EgressAttempt };
|
|
4
|
+
/** Result of {@link propertyHook}: the first shrunk counterexample, if any. */
|
|
5
|
+
export interface HookPropertyResult<E> {
|
|
6
|
+
readonly passed: boolean;
|
|
7
|
+
readonly iterations: number;
|
|
8
|
+
/** The (shrunk) event that broke an invariant — present iff `!passed`. */
|
|
9
|
+
readonly counterexample?: E;
|
|
10
|
+
/** Which invariant failed — present iff `!passed`. */
|
|
11
|
+
readonly failedInvariant?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Property-test a hook's decision over generated events. Throws nothing — returns
|
|
15
|
+
* a result you assert on (`assert.ok(r.passed)`), so the counterexample is
|
|
16
|
+
* inspectable. `mutate(event, rng)` produces a variation from the running event;
|
|
17
|
+
* each named invariant is checked against `decide(event)`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function propertyHook<E, D>(opts: {
|
|
20
|
+
readonly seed: E;
|
|
21
|
+
readonly mutate: (event: E, rng: number) => E;
|
|
22
|
+
readonly decide: (event: E) => D;
|
|
23
|
+
readonly invariants: Record<string, (decision: D, event: E) => boolean>;
|
|
24
|
+
readonly iterations?: number;
|
|
25
|
+
}): HookPropertyResult<E>;
|
|
4
26
|
/** A hook event payload (the JSON Claude Code writes to the hook's stdin). */
|
|
5
27
|
export interface HookInput {
|
|
6
28
|
/** e.g. "PreToolUse", "PostToolUse", "Stop", "SessionStart", "PreCompact". */
|
package/dist/run-hook.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.propertyHook = propertyHook;
|
|
3
4
|
exports.parseHookOutput = parseHookOutput;
|
|
4
5
|
exports.decideHook = decideHook;
|
|
5
6
|
exports.runHookWith = runHookWith;
|
|
@@ -41,6 +42,33 @@ const node_path_1 = require("node:path");
|
|
|
41
42
|
const hook_protocol_js_1 = require("./adapters/claude-code/hook-protocol.js");
|
|
42
43
|
const egress_js_1 = require("./egress.js");
|
|
43
44
|
const sandbox_js_1 = require("./sandbox.js");
|
|
45
|
+
const proofs_js_1 = require("./core/proofs.js");
|
|
46
|
+
/**
|
|
47
|
+
* Property-test a hook's decision over generated events. Throws nothing — returns
|
|
48
|
+
* a result you assert on (`assert.ok(r.passed)`), so the counterexample is
|
|
49
|
+
* inspectable. `mutate(event, rng)` produces a variation from the running event;
|
|
50
|
+
* each named invariant is checked against `decide(event)`.
|
|
51
|
+
*/
|
|
52
|
+
function propertyHook(opts) {
|
|
53
|
+
const wrapped = {};
|
|
54
|
+
for (const [name, inv] of Object.entries(opts.invariants)) {
|
|
55
|
+
wrapped[name] = (e) => inv(opts.decide(e), e);
|
|
56
|
+
}
|
|
57
|
+
const r = (0, proofs_js_1.propertyTest)(opts.seed, opts.mutate, wrapped, {
|
|
58
|
+
iterations: opts.iterations ?? 100,
|
|
59
|
+
sequenceLength: 1, // each event is independent — no mutation sequence
|
|
60
|
+
seed: 1,
|
|
61
|
+
});
|
|
62
|
+
if (r.passed)
|
|
63
|
+
return { passed: true, iterations: r.iterations };
|
|
64
|
+
const last = r.failingSequence?.[r.failingSequence.length - 1];
|
|
65
|
+
return {
|
|
66
|
+
passed: false,
|
|
67
|
+
iterations: r.iterations,
|
|
68
|
+
counterexample: r.shrunk ?? last,
|
|
69
|
+
failedInvariant: r.failedInvariant,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
44
72
|
/** Parse stdout as a hook JSON decision (pure, testable without a process). */
|
|
45
73
|
function parseHookOutput(stdout) {
|
|
46
74
|
const s = stdout.trim();
|
package/dist/scan.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* and what's broken, with **no model and no API key**.
|
|
4
4
|
*
|
|
5
5
|
* This is the deterministic substrate under the plugin/skill leaderboard
|
|
6
|
-
* (research/divergent-bets.md #9) and the harness-aware
|
|
6
|
+
* (research/divergent-bets.md #9) and the harness-aware scan
|
|
7
7
|
* (research/agent-supply-chain-security.md #1): it re-aims the machinery that
|
|
8
8
|
* already exists — `loadPlugin` (surfaces + dangling-ref/MCP/empty-machine
|
|
9
9
|
* warnings), `parseAgentTools` (the declared tool contract), and
|
package/dist/scan.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and what's broken, with **no model and no API key**.
|
|
5
5
|
*
|
|
6
6
|
* This is the deterministic substrate under the plugin/skill leaderboard
|
|
7
|
-
* (research/divergent-bets.md #9) and the harness-aware
|
|
7
|
+
* (research/divergent-bets.md #9) and the harness-aware scan
|
|
8
8
|
* (research/agent-supply-chain-security.md #1): it re-aims the machinery that
|
|
9
9
|
* already exists — `loadPlugin` (surfaces + dangling-ref/MCP/empty-machine
|
|
10
10
|
* warnings), `parseAgentTools` (the declared tool contract), and
|
package/dist/setup-plan.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
/** What `vigiles init` will set up. */
|
|
12
12
|
export interface SetupPlan {
|
|
13
|
-
/** Lint pillar — verify instruction-file references (specs, types, compile, lint
|
|
13
|
+
/** Lint pillar — verify instruction-file references (specs, types, compile, lint, hooks). */
|
|
14
14
|
lint: boolean;
|
|
15
15
|
/** Test pillar — test the harness (scaffold a starter harness test + CI job). */
|
|
16
16
|
test: boolean;
|
|
@@ -20,12 +20,16 @@ export interface SetupPlan {
|
|
|
20
20
|
plugin: boolean;
|
|
21
21
|
/** Strict rule severities in `.vigilesrc.json`. */
|
|
22
22
|
strict: boolean;
|
|
23
|
+
/** Rewrite an existing STALE CI workflow in place (instead of only warning). */
|
|
24
|
+
force: boolean;
|
|
23
25
|
}
|
|
24
26
|
/** The explicit choices a user pinned via flags (undefined = "not specified"). */
|
|
25
27
|
export interface ParsedSetupArgs {
|
|
26
28
|
target?: string;
|
|
27
29
|
strict: boolean;
|
|
28
30
|
yes: boolean;
|
|
31
|
+
/** `--force` — rewrite a stale CI workflow in place. */
|
|
32
|
+
force: boolean;
|
|
29
33
|
/** Lint pillar — `--lint` → true, `--no-lint` → false, absent → undefined. */
|
|
30
34
|
lint?: boolean;
|
|
31
35
|
/** Test pillar — `--test` → true, `--no-test` → false, absent → undefined. */
|
|
@@ -40,6 +44,17 @@ export interface ParsedSetupArgs {
|
|
|
40
44
|
export declare function parseSetupArgs(args: readonly string[]): ParsedSetupArgs;
|
|
41
45
|
/** The non-interactive defaults: both pillars, CI, and the plugin. */
|
|
42
46
|
export declare function defaultPlan(strict?: boolean): SetupPlan;
|
|
47
|
+
/**
|
|
48
|
+
* Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
|
|
49
|
+
* the `harness` if absent, add strict rule severities if `--strict`, NEVER
|
|
50
|
+
* clobber an existing key. Returns the merged config, or `null` when nothing
|
|
51
|
+
* changed (so the IO layer skips the write). The IO (read/parse/write + the
|
|
52
|
+
* malformed-file guard) stays in cli.ts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mergeProjectConfig(existing: Record<string, unknown>, opts: {
|
|
55
|
+
harness: string | string[];
|
|
56
|
+
strict: boolean;
|
|
57
|
+
}): Record<string, unknown> | null;
|
|
43
58
|
/**
|
|
44
59
|
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
45
60
|
* `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
|
package/dist/setup-plan.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.parseSetupArgs = parseSetupArgs;
|
|
14
14
|
exports.defaultPlan = defaultPlan;
|
|
15
|
+
exports.mergeProjectConfig = mergeProjectConfig;
|
|
15
16
|
exports.shouldPrompt = shouldPrompt;
|
|
16
17
|
exports.planPluginInstall = planPluginInstall;
|
|
17
18
|
exports.resolvePlan = resolvePlan;
|
|
@@ -33,6 +34,7 @@ function parseSetupArgs(args) {
|
|
|
33
34
|
target: flagValue(args, "--target="),
|
|
34
35
|
strict: args.includes("--strict"),
|
|
35
36
|
yes: args.includes("--yes") || args.includes("-y"),
|
|
37
|
+
force: args.includes("--force"),
|
|
36
38
|
lint: boolFlag(args, "lint"),
|
|
37
39
|
test: boolFlag(args, "test"),
|
|
38
40
|
harness: flagValue(args, "--harness="),
|
|
@@ -42,7 +44,40 @@ function parseSetupArgs(args) {
|
|
|
42
44
|
}
|
|
43
45
|
/** The non-interactive defaults: both pillars, CI, and the plugin. */
|
|
44
46
|
function defaultPlan(strict = false) {
|
|
45
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
lint: true,
|
|
49
|
+
test: true,
|
|
50
|
+
gha: true,
|
|
51
|
+
plugin: true,
|
|
52
|
+
strict,
|
|
53
|
+
force: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
|
|
58
|
+
* the `harness` if absent, add strict rule severities if `--strict`, NEVER
|
|
59
|
+
* clobber an existing key. Returns the merged config, or `null` when nothing
|
|
60
|
+
* changed (so the IO layer skips the write). The IO (read/parse/write + the
|
|
61
|
+
* malformed-file guard) stays in cli.ts.
|
|
62
|
+
*/
|
|
63
|
+
function mergeProjectConfig(existing, opts) {
|
|
64
|
+
const config = { ...existing };
|
|
65
|
+
let changed = false;
|
|
66
|
+
if (config.harness === undefined) {
|
|
67
|
+
config.harness = opts.harness;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
if (opts.strict) {
|
|
71
|
+
const rules = { ...config.rules };
|
|
72
|
+
for (const r of ["require-spec", "require-skill-spec"]) {
|
|
73
|
+
if (rules[r] === undefined) {
|
|
74
|
+
rules[r] = "error";
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
config.rules = rules;
|
|
79
|
+
}
|
|
80
|
+
return changed ? config : null;
|
|
46
81
|
}
|
|
47
82
|
/**
|
|
48
83
|
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
@@ -151,6 +186,8 @@ function resolvePlan(parsed, answers) {
|
|
|
151
186
|
plan.gha = false;
|
|
152
187
|
if (parsed.plugin === false)
|
|
153
188
|
plan.plugin = false;
|
|
189
|
+
if (parsed.force)
|
|
190
|
+
plan.force = true;
|
|
154
191
|
if (parsed.target)
|
|
155
192
|
plan.test = false;
|
|
156
193
|
if (answers)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-harness skill-frontmatter verification (slice 3 of
|
|
3
|
+
* research/multi-harness-compile.md, the *verify* half).
|
|
4
|
+
*
|
|
5
|
+
* A skill's `SKILL.md` references are harness-agnostic; the one harness-specific
|
|
6
|
+
* surface is the frontmatter PROFILE. The `claude-code` profile emits CC-only
|
|
7
|
+
* keys (`disable-model-invocation`, `argument-hint`); the `minimal` profile
|
|
8
|
+
* (Codex, OpenCode) omits them. So a skill that sets those keys, in a repo that
|
|
9
|
+
* also targets a minimal-profile harness, has a silent semantic gap: the
|
|
10
|
+
* constraint the author expressed won't take effect there.
|
|
11
|
+
*
|
|
12
|
+
* This reports that gap. It is ASSUMPTION-FREE — the minimal profile *drops* the
|
|
13
|
+
* keys, so the warning states a fact about vigiles's own output, not a guess
|
|
14
|
+
* about another tool's parser tolerance.
|
|
15
|
+
*/
|
|
16
|
+
import type { SkillSpec } from "./core/spec.js";
|
|
17
|
+
/** The Claude-Code-only frontmatter keys a skill spec would emit. */
|
|
18
|
+
export declare function claudeOnlyFrontmatterKeys(spec: SkillSpec): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Warn for each declared harness whose `minimal` SKILL.md profile would DROP a
|
|
21
|
+
* skill's Claude-Code-only frontmatter. Empty when the skill uses no such keys or
|
|
22
|
+
* no declared harness is minimal-profile.
|
|
23
|
+
*/
|
|
24
|
+
export declare function skillFrontmatterDropWarnings(spec: SkillSpec, harnessNames: readonly string[]): string[];
|
|
25
|
+
//# sourceMappingURL=skill-harness.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeOnlyFrontmatterKeys = claudeOnlyFrontmatterKeys;
|
|
4
|
+
exports.skillFrontmatterDropWarnings = skillFrontmatterDropWarnings;
|
|
5
|
+
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
6
|
+
/** The Claude-Code-only frontmatter keys a skill spec would emit. */
|
|
7
|
+
function claudeOnlyFrontmatterKeys(spec) {
|
|
8
|
+
const keys = [];
|
|
9
|
+
if (spec.disableModelInvocation !== undefined) {
|
|
10
|
+
keys.push("disable-model-invocation");
|
|
11
|
+
}
|
|
12
|
+
if (spec.argumentHint || (spec.inputs && spec.inputs.length > 0)) {
|
|
13
|
+
keys.push("argument-hint");
|
|
14
|
+
}
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Warn for each declared harness whose `minimal` SKILL.md profile would DROP a
|
|
19
|
+
* skill's Claude-Code-only frontmatter. Empty when the skill uses no such keys or
|
|
20
|
+
* no declared harness is minimal-profile.
|
|
21
|
+
*/
|
|
22
|
+
function skillFrontmatterDropWarnings(spec, harnessNames) {
|
|
23
|
+
const ccKeys = claudeOnlyFrontmatterKeys(spec);
|
|
24
|
+
if (ccKeys.length === 0)
|
|
25
|
+
return [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const name of harnessNames) {
|
|
29
|
+
const adapter = (0, adapter_registry_js_1.getAdapter)(name);
|
|
30
|
+
if (!adapter || seen.has(adapter.name))
|
|
31
|
+
continue;
|
|
32
|
+
seen.add(adapter.name);
|
|
33
|
+
if (adapter.dialect.skillFrontmatter === "minimal") {
|
|
34
|
+
const one = ccKeys.length === 1;
|
|
35
|
+
warnings.push(`skill "${spec.name}": ${ccKeys.join(", ")} ${one ? "is" : "are"} Claude-Code-only — declared harness "${adapter.name}" drops ${one ? "it" : "them"}.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return warnings;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=skill-harness.js.map
|
package/dist/test-coverage.js
CHANGED
|
@@ -151,7 +151,14 @@ function discoverHooks(basePath) {
|
|
|
151
151
|
}));
|
|
152
152
|
}
|
|
153
153
|
function discoverTests(basePath, globs, ignore) {
|
|
154
|
-
|
|
154
|
+
// `dot: true` so a colocated test under a DOT directory is found — most
|
|
155
|
+
// loose skills live in `.claude/skills/<name>/`, so the eval the warning
|
|
156
|
+
// suggests (`.claude/skills/<name>/<name>.eval.mjs`) is itself dot-pathed.
|
|
157
|
+
// Without this, a globstar (`**/*.eval.mjs`) silently skips it while the
|
|
158
|
+
// skill (matched by the explicit-dot `.claude/skills/*/SKILL.md` pattern) is
|
|
159
|
+
// still discovered — so the surface looks untested even after the user adds
|
|
160
|
+
// exactly the suggested file. DEFAULT_IGNORE still drops .git/node_modules/etc.
|
|
161
|
+
const found = (0, glob_1.globSync)([...globs], { cwd: basePath, ignore, dot: true });
|
|
155
162
|
return found.map((path) => ({ path, content: read((0, node_path_1.join)(basePath, path)) }));
|
|
156
163
|
}
|
|
157
164
|
/** Colocated: a test inside a skill dir, or a name-prefixed sibling of an agent/hook. */
|
package/dist/testing.d.ts
CHANGED
package/dist/testing.js
CHANGED
|
@@ -14,6 +14,7 @@ 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.hookFired = void 0;
|
|
17
18
|
/**
|
|
18
19
|
* `vigiles/testing` — Pillar 2 entry point: the **harness-testing** API. Re-exports
|
|
19
20
|
* the three tiers — `runHook` (unit), `runHarnessTest` (deterministic), `runEval`
|
|
@@ -30,4 +31,10 @@ __exportStar(require("./run-hook.js"), exports);
|
|
|
30
31
|
__exportStar(require("./harness-test.js"), exports);
|
|
31
32
|
__exportStar(require("./eval.js"), exports);
|
|
32
33
|
__exportStar(require("./harness-assert.js"), exports);
|
|
34
|
+
// The declarative check vocabulary is now first-class at the front door. Its
|
|
35
|
+
// `hookFired` (a `Check<Trace>`) supersedes the legacy boolean predicate of the
|
|
36
|
+
// same name — the explicit re-export below wins over the two `export *`s.
|
|
37
|
+
__exportStar(require("./check.js"), exports);
|
|
38
|
+
var check_js_1 = require("./check.js");
|
|
39
|
+
Object.defineProperty(exports, "hookFired", { enumerable: true, get: function () { return check_js_1.hookFired; } });
|
|
33
40
|
//# sourceMappingURL=testing.js.map
|
package/dist/unit.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `vigiles/e2e`.
|
|
13
13
|
*/
|
|
14
14
|
export * from "./harness-assert.js";
|
|
15
|
-
export { runHook, parseHookOutput, decideHook } from "./run-hook.js";
|
|
16
|
-
export type { HookInput, HookOutput, HookRunResult, RunHookOptions, } from "./run-hook.js";
|
|
15
|
+
export { runHook, parseHookOutput, decideHook, propertyHook, } from "./run-hook.js";
|
|
16
|
+
export type { HookInput, HookOutput, HookRunResult, RunHookOptions, HookPropertyResult, } from "./run-hook.js";
|
|
17
|
+
export * from "./check.js";
|
|
18
|
+
export { hookFired } from "./check.js";
|
|
17
19
|
//# sourceMappingURL=unit.d.ts.map
|
package/dist/unit.js
CHANGED
|
@@ -14,7 +14,7 @@ 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.decideHook = exports.parseHookOutput = exports.runHook = void 0;
|
|
17
|
+
exports.hookFired = exports.propertyHook = exports.decideHook = exports.parseHookOutput = exports.runHook = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* `vigiles/unit` — the **no-capability** harness-testing surface.
|
|
20
20
|
*
|
|
@@ -33,4 +33,10 @@ var run_hook_js_1 = require("./run-hook.js");
|
|
|
33
33
|
Object.defineProperty(exports, "runHook", { enumerable: true, get: function () { return run_hook_js_1.runHook; } });
|
|
34
34
|
Object.defineProperty(exports, "parseHookOutput", { enumerable: true, get: function () { return run_hook_js_1.parseHookOutput; } });
|
|
35
35
|
Object.defineProperty(exports, "decideHook", { enumerable: true, get: function () { return run_hook_js_1.decideHook; } });
|
|
36
|
+
Object.defineProperty(exports, "propertyHook", { enumerable: true, get: function () { return run_hook_js_1.propertyHook; } });
|
|
37
|
+
// The check vocabulary is part of the base surface (pure, no capability). Its
|
|
38
|
+
// `hookFired` check supersedes the legacy boolean predicate of the same name.
|
|
39
|
+
__exportStar(require("./check.js"), exports);
|
|
40
|
+
var check_js_1 = require("./check.js");
|
|
41
|
+
Object.defineProperty(exports, "hookFired", { enumerable: true, get: function () { return check_js_1.hookFired; } });
|
|
36
42
|
//# sourceMappingURL=unit.js.map
|
package/dist/vitest.d.mts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Check } from "./check.js";
|
|
1
2
|
declare module "@vitest/expect" {
|
|
2
3
|
interface Matchers<T = any> {
|
|
3
4
|
toHaveCreated(path: string): T;
|
|
4
5
|
toBlock(): T;
|
|
5
6
|
toBeatBaseline(baseline: string, arm: string, metric: string, by?: number): T;
|
|
7
|
+
toPass(check: Check<any>): T;
|
|
8
|
+
toPassAll(checks: readonly Check<any>[]): T;
|
|
6
9
|
}
|
|
7
10
|
}
|
|
8
|
-
export {};
|
|
9
11
|
//# sourceMappingURL=vitest.d.mts.map
|
package/hooks/refs-nudge.sh
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# PostToolUse hook — nudge the agent to express references in instruction files
|
|
3
|
-
# (CLAUDE.md / AGENTS.md / SKILL.md) as vigiles marks, so `vigiles
|
|
3
|
+
# (CLAUDE.md / AGENTS.md / SKILL.md) as vigiles marks, so `vigiles lint` can
|
|
4
4
|
# actually verify them. Non-blocking by default; set the `unmarked-refs` rule to
|
|
5
5
|
# "error" in .vigilesrc.json to turn the nudge into a hard block, or to false to
|
|
6
6
|
# disable it. Runs as its OWN PostToolUse entry so its stdout stays clean JSON.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Compile .spec.ts files to instruction files (CLAUDE.md, AGENTS.md) with linter cross-referencing",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vigiles": "dist/cli.js"
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"types": "./dist/vitest.d.mts",
|
|
33
33
|
"default": "./dist/vitest.mjs"
|
|
34
34
|
},
|
|
35
|
-
"./jest": "./dist/jest.js"
|
|
35
|
+
"./jest": "./dist/jest.js",
|
|
36
|
+
"./check": "./dist/check.js"
|
|
36
37
|
},
|
|
37
38
|
"files": [
|
|
38
39
|
"dist/**/*.js",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: edit-spec
|
|
3
|
-
description: Edit a vigiles spec
|
|
4
|
-
disable-model-invocation: true
|
|
3
|
+
description: Edit a vigiles .spec.ts to change a compiled instruction file (CLAUDE.md / AGENTS.md) — add, modify, or remove a rule, section, command, or key file. Use whenever you need to change a CLAUDE.md/AGENTS.md that carries a vigiles hash (edit the spec, never the artifact), including adding a new enforce()/check()/guidance() rule.
|
|
5
4
|
argument-hint: <what to change — e.g., "add a rule about error handling" or "update the testing section">
|
|
6
5
|
---
|
|
7
6
|
|
|
@@ -27,7 +26,8 @@ Look for spec files in the repo root:
|
|
|
27
26
|
- `AGENTS.md.spec.ts` — source for AGENTS.md
|
|
28
27
|
- Any `*.spec.ts` matching instruction files
|
|
29
28
|
|
|
30
|
-
If no spec exists
|
|
29
|
+
If no spec exists: if there's a hand-written `CLAUDE.md`, suggest the
|
|
30
|
+
`migrate-to-spec` skill; otherwise suggest `npx vigiles init` to scaffold one.
|
|
31
31
|
|
|
32
32
|
### Step 2: Read and Understand the Spec
|
|
33
33
|
|
|
@@ -83,11 +83,22 @@ export default claude({
|
|
|
83
83
|
|
|
84
84
|
Based on what the user asked for:
|
|
85
85
|
|
|
86
|
-
**Adding a rule
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
86
|
+
**Adding a rule** (this absorbs the old `generate-rule` skill):
|
|
87
|
+
|
|
88
|
+
- **Classify the rule** from the request:
|
|
89
|
+
- `enforce()` — a linter rule can back it. Check the project's linter configs
|
|
90
|
+
(ESLint, Ruff, Clippy, Pylint, RuboCop, Stylelint) for a matching rule; also
|
|
91
|
+
consider an architectural tool (ast-grep, Dependency Cruiser, Steiger). If
|
|
92
|
+
uncertain whether a rule exists, **ask the user** rather than guessing.
|
|
93
|
+
- `check()` — a filesystem structural convention ("every X needs a Y"). Only
|
|
94
|
+
for file-pairing; never for code content.
|
|
95
|
+
- `guidance()` — can't be mechanically enforced (subjective conventions,
|
|
96
|
+
process rules, migration context).
|
|
97
|
+
- For `enforce()`: use the real linter rule name (e.g. `eslint/no-console`,
|
|
98
|
+
`@typescript-eslint/no-explicit-any`, `ruff/T201`).
|
|
99
|
+
- Add to the `rules` object with a kebab-case key derived from the intent,
|
|
100
|
+
preserving alphabetical order if the existing rules are alphabetical. Import
|
|
101
|
+
any new builders needed (e.g. `check` and `every` for the first `check()`).
|
|
91
102
|
|
|
92
103
|
**Updating a section:**
|
|
93
104
|
|
|
@@ -118,10 +129,10 @@ This regenerates the compiled instruction file(s). Review the output for any err
|
|
|
118
129
|
### Step 5: Verify
|
|
119
130
|
|
|
120
131
|
```bash
|
|
121
|
-
npx vigiles
|
|
132
|
+
npx vigiles lint
|
|
122
133
|
```
|
|
123
134
|
|
|
124
|
-
If the
|
|
135
|
+
If the vigiles plugin is installed (`/plugin marketplace add zernie/vigiles` then `/plugin install vigiles@vigiles`, or `npx vigiles init`), the PostToolUse hook recompiles automatically after you save the spec.
|
|
125
136
|
|
|
126
137
|
## Important
|
|
127
138
|
|