vigiles 2.6.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 +74 -129
- 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.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -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/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
- package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
- 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} +10 -0
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
- 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 +8 -2
- package/dist/claude-code.js +8 -2
- 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 +29 -14
- package/dist/harness-assert.js +37 -10
- 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 +2 -2
- package/dist/linting.js +2 -2
- 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 +3 -3
- package/dist/testing.js +3 -3
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +30 -15
- 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-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/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
- /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
- /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
- /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.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,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-harness
|
|
3
|
+
description: Install vigiles and test a Claude Code harness — hooks, skills, settings, CLAUDE.md — by picking the right tier (unit / deterministic / eval) and writing a test that passes. Use when the user wants to check that a hook fires or blocks, that a skill triggers, that injected context lands, or that a harness change moves what the agent does.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Test the Claude Code **harness** — the hooks, skills, settings, and CLAUDE.md that
|
|
7
|
+
steer an agent — as the assembled machine it ships as. vigiles gives three tiers,
|
|
8
|
+
cheapest first; this skill picks the right one, writes the test, and runs it.
|
|
9
|
+
|
|
10
|
+
The guiding rule: **start at the cheapest tier that can answer the question, and
|
|
11
|
+
climb only when it genuinely can't.** Two of the three tiers need no model and no
|
|
12
|
+
API key, so they run on every commit for free — reach for the paid real-model
|
|
13
|
+
tier only when the question actually requires a real model.
|
|
14
|
+
|
|
15
|
+
## Step 0 — Pick the tier (the judgment call)
|
|
16
|
+
|
|
17
|
+
Match what you're testing to the cheapest tier that can answer it:
|
|
18
|
+
|
|
19
|
+
| What you're testing | Tier | Cost | API |
|
|
20
|
+
| -------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
|
21
|
+
| "Does this hook block/allow event X?" — pure hook logic, **every** event type (incl. Edit/Write, PreCompact, SessionEnd, SubagentStop) | **Unit** | free, milliseconds, no `claude` | `runHook` |
|
|
22
|
+
| "Is the hook actually **wired into** the assembled plugin and does it fire in a real session?" | **Deterministic** | free, no API key (real `claude` + scripted mock) | `runHarnessTest` + `scriptModel` |
|
|
23
|
+
| "Did the injected context (a SessionStart hook, a `/command`) actually **reach the model**?" | **Deterministic** | free, no API key | `runHarnessTest` → `trace.modelRequests` / `assertRequestContains` |
|
|
24
|
+
| "Does this skill's **description trigger** when it should (recall) **and stay quiet** when it shouldn't (precision)?" | **Eval** | **paid** (real model) | `measureTriggerRate` (+ `irrelevantPrompts`) → `assertTriggerRate({ min, maxFalsePositive })` |
|
|
25
|
+
| "Does this harness change **move what the agent does**?" (A/B, signal vs noise) | **Eval** | **paid** (real model) | `runEval` + `assertSignificant` |
|
|
26
|
+
|
|
27
|
+
Most harness questions — block/allow, wired-in, context-landed — never need a
|
|
28
|
+
model. Only "does the model trigger / behave differently" needs the eval tier.
|
|
29
|
+
|
|
30
|
+
If the unit and deterministic tiers can both answer it, **prefer unit**: it's
|
|
31
|
+
faster and reaches events the deterministic mock can't drive.
|
|
32
|
+
|
|
33
|
+
## Step 1 — Ensure vigiles is installed
|
|
34
|
+
|
|
35
|
+
Check whether `vigiles` is a dependency (`package.json`), and install it as a
|
|
36
|
+
dev dependency if not:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm i -D vigiles # or: pnpm add -D vigiles / yarn add -D vigiles
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The deterministic tier additionally needs the `claude` CLI on PATH (no API key):
|
|
43
|
+
`npm i -g @anthropic-ai/claude-code`. The eval tier needs model auth. If the
|
|
44
|
+
`claude` CLI is missing, you can still write and run **unit**-tier tests.
|
|
45
|
+
|
|
46
|
+
## Step 2 — Locate the harness surface to test
|
|
47
|
+
|
|
48
|
+
Find what the project actually ships, in this order:
|
|
49
|
+
|
|
50
|
+
1. `.claude/settings.json` / `.claude/settings.local.json` — inline `hooks`.
|
|
51
|
+
2. `.claude-plugin/plugin.json` — a plugin manifest (`hooks`, `skills`, `agents`, `mcpServers`).
|
|
52
|
+
3. `hooks/hooks.json` — the plugin hooks convention (e.g. obra/superpowers).
|
|
53
|
+
4. `skills/<name>/SKILL.md`, `agents/<name>.md`, `commands/<name>.md`.
|
|
54
|
+
|
|
55
|
+
Pick one concrete thing to pin down — a specific `PreToolUse` hook, a specific
|
|
56
|
+
`SessionStart` injection, a specific skill.
|
|
57
|
+
|
|
58
|
+
## Step 3 — Write the test for the chosen tier
|
|
59
|
+
|
|
60
|
+
**Unit (`runHook`)** — hand a hook a synthesized event, assert the decision:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { runHook, assertHookBlocked } from "vigiles/testing";
|
|
64
|
+
|
|
65
|
+
const r = runHook(hookCommand, {
|
|
66
|
+
hook_event_name: "PreToolUse",
|
|
67
|
+
tool_name: "Bash",
|
|
68
|
+
tool_input: { command: "git commit --no-verify" },
|
|
69
|
+
});
|
|
70
|
+
assertHookBlocked(r); // exit 2 / decision:"block" / permissionDecision:"deny"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Testing a hook you didn't write (a vendored third-party script)? Mark it
|
|
74
|
+
`{ trusted: false }` and it runs confined under bubblewrap by default (read-only
|
|
75
|
+
host, cleared env, no network egress). Add `{ recordEgress: true }` to also
|
|
76
|
+
**record** what it tries to reach — `r.egress` plus `assertNoEgress(r)` /
|
|
77
|
+
`assertEgressOnly(r, [...])` — the supply-chain check for "what does this skill
|
|
78
|
+
phone home to / install from?". When the hook's setup needs a _real_ install,
|
|
79
|
+
`{ egress: { allow: ["registry.npmjs.org"] } }` lets it reach only that
|
|
80
|
+
allowlist (a packet-layer `nft` wall, so a raw socket off-list is dropped too) →
|
|
81
|
+
`r.egress` (allowed hosts) + `r.egressDropped`. Be precise about the boundaries:
|
|
82
|
+
see
|
|
83
|
+
[`docs/sandboxing.md`](../../docs/sandboxing.md) (it blocks destruction and
|
|
84
|
+
egress, but does NOT isolate reads of host files, and only under bwrap).
|
|
85
|
+
|
|
86
|
+
**Deterministic (`runHarnessTest`)** — load the real plugin, drive a scripted
|
|
87
|
+
mock model, assert the hook fired (or the context landed):
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import {
|
|
91
|
+
runHarnessTest,
|
|
92
|
+
scriptModel,
|
|
93
|
+
assertHookFired,
|
|
94
|
+
assertRequestContains,
|
|
95
|
+
} from "vigiles/testing";
|
|
96
|
+
|
|
97
|
+
const r = await runHarnessTest({
|
|
98
|
+
pluginDir: "./", // or { settings: { hooks: {...} } }
|
|
99
|
+
transcript: true,
|
|
100
|
+
model: scriptModel([{ text: "ok" }]),
|
|
101
|
+
});
|
|
102
|
+
assertHookFired(r, "SessionStart");
|
|
103
|
+
assertRequestContains(r, "expected injected text"); // did it actually land?
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Eval (`runEval`)** — A/B the change on vs off across real-model trials, then
|
|
107
|
+
gate on significance, not eyeballing:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { runEval, assertSignificant } from "vigiles/testing";
|
|
111
|
+
|
|
112
|
+
const report = await runEval({
|
|
113
|
+
arms: { off: {}, on: { pluginDir: "./" } },
|
|
114
|
+
task: "…a task the harness change should affect…",
|
|
115
|
+
measure: (ctx) => ({ ok: /* a bare predicate over the trace */ true }),
|
|
116
|
+
trials: 6,
|
|
117
|
+
cache: "readwrite",
|
|
118
|
+
});
|
|
119
|
+
assertSignificant(report, { baseline: "off", arm: "on", metric: "ok" });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Step 4 — Run it
|
|
123
|
+
|
|
124
|
+
In a runner (node:test / vitest / jest) the tests are plain async functions. Or
|
|
125
|
+
use the zero-setup CLI, which discovers and runs the files:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx vigiles test # *.harness.{mjs,ts} — unit + deterministic, no API key
|
|
129
|
+
npx vigiles eval --trials=6 # *.eval.{mjs,ts} — real model (local / nightly, not CI)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Unit-tier `runHook` tests need no `claude` and **always run** — write and run them
|
|
133
|
+
even with no `claude` installed. A tier that genuinely can't run reports a loud
|
|
134
|
+
`⊘ SKIPPED` (tallied separately, never a fake `✓`); a standalone script emits one
|
|
135
|
+
via `skip(reason)` from `vigiles/testing`. A skip passes by default, but in a CI
|
|
136
|
+
job that asserts the capability is present, run **`vigiles test --no-skip`** so a
|
|
137
|
+
skipped tier fails — a green-with-skips is untested surface. Keep unit +
|
|
138
|
+
deterministic tests in CI (free); run evals locally or on a schedule with auth.
|
|
139
|
+
|
|
140
|
+
## When the user didn't say what to test
|
|
141
|
+
|
|
142
|
+
Don't ask them to specify — **pick something real and demonstrate.** Scan the
|
|
143
|
+
harness surface (Step 2), choose the cheapest meaningful test, write it, run it,
|
|
144
|
+
and show the result. Good default picks, in order:
|
|
145
|
+
|
|
146
|
+
1. A `PreToolUse` hook → **unit-test** that it blocks the thing it's meant to block (and allows a safe sibling).
|
|
147
|
+
2. A `SessionStart` hook that injects context → **deterministic** test that the text actually reaches the model (`assertRequestContains`).
|
|
148
|
+
3. A skill → **deterministic** test that it resolves via `pluginDir`, then offer the paid `measureTriggerRate` eval as a follow-up.
|
|
149
|
+
|
|
150
|
+
Then say which tier you used and why, and offer to climb a tier if the cheaper
|
|
151
|
+
test can't fully answer their question.
|
|
152
|
+
|
|
153
|
+
## Reference
|
|
154
|
+
|
|
155
|
+
The full guide — every tier, testing skills for real, "fired ≠ landed", the
|
|
156
|
+
safe-by-default sandbox, the coverage matrix, and how it compares to promptfoo —
|
|
157
|
+
is in [`docs/harness-testing.md`](../../docs/harness-testing.md).
|
package/dist/action.d.ts
DELETED
package/dist/action.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* GitHub Action entry point for vigiles.
|
|
4
|
-
*
|
|
5
|
-
* Runs `compile` or `audit` depending on the action input.
|
|
6
|
-
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
const node_fs_1 = require("node:fs");
|
|
9
|
-
const node_path_1 = require("node:path");
|
|
10
|
-
const node_child_process_1 = require("node:child_process");
|
|
11
|
-
const glob_1 = require("glob");
|
|
12
|
-
const compile_js_1 = require("./compile.js");
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Read action inputs
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
const command = process.env["INPUT_COMMAND"] ?? "audit";
|
|
17
|
-
const pathsInput = process.env["INPUT_PATHS"];
|
|
18
|
-
const maxRulesInput = process.env["INPUT_MAX-RULES"] ?? process.env["INPUT_MAX_RULES"];
|
|
19
|
-
const catalogOnly = (process.env["INPUT_CATALOG-ONLY"] ??
|
|
20
|
-
process.env["INPUT_CATALOG_ONLY"] ??
|
|
21
|
-
"false") === "true";
|
|
22
|
-
const maxRules = maxRulesInput ? Number(maxRulesInput) : undefined;
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Spec loading (from compiled dist/)
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
async function loadSpec(specPath) {
|
|
27
|
-
// Try several resolution strategies:
|
|
28
|
-
// 1. Replace /src/ with /dist/ (standard TS project layout)
|
|
29
|
-
// 2. Look under dist/ directly (root-level specs like CLAUDE.md.spec.ts)
|
|
30
|
-
// 3. Use the .js extension in-place (pre-compiled specs)
|
|
31
|
-
const jsName = specPath.replace(/\.ts$/, ".js");
|
|
32
|
-
const candidates = [
|
|
33
|
-
(0, node_path_1.resolve)(process.cwd(), jsName.replace(/\/src\//, "/dist/")),
|
|
34
|
-
(0, node_path_1.resolve)(process.cwd(), "dist", jsName),
|
|
35
|
-
(0, node_path_1.resolve)(process.cwd(), jsName),
|
|
36
|
-
];
|
|
37
|
-
// Deduplicate (candidate 1 and 3 may match when there's no /src/)
|
|
38
|
-
const unique = [...new Set(candidates)];
|
|
39
|
-
for (const distPath of unique) {
|
|
40
|
-
if (!(0, node_fs_1.existsSync)(distPath))
|
|
41
|
-
continue;
|
|
42
|
-
try {
|
|
43
|
-
const mod = (await import(distPath));
|
|
44
|
-
// CJS modules (TypeScript with module: "Node16", no "type":
|
|
45
|
-
// "module") produce `exports["default"] = spec`, so dynamic
|
|
46
|
-
// import wraps it as `{ default: { default: spec } }`. Unwrap
|
|
47
|
-
// the double-default when present.
|
|
48
|
-
const raw = mod.default;
|
|
49
|
-
if (raw && typeof raw === "object" && "default" in raw) {
|
|
50
|
-
return raw.default;
|
|
51
|
-
}
|
|
52
|
-
return raw;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Commands
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
async function runCompile() {
|
|
64
|
-
const specs = pathsInput
|
|
65
|
-
? pathsInput
|
|
66
|
-
.split(",")
|
|
67
|
-
.map((p) => p.trim())
|
|
68
|
-
.filter(Boolean)
|
|
69
|
-
: (0, glob_1.globSync)("**/*.spec.ts", { ignore: ["node_modules/**", "dist/**"] });
|
|
70
|
-
if (specs.length === 0) {
|
|
71
|
-
console.log("No .spec.ts files found.");
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
let allValid = true;
|
|
75
|
-
const basePath = process.cwd();
|
|
76
|
-
for (const specPath of specs) {
|
|
77
|
-
const spec = await loadSpec(specPath);
|
|
78
|
-
if (!spec) {
|
|
79
|
-
console.log(`::error file=${specPath}::Failed to load spec`);
|
|
80
|
-
allValid = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (spec._specType === "claude") {
|
|
84
|
-
const primaryOutput = specPath.replace(/\.spec\.ts$/, "");
|
|
85
|
-
const { markdown, errors, targets } = (0, compile_js_1.compileClaude)(spec, {
|
|
86
|
-
basePath,
|
|
87
|
-
specFile: specPath,
|
|
88
|
-
maxRules,
|
|
89
|
-
catalogOnly,
|
|
90
|
-
});
|
|
91
|
-
if (errors.length > 0) {
|
|
92
|
-
for (const err of errors) {
|
|
93
|
-
console.log(`::error file=${specPath}::${err.message}`);
|
|
94
|
-
}
|
|
95
|
-
allValid = false;
|
|
96
|
-
}
|
|
97
|
-
// Write primary target
|
|
98
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
|
|
99
|
-
const outputNames = [primaryOutput];
|
|
100
|
-
// Write additional targets with swapped heading + recomputed hash
|
|
101
|
-
for (const t of targets.slice(1)) {
|
|
102
|
-
const body = markdown
|
|
103
|
-
.replace(/^<!-- vigiles:[^\n]+\n\n?/, "")
|
|
104
|
-
.replace(/^# [^\n]+/, `# ${t}`);
|
|
105
|
-
const additional = (0, compile_js_1.addHash)(body, specPath);
|
|
106
|
-
const dir = primaryOutput.substring(0, primaryOutput.lastIndexOf("/") + 1);
|
|
107
|
-
const targetPath = dir + t;
|
|
108
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, targetPath), additional);
|
|
109
|
-
outputNames.push(targetPath);
|
|
110
|
-
}
|
|
111
|
-
console.log(`Compiled: ${specPath} → ${outputNames.join(", ")}`);
|
|
112
|
-
}
|
|
113
|
-
else if (spec._specType === "skill") {
|
|
114
|
-
const outputPath = specPath.replace(/\.spec\.ts$/, "");
|
|
115
|
-
const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
|
|
116
|
-
basePath,
|
|
117
|
-
specFile: specPath,
|
|
118
|
-
});
|
|
119
|
-
if (errors.length > 0) {
|
|
120
|
-
for (const err of errors) {
|
|
121
|
-
console.log(`::error file=${specPath}::${err.message}`);
|
|
122
|
-
}
|
|
123
|
-
allValid = false;
|
|
124
|
-
}
|
|
125
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, outputPath), markdown);
|
|
126
|
-
console.log(`Compiled: ${specPath} → ${outputPath}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return allValid;
|
|
130
|
-
}
|
|
131
|
-
function runAudit() {
|
|
132
|
-
// Delegate to the CLI so the action always runs the full audit flow
|
|
133
|
-
// (hash verification, spec validation, duplicate detection, coverage
|
|
134
|
-
// report, strengthen suggestions). Previously this only ran
|
|
135
|
-
// checkFileHash, which silently skipped everything else.
|
|
136
|
-
const files = pathsInput
|
|
137
|
-
? pathsInput
|
|
138
|
-
.split(",")
|
|
139
|
-
.map((p) => p.trim())
|
|
140
|
-
.filter(Boolean)
|
|
141
|
-
: [];
|
|
142
|
-
const cliPath = (0, node_path_1.resolve)(__dirname, "cli.js");
|
|
143
|
-
const args = ["audit", ...files];
|
|
144
|
-
const result = (0, node_child_process_1.spawnSync)(process.execPath, [cliPath, ...args], {
|
|
145
|
-
stdio: "inherit",
|
|
146
|
-
env: {
|
|
147
|
-
...process.env,
|
|
148
|
-
// Ensure the CLI emits GitHub annotations during the action run
|
|
149
|
-
GITHUB_ACTIONS: process.env.GITHUB_ACTIONS ?? "true",
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
if (result.error) {
|
|
153
|
-
console.log(`::error::Failed to run vigiles audit: ${result.error.message}`);
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
// Exit codes: 0 clean, 1 warnings, 2 hard errors.
|
|
157
|
-
return result.status === 0;
|
|
158
|
-
}
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// Main
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
void (async () => {
|
|
163
|
-
let valid;
|
|
164
|
-
if (command === "compile") {
|
|
165
|
-
valid = await runCompile();
|
|
166
|
-
}
|
|
167
|
-
else if (command === "audit") {
|
|
168
|
-
valid = runAudit();
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
console.log(`::error::Unknown vigiles command "${command}". Valid commands: compile, audit.`);
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
console.log(`::set-output name=valid::${String(valid)}`);
|
|
175
|
-
if (!valid) {
|
|
176
|
-
console.log("::error::vigiles failed — see errors above");
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
})();
|
|
180
|
-
//# sourceMappingURL=action.js.map
|
package/dist/refs.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/** An inline code span with its 1-based source line. */
|
|
2
|
-
export interface Span {
|
|
3
|
-
readonly text: string;
|
|
4
|
-
readonly line: number;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Extract inline code spans, skipping fenced code blocks (R1). Returns each
|
|
8
|
-
* span's trimmed text and 1-based line.
|
|
9
|
-
*/
|
|
10
|
-
export declare function inlineSpans(markdown: string): Span[];
|
|
11
|
-
/** A parsed file-qualified reference. */
|
|
12
|
-
export interface SymbolRef {
|
|
13
|
-
readonly file: string;
|
|
14
|
-
readonly symbol: string;
|
|
15
|
-
readonly line: number;
|
|
16
|
-
}
|
|
17
|
-
/** A reference that failed verification. */
|
|
18
|
-
export interface SymbolRefError extends SymbolRef {
|
|
19
|
-
readonly reason: string;
|
|
20
|
-
}
|
|
21
|
-
/** Extract the `vigiles:symbol` references from a markdown file. */
|
|
22
|
-
export declare function symbolRefs(markdown: string): SymbolRef[];
|
|
23
|
-
/**
|
|
24
|
-
* Verify the file-qualified symbol references in a markdown file: the named
|
|
25
|
-
* file must exist and define the named symbol. `basePath` is the directory the
|
|
26
|
-
* paths resolve against (the instruction file's own directory).
|
|
27
|
-
*/
|
|
28
|
-
export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[];
|
|
29
|
-
/**
|
|
30
|
-
* Whether a span looks like a *code reference* that ought to carry a
|
|
31
|
-
* file-qualified mark — a scoped name, or an identifier that isn't a bare
|
|
32
|
-
* lowercase prose word. A function-call form `` `foo(args)` `` is treated as a
|
|
33
|
-
* reference to its callee `foo`. Paths/filenames are excluded (they are `file`
|
|
34
|
-
* refs).
|
|
35
|
-
*/
|
|
36
|
-
export declare function isCodeShaped(text: string): boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Code-shaped inline references that are NOT yet marked — the spans the
|
|
39
|
-
* enforcement hook makes the agent mark as `` `vigiles:symbol path.ext#symbol` ``
|
|
40
|
-
* or opt out of with `<!-- vigiles:ignore -->` (or `<!-- vigiles:ignore-file -->`
|
|
41
|
-
* for the whole file).
|
|
42
|
-
*/
|
|
43
|
-
export declare function unmarkedCodeRefs(markdown: string): Span[];
|
|
44
|
-
//# sourceMappingURL=refs.d.ts.map
|
package/dist/run-scripts.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface ScriptRunResult {
|
|
2
|
-
readonly file: string;
|
|
3
|
-
readonly code: number;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Expand the given path/glob patterns into concrete script files. A pattern
|
|
7
|
-
* that is an existing file passes through unchanged; anything else is treated
|
|
8
|
-
* as a glob. Falls back to `defaultGlob` when no patterns are given. Results
|
|
9
|
-
* are deduped and sorted; `node_modules` and `dist` are always ignored.
|
|
10
|
-
*/
|
|
11
|
-
export declare function discoverScripts(patterns: readonly string[], defaultGlob: string, cwd: string): string[];
|
|
12
|
-
/**
|
|
13
|
-
* Run each script as `node <file>`, inheriting stdio so the script's own report
|
|
14
|
-
* streams to the console. `env` is merged over `process.env` for every child
|
|
15
|
-
* (e.g. `VIGILES_TRIALS`). Returns the per-file exit codes.
|
|
16
|
-
*/
|
|
17
|
-
export declare function runScripts(files: readonly string[], cwd: string, env?: NodeJS.ProcessEnv): ScriptRunResult[];
|
|
18
|
-
/** Format a one-line-per-file run summary with a pass/fail tally. */
|
|
19
|
-
export declare function formatScriptSummary(results: readonly ScriptRunResult[]): string;
|
|
20
|
-
//# sourceMappingURL=run-scripts.d.ts.map
|
package/dist/run-scripts.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.discoverScripts = discoverScripts;
|
|
4
|
-
exports.runScripts = runScripts;
|
|
5
|
-
exports.formatScriptSummary = formatScriptSummary;
|
|
6
|
-
/**
|
|
7
|
-
* vigiles — run harness-test / eval script files via the CLI.
|
|
8
|
-
*
|
|
9
|
-
* `vigiles test` and `vigiles eval` discover `*.harness.mjs` / `*.eval.mjs`
|
|
10
|
-
* scripts and run each as a child `node` process, so the two-tier
|
|
11
|
-
* harness-testing API (`src/harness-test.ts`, `src/eval.ts`) works as a CI
|
|
12
|
-
* command, not just `node x.mjs`. The scripts stay plain Node modules (they
|
|
13
|
-
* import from the built `dist/`), so they also run standalone — the CLI just
|
|
14
|
-
* discovers, runs, and aggregates exit codes.
|
|
15
|
-
*/
|
|
16
|
-
const node_child_process_1 = require("node:child_process");
|
|
17
|
-
const node_path_1 = require("node:path");
|
|
18
|
-
const node_fs_1 = require("node:fs");
|
|
19
|
-
const glob_1 = require("glob");
|
|
20
|
-
/**
|
|
21
|
-
* Expand the given path/glob patterns into concrete script files. A pattern
|
|
22
|
-
* that is an existing file passes through unchanged; anything else is treated
|
|
23
|
-
* as a glob. Falls back to `defaultGlob` when no patterns are given. Results
|
|
24
|
-
* are deduped and sorted; `node_modules` and `dist` are always ignored.
|
|
25
|
-
*/
|
|
26
|
-
function discoverScripts(patterns, defaultGlob, cwd) {
|
|
27
|
-
const globs = patterns.length > 0 ? patterns : [defaultGlob];
|
|
28
|
-
const found = new Set();
|
|
29
|
-
for (const p of globs) {
|
|
30
|
-
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, p))) {
|
|
31
|
-
found.add(p);
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
for (const m of (0, glob_1.globSync)(p, {
|
|
35
|
-
cwd,
|
|
36
|
-
ignore: ["node_modules/**", "dist/**"],
|
|
37
|
-
})) {
|
|
38
|
-
found.add(m);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return [...found].sort();
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Run each script as `node <file>`, inheriting stdio so the script's own report
|
|
45
|
-
* streams to the console. `env` is merged over `process.env` for every child
|
|
46
|
-
* (e.g. `VIGILES_TRIALS`). Returns the per-file exit codes.
|
|
47
|
-
*/
|
|
48
|
-
function runScripts(files, cwd, env = {}) {
|
|
49
|
-
const results = [];
|
|
50
|
-
for (const file of files) {
|
|
51
|
-
const res = (0, node_child_process_1.spawnSync)("node", [file], {
|
|
52
|
-
cwd,
|
|
53
|
-
stdio: "inherit",
|
|
54
|
-
env: { ...process.env, ...env },
|
|
55
|
-
});
|
|
56
|
-
results.push({ file, code: res.status ?? 1 });
|
|
57
|
-
}
|
|
58
|
-
return results;
|
|
59
|
-
}
|
|
60
|
-
/** Format a one-line-per-file run summary with a pass/fail tally. */
|
|
61
|
-
function formatScriptSummary(results) {
|
|
62
|
-
const lines = results.map((r) => ` ${r.code === 0 ? "✓" : "✗"} ${r.file}` +
|
|
63
|
-
(r.code === 0 ? "" : ` (exit ${String(r.code)})`));
|
|
64
|
-
const failed = results.filter((r) => r.code !== 0).length;
|
|
65
|
-
lines.push(failed === 0
|
|
66
|
-
? `\n${String(results.length)} passed.`
|
|
67
|
-
: `\n${String(failed)}/${String(results.length)} failed.`);
|
|
68
|
-
return lines.join("\n");
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=run-scripts.js.map
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|