vigiles 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -5
- package/dist/action-gate.d.ts +28 -0
- package/dist/action-gate.js +73 -0
- package/dist/cli.js +408 -75
- package/dist/community-skills.d.ts +22 -0
- package/dist/community-skills.js +86 -0
- package/dist/compile-generator.d.ts +48 -0
- package/dist/compile-generator.js +322 -0
- package/dist/compile.d.ts +3 -0
- package/dist/compile.js +217 -26
- package/dist/eval.d.ts +62 -0
- package/dist/eval.js +174 -0
- package/dist/frontmatter.d.ts +24 -6
- package/dist/frontmatter.js +103 -30
- package/dist/generate-schema.js +10 -0
- package/dist/harness-test.d.ts +38 -0
- package/dist/harness-test.js +129 -0
- package/dist/inline.d.ts +22 -4
- package/dist/inline.js +60 -13
- package/dist/linters.js +28 -0
- package/dist/mock-model.d.ts +31 -0
- package/dist/mock-model.js +189 -0
- package/dist/refs.d.ts +44 -0
- package/dist/refs.js +144 -0
- package/dist/skill-driver.d.ts +77 -0
- package/dist/skill-driver.js +76 -0
- package/dist/skill-runtime.d.ts +101 -0
- package/dist/skill-runtime.js +289 -0
- package/dist/skill-test.d.ts +47 -0
- package/dist/skill-test.js +77 -0
- package/dist/spec.d.ts +90 -4
- package/dist/spec.js +29 -0
- package/dist/symbols.d.ts +30 -0
- package/dist/symbols.js +142 -0
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Reads fine. Four things are wrong:
|
|
|
40
40
|
3. `npm run typecheck` — script removed from package.json
|
|
41
41
|
4. Service/test pairing — no automated check, just a hope
|
|
42
42
|
|
|
43
|
-
The agent reads this, trusts it, and writes code based on stale claims nobody verified. vigiles **verifies the references in your instruction files** — that each linter rule exists and is enabled, that every file path and script is real — and meets you at whatever commitment level you want.
|
|
43
|
+
The agent reads this, trusts it, and writes code based on stale claims nobody verified. vigiles **verifies the references in your instruction files** — that each linter rule exists and is enabled, that every file path and script is real, and that referenced **code symbols** (functions, classes, constants) actually exist in the files that define them — and meets you at whatever commitment level you want.
|
|
44
44
|
|
|
45
45
|
Three levels. Each is independently useful; adopt as far up as you like.
|
|
46
46
|
|
|
@@ -210,19 +210,20 @@ Same monotonicity guarantees as `enforce()` — guards can't be silently removed
|
|
|
210
210
|
|
|
211
211
|
## Verified References
|
|
212
212
|
|
|
213
|
-
`file()`, `cmd()`, and `ref()` catch stale references at compile time:
|
|
213
|
+
`file()`, `cmd()`, `symbol()`, and `ref()` catch stale references at compile time:
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
|
-
import { claude, file, cmd, ref, instructions } from "vigiles/spec";
|
|
216
|
+
import { claude, file, cmd, symbol, ref, instructions } from "vigiles/spec";
|
|
217
217
|
|
|
218
218
|
export default claude({
|
|
219
219
|
sections: {
|
|
220
220
|
architecture: instructions`
|
|
221
221
|
Core engine in ${file("src/compile.ts")}.
|
|
222
|
+
Compile specs with ${symbol("src/compile.ts", "compileClaude")}.
|
|
222
223
|
Run ${cmd("npm test")} to verify.
|
|
223
224
|
See ${ref("skills/strengthen/SKILL.md")} for the strengthen skill.
|
|
224
225
|
`,
|
|
225
|
-
// If any path is stale → compile error
|
|
226
|
+
// If any path / script / symbol is stale → compile error
|
|
226
227
|
},
|
|
227
228
|
// ...
|
|
228
229
|
});
|
|
@@ -230,6 +231,12 @@ export default claude({
|
|
|
230
231
|
|
|
231
232
|
Skill specs use the same helpers for verified references inside instructions. [Full spec format →](docs/spec-format.md)
|
|
232
233
|
|
|
234
|
+
### Symbol references (cross-language)
|
|
235
|
+
|
|
236
|
+
`symbol("file", "name")` (and the markdown mark `` `vigiles:symbol file#name` ``) verify that the named file actually **defines** the symbol — a function, class, method, or constant — parsed with [ast-grep](https://ast-grep.github.io) across **JS/TS, Python, Ruby, Rust, and CSS**. Rename the function and `audit` fails; no project-wide index, no autoloader guessing — it parses the one named file.
|
|
237
|
+
|
|
238
|
+
In markdown mode the `refs-hook` (PostToolUse) **forces the mark**: it blocks an edit that leaves a code reference bare, telling the agent to write `` `vigiles:symbol path#name` `` or opt out with `<!-- vigiles:ignore -->`. The harness makes the agent mark its references at write time, with full context; `audit` re-verifies them. [Symbol verification →](research/symbol-verification.md)
|
|
239
|
+
|
|
233
240
|
## Type-Safe Rule References
|
|
234
241
|
|
|
235
242
|
`vigiles generate-types` scans your linter configs and emits `.vigiles/generated.d.ts`. With this file, `enforce("eslint/no-consolee")` is a red squiggle in your editor — a typo caught at authoring time, not a runtime surprise. Without it, everything falls back to broad types and still works.
|
|
@@ -249,7 +256,8 @@ For markdown frontmatter (Level 1), `vigiles generate-schema` gives the same aut
|
|
|
249
256
|
```bash
|
|
250
257
|
npx vigiles init [--target=X.md] # Scaffold a spec (runs full setup wizard by default)
|
|
251
258
|
npx vigiles compile [files...] # Compile .spec.ts → .md
|
|
252
|
-
npx vigiles audit [files...] # Verify hashes + inline/frontmatter/spec rules + coverage
|
|
259
|
+
npx vigiles audit [files...] # Verify hashes + inline/frontmatter/spec rules + symbols + coverage
|
|
260
|
+
npx vigiles refs <file.md> # Check the symbol references in an instruction file
|
|
253
261
|
npx vigiles generate-types # Emit .d.ts from project state (for spec mode)
|
|
254
262
|
npx vigiles generate-types --check # Verify .d.ts is up to date
|
|
255
263
|
npx vigiles generate-schema # Emit JSON Schema for vigiles: frontmatter (Level 1)
|
|
@@ -323,6 +331,69 @@ Install with [Vercel Skills](https://github.com/vercel-labs/skills): `npx skills
|
|
|
323
331
|
| `enforce-rules-format` | Validate all rules have enforcement classification |
|
|
324
332
|
| `audit-feedback-loop` | Score your repo's feedback loop maturity |
|
|
325
333
|
|
|
334
|
+
## Test your Claude Code harness
|
|
335
|
+
|
|
336
|
+
vigiles also ships a library for **testing the harness itself** — your hooks,
|
|
337
|
+
settings, skills, and instruction files. `Agent = Model + Harness`; this tests
|
|
338
|
+
the harness, at two levels.
|
|
339
|
+
|
|
340
|
+
**Evals — does my change actually move agent behaviour?** Define a fixture, a set
|
|
341
|
+
of **arms** (a hook on vs off, with/without a CLAUDE.md rule), a task, and a
|
|
342
|
+
metric; `runEval` drives the real `claude` CLI N trials per arm and aggregates.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { runEval, formatEvalReport } from "vigiles/eval";
|
|
346
|
+
|
|
347
|
+
const report = await runEval({
|
|
348
|
+
fixture: { "src/billing.ts": "export function chargeCard() {}" },
|
|
349
|
+
arms: {
|
|
350
|
+
vanilla: {},
|
|
351
|
+
gated: { settings: { hooks: { PostToolUse: [refsHook] } } },
|
|
352
|
+
},
|
|
353
|
+
task: "Document chargeCard in SKILL.md, referencing it by name.",
|
|
354
|
+
measure: (ctx) => ({
|
|
355
|
+
marked: ctx.sh("grep -c vigiles:symbol SKILL.md") !== "0",
|
|
356
|
+
}),
|
|
357
|
+
trials: 6,
|
|
358
|
+
});
|
|
359
|
+
console.log(formatEvalReport(report)); // vanilla marked=0.00 gated marked=0.50
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Deterministic tests — does my hook fire correctly?** No API key, no cost.
|
|
363
|
+
`runHarnessTest` runs real `claude` against a **scripted mock model**
|
|
364
|
+
(`vigiles/mock-model`), so your real hooks fire but the agent's turns are fixed.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { runHarnessTest, scriptModel } from "vigiles/harness-test";
|
|
368
|
+
|
|
369
|
+
const r = await runHarnessTest({
|
|
370
|
+
settings: {
|
|
371
|
+
hooks: {
|
|
372
|
+
Stop: [
|
|
373
|
+
{
|
|
374
|
+
hooks: [
|
|
375
|
+
{
|
|
376
|
+
type: "command",
|
|
377
|
+
command: "test -f DONE || { echo 'not done' >&2; exit 2; }",
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
model: scriptModel([
|
|
385
|
+
{ text: "I'm done" }, // tries to stop → blocked
|
|
386
|
+
{ tool: "Bash", input: { command: "touch DONE" } },
|
|
387
|
+
{ text: "now done" },
|
|
388
|
+
]),
|
|
389
|
+
});
|
|
390
|
+
assert(JSON.parse(r.stdout).num_turns > 1); // the Stop hook forced more work
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The deterministic tier is reliable for **Stop hooks**; tool-event hooks
|
|
394
|
+
(Edit/Write) are headless-gated, so test those via the eval tier. Our own
|
|
395
|
+
findings from this harness live in [`research/benchmarks-runtime-gates.md`](research/benchmarks-runtime-gates.md).
|
|
396
|
+
|
|
326
397
|
## Maturity Levels
|
|
327
398
|
|
|
328
399
|
From [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need):
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type RuntimeGate } from "./skill-runtime.js";
|
|
2
|
+
export interface ActionGate {
|
|
3
|
+
/** Tool name to gate, e.g. "Write" | "Edit" | "Bash". */
|
|
4
|
+
readonly on: string;
|
|
5
|
+
/** Deterministic check; a `cmd` command may include `{file}`. */
|
|
6
|
+
readonly gate: RuntimeGate;
|
|
7
|
+
/** Optional substring the (JSON-serialized) tool input must contain. */
|
|
8
|
+
readonly when?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ActionEvent {
|
|
11
|
+
/** The tool that just ran (PostToolUse `tool_name`). */
|
|
12
|
+
readonly tool: string;
|
|
13
|
+
/** The tool input (`tool_input`), e.g. `{ file_path, command }`. */
|
|
14
|
+
readonly input?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface ActionDecision {
|
|
17
|
+
readonly allow: boolean;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate action gates against a tool event. Runs every gate whose `on`
|
|
22
|
+
* matches the tool (and whose `when` substring matches the input); the first
|
|
23
|
+
* failure blocks. Plan-agnostic — order in any runtime workflow is irrelevant.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateAction(event: ActionEvent, gates: readonly ActionGate[], cwd: string): ActionDecision;
|
|
26
|
+
/** Load action gates from `.vigiles/action-gates.json`. */
|
|
27
|
+
export declare function loadActionGates(cwd: string): ActionGate[];
|
|
28
|
+
//# sourceMappingURL=action-gate.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateAction = evaluateAction;
|
|
4
|
+
exports.loadActionGates = loadActionGates;
|
|
5
|
+
/**
|
|
6
|
+
* vigiles — Action gates (the dynamic-workflow reframe).
|
|
7
|
+
*
|
|
8
|
+
* A skill gate is bound to a *step* (a fixed position in a plan). When the plan
|
|
9
|
+
* is generated at runtime (dynamic workflows), the step is the wrong unit. An
|
|
10
|
+
* **action gate** binds a deterministic check to a *tool action type* instead —
|
|
11
|
+
* "any time a Write happens to a `.ts` file, eslint must pass on it" — so it
|
|
12
|
+
* fires regardless of where in the runtime plan the action occurs.
|
|
13
|
+
*
|
|
14
|
+
* It is the same deterministic gate primitive (reuses `runGate` + the
|
|
15
|
+
* author-time reference resolution), re-anchored from step → action. Delivered
|
|
16
|
+
* as a PostToolUse hook (`vigiles action-hook`): exit 2 blocks the action and
|
|
17
|
+
* feeds the reason back, exit 0 allows it.
|
|
18
|
+
*
|
|
19
|
+
* Config: `.vigiles/action-gates.json` → `{ "gates": [ { on, gate, when? } ] }`.
|
|
20
|
+
* The gate command may contain `{file}`, substituted with the action's path.
|
|
21
|
+
*/
|
|
22
|
+
const node_fs_1 = require("node:fs");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const skill_runtime_js_1 = require("./skill-runtime.js");
|
|
25
|
+
/** The file path an action touched, for `{file}` substitution. */
|
|
26
|
+
function fileOf(event) {
|
|
27
|
+
const i = event.input ?? {};
|
|
28
|
+
const v = i.file_path ?? i.path;
|
|
29
|
+
return typeof v === "string" ? v : "";
|
|
30
|
+
}
|
|
31
|
+
/** Substitute `{file}` in a cmd gate with the action's path. */
|
|
32
|
+
function resolveGate(gate, event) {
|
|
33
|
+
if (gate.kind !== "cmd" || !gate.command.includes("{file}"))
|
|
34
|
+
return gate;
|
|
35
|
+
return { ...gate, command: gate.command.replaceAll("{file}", fileOf(event)) };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Evaluate action gates against a tool event. Runs every gate whose `on`
|
|
39
|
+
* matches the tool (and whose `when` substring matches the input); the first
|
|
40
|
+
* failure blocks. Plan-agnostic — order in any runtime workflow is irrelevant.
|
|
41
|
+
*/
|
|
42
|
+
function evaluateAction(event, gates, cwd) {
|
|
43
|
+
const inputStr = JSON.stringify(event.input ?? "");
|
|
44
|
+
for (const g of gates) {
|
|
45
|
+
if (g.on !== event.tool)
|
|
46
|
+
continue;
|
|
47
|
+
if (g.when && !inputStr.includes(g.when))
|
|
48
|
+
continue;
|
|
49
|
+
const outcome = (0, skill_runtime_js_1.runGate)(resolveGate(g.gate, event), cwd);
|
|
50
|
+
if (!outcome.ok) {
|
|
51
|
+
const tail = outcome.output ? `\n${outcome.output}` : "";
|
|
52
|
+
return {
|
|
53
|
+
allow: false,
|
|
54
|
+
message: `Action gate failed after ${event.tool}: ${(0, skill_runtime_js_1.gateLabel)(g.gate)} did not pass.${tail}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { allow: true, message: "" };
|
|
59
|
+
}
|
|
60
|
+
/** Load action gates from `.vigiles/action-gates.json`. */
|
|
61
|
+
function loadActionGates(cwd) {
|
|
62
|
+
const p = (0, node_path_1.resolve)(cwd, ".vigiles/action-gates.json");
|
|
63
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
64
|
+
return [];
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
|
|
67
|
+
return Array.isArray(parsed.gates) ? parsed.gates : [];
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=action-gate.js.map
|