vigiles 5.2.0 → 6.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/README.md +57 -16
- package/dist/adapters/claude-code/agent-runtime.d.ts +25 -0
- package/dist/adapters/claude-code/agent-runtime.js +43 -0
- package/dist/adapters/claude-code/dialect.d.ts +34 -0
- package/dist/adapters/claude-code/dialect.js +46 -33
- package/dist/adapters/claude-code/skill-runtime.js +0 -8
- package/dist/adapters/claude-code/typed-spec.d.ts +58 -0
- package/dist/adapters/claude-code/typed-spec.js +55 -0
- package/dist/claude-code.d.ts +1 -0
- package/dist/claude-code.js +8 -1
- package/dist/cli.js +150 -4
- package/dist/core/compile.d.ts +1 -1
- package/dist/core/compile.js +14 -0
- package/dist/core/generate-harness.d.ts +187 -0
- package/dist/core/generate-harness.js +337 -0
- package/dist/core/spec.d.ts +290 -8
- package/dist/core/spec.js +118 -3
- package/dist/scaffold-test.d.ts +28 -0
- package/dist/scaffold-test.js +113 -8
- package/package.json +1 -1
package/dist/scaffold-test.js
CHANGED
|
@@ -100,18 +100,68 @@ console.log(formatTriggerRateReport(report));
|
|
|
100
100
|
assertTriggerRate(report, { min: 0.8, maxFalsePositive: 0.3 });
|
|
101
101
|
`;
|
|
102
102
|
}
|
|
103
|
-
/** A
|
|
104
|
-
function
|
|
103
|
+
/** A JSON value placeholder for an `OutputFieldType`, for the `vigiles:ok` block. */
|
|
104
|
+
function placeholderFor(type) {
|
|
105
|
+
switch (type) {
|
|
106
|
+
case "number":
|
|
107
|
+
return 1;
|
|
108
|
+
case "boolean":
|
|
109
|
+
return true;
|
|
110
|
+
case "string[]":
|
|
111
|
+
return ["example"];
|
|
112
|
+
default:
|
|
113
|
+
return "example";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Render a `result(ok, err)` builder call reconstructed from the parsed contract. */
|
|
117
|
+
function renderContractBuilder(contract) {
|
|
118
|
+
const shape = (fields) => `{ ${fields.map((f) => `${f.name}: ${JSON.stringify(f.type)}`).join(", ")} }`;
|
|
119
|
+
return `result(\n ${shape(contract.ok)},\n ${shape(contract.err)},\n)`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The OUTCOME test, GENERATED FROM the subagent's `result()` contract: reconstruct
|
|
123
|
+
* the contract, build a matching `vigiles:ok` block, and `assertAgentOk` it —
|
|
124
|
+
* deterministic, no LLM judge. This is the typed-spec payoff a markdown
|
|
125
|
+
* `description:` cannot give you: a parseable, typed outcome a test reads directly.
|
|
126
|
+
*/
|
|
127
|
+
function outcomeSection(input, contract) {
|
|
128
|
+
const okValue = Object.fromEntries(contract.ok.map((f) => [f.name, placeholderFor(f.type)]));
|
|
129
|
+
const firstField = contract.ok[0]?.name;
|
|
130
|
+
const fieldAssertion = firstField
|
|
131
|
+
? `// TODO: assert the VALUES you expect (the shape is already validated above), e.g.:\n// assert.ok(value.${firstField}, "expected a ${firstField}");`
|
|
132
|
+
: "";
|
|
133
|
+
return `import assert from "node:assert/strict";
|
|
134
|
+
import { result } from "vigiles/spec";
|
|
135
|
+
import { assertAgentOk } from "vigiles/testing";
|
|
136
|
+
|
|
137
|
+
// Reconstructed from ${input.name}'s ## Output contract (its compiled .md) — the
|
|
138
|
+
// typed result() the spec wrote. assertAgentOk parses + validates the outcome
|
|
139
|
+
// with NO model judge; swap \`okOutput\` for a real \`runHarness\` turn (Part B in
|
|
140
|
+
// examples/harness/railway-result.harness.mjs) to assert REAL behaviour.
|
|
141
|
+
const contract = ${renderContractBuilder(contract)};
|
|
142
|
+
|
|
143
|
+
const okOutput = [
|
|
144
|
+
"${input.name} finished its task.",
|
|
145
|
+
"\`\`\`vigiles:ok",
|
|
146
|
+
${JSON.stringify(JSON.stringify(okValue))},
|
|
147
|
+
"\`\`\`",
|
|
148
|
+
].join("\\n");
|
|
149
|
+
|
|
150
|
+
const value = assertAgentOk(okOutput, contract); // deterministic — no LLM judge
|
|
151
|
+
${fieldAssertion}
|
|
152
|
+
console.log("✓ ${input.name}: result() outcome parses + validates against its typed contract");
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
/** The fallback when the subagent has no `result()` contract — assert a tool use. */
|
|
156
|
+
function fallbackSection(input) {
|
|
105
157
|
const toolHint = input.tools && input.tools.length > 0
|
|
106
158
|
? `assertToolUsed(r, ${JSON.stringify(input.tools[0])}); // its declared contract: ${input.tools.join(", ")}`
|
|
107
159
|
: `assertToolUsed(r, "Task"); // TODO: assert what the subagent should do`;
|
|
108
|
-
return
|
|
109
|
-
import { runHarnessTest, assertToolUsed } from "vigiles/testing";
|
|
110
|
-
|
|
111
|
-
// A subagent's OUTCOME is best asserted via a result() contract — deterministic,
|
|
112
|
-
// no LLM judge. If ${input.name} has one, use assertAgentOk(r.output, contract)
|
|
113
|
-
// instead; see the railway-result example in the vigiles docs.
|
|
160
|
+
return `import { runHarnessTest, assertToolUsed } from "vigiles/testing";
|
|
114
161
|
|
|
162
|
+
// ${input.name} has no result() contract, so its outcome can't be asserted
|
|
163
|
+
// deterministically — add one (result() on its agent() spec) for a no-judge
|
|
164
|
+
// outcome test. For now, assert it reaches for the right tool.
|
|
115
165
|
const r = await runHarnessTest({
|
|
116
166
|
plugin: ".", // TODO: the plugin dir holding this subagent
|
|
117
167
|
// TODO: a prompt that dispatches ${input.name} (via the Task tool).
|
|
@@ -124,6 +174,61 @@ ${toolHint}
|
|
|
124
174
|
console.log("✓ ${input.name}: subagent test ran");
|
|
125
175
|
`;
|
|
126
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* The SAFETY check, GENERATED FROM the subagent's side-effecting `tools`: assert it
|
|
179
|
+
* stays inside its declared write surface and never reaches for a destructive op.
|
|
180
|
+
* The `tools` allowlist + effectSurface identify the "hole"; the check asserts it —
|
|
181
|
+
* a test the typed contract writes for you (markdown can declare the tools, not test them).
|
|
182
|
+
*/
|
|
183
|
+
function safetySection(input, sideEffecting) {
|
|
184
|
+
const checks = [];
|
|
185
|
+
if (sideEffecting.includes("Bash")) {
|
|
186
|
+
checks.push(` notTool("Bash", { command: /git push|rm -rf/ }), // never a destructive op`);
|
|
187
|
+
}
|
|
188
|
+
if (sideEffecting.some((t) => t === "Write" || t === "Edit")) {
|
|
189
|
+
checks.push(` didNotWrite("secrets.env"), // TODO: the path(s) it must NOT write outside its surface`);
|
|
190
|
+
}
|
|
191
|
+
if (checks.length === 0) {
|
|
192
|
+
checks.push(` // TODO: a notTool()/didNotWrite() per side-effecting tool: ${sideEffecting.join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
return `
|
|
195
|
+
// --- Safety (deterministic) — generated from ${input.name}'s side-effecting tools: ${sideEffecting.join(", ")} ---
|
|
196
|
+
// In a real run, replace this constructed Trace with a real \`runHarness\` /
|
|
197
|
+
// \`measure\` turn (use interceptTools so a real model's attempt is DENIED, never
|
|
198
|
+
// executed — see docs/eval-architecture.md). The checks below are derived from the
|
|
199
|
+
// declared tools contract — the agent's "hole" asserted to stay in its lane.
|
|
200
|
+
{
|
|
201
|
+
const trace = {
|
|
202
|
+
output: "done",
|
|
203
|
+
turns: 1,
|
|
204
|
+
hooks: [],
|
|
205
|
+
toolCalls: [
|
|
206
|
+
// TODO: the tool calls a benign run of ${input.name} makes.
|
|
207
|
+
{ name: "Bash", input: { command: "git status" } },
|
|
208
|
+
],
|
|
209
|
+
file: () => null,
|
|
210
|
+
};
|
|
211
|
+
assertChecks(trace, [
|
|
212
|
+
${checks.join("\n")}
|
|
213
|
+
]);
|
|
214
|
+
console.log("✓ ${input.name}: stayed inside its declared side-effect surface");
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
/** A subagent → deterministic outcome + safety tests, generated from its typed contract. */
|
|
219
|
+
function agentScaffold(input) {
|
|
220
|
+
const head = header(`Starter harness test for the \`${input.name}\` subagent.`, `npx vigiles test ${suggestedPath(input)}`);
|
|
221
|
+
const safetyImport = input.sideEffectingTools && input.sideEffectingTools.length > 0
|
|
222
|
+
? `import { notTool, didNotWrite, assertChecks } from "vigiles/testing";\n`
|
|
223
|
+
: "";
|
|
224
|
+
const body = input.resultContract
|
|
225
|
+
? outcomeSection(input, input.resultContract)
|
|
226
|
+
: fallbackSection(input);
|
|
227
|
+
const safety = input.sideEffectingTools && input.sideEffectingTools.length > 0
|
|
228
|
+
? safetySection(input, input.sideEffectingTools)
|
|
229
|
+
: "";
|
|
230
|
+
return `${head}\n${safetyImport}${body}${safety}`;
|
|
231
|
+
}
|
|
127
232
|
const TIER = {
|
|
128
233
|
hook: "unit",
|
|
129
234
|
skill: "eval",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Lint & test the harness your AI agent runs on — verify the references in your CLAUDE.md / AGENTS.md and test that your hooks and skills actually work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|