vigiles 15.0.2 → 15.0.3

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.
Files changed (82) hide show
  1. package/dist/adapters/claude-code/layout.js +3 -0
  2. package/dist/adapters/claude-code/run-scripts.d.ts +21 -9
  3. package/dist/adapters/claude-code/run-scripts.js +33 -22
  4. package/dist/adapters/codex/driver.js +3 -1
  5. package/dist/adapters/codex/eval.js +5 -0
  6. package/dist/audit-score.js +32 -6
  7. package/dist/check-count.d.ts +62 -3
  8. package/dist/check-count.js +123 -5
  9. package/dist/check.d.ts +55 -0
  10. package/dist/check.js +91 -0
  11. package/dist/cli.js +456 -54
  12. package/dist/core/bash-effects.d.ts +41 -0
  13. package/dist/core/bash-effects.js +278 -0
  14. package/dist/core/foreign-runner.d.ts +185 -0
  15. package/dist/core/foreign-runner.js +228 -0
  16. package/dist/core/harness-driver.d.ts +18 -1
  17. package/dist/core/hook-program.d.ts +319 -20
  18. package/dist/core/hook-program.js +743 -49
  19. package/dist/core/layout.d.ts +13 -0
  20. package/dist/core/lethal-trifecta.d.ts +93 -0
  21. package/dist/core/lethal-trifecta.js +409 -65
  22. package/dist/core/markdown.d.ts +32 -0
  23. package/dist/core/markdown.js +36 -0
  24. package/dist/core/merge-conflict.d.ts +50 -0
  25. package/dist/core/merge-conflict.js +84 -0
  26. package/dist/core/test-file-ext.d.ts +80 -0
  27. package/dist/core/test-file-ext.js +90 -0
  28. package/dist/core/types.d.ts +11 -0
  29. package/dist/coverage-artifact.d.ts +383 -0
  30. package/dist/coverage-artifact.js +586 -0
  31. package/dist/coverage-evidence.d.ts +87 -79
  32. package/dist/coverage-evidence.js +212 -211
  33. package/dist/coverage-probe.d.ts +125 -0
  34. package/dist/coverage-probe.js +700 -0
  35. package/dist/doc-commands.d.ts +119 -0
  36. package/dist/doc-commands.js +158 -0
  37. package/dist/eval-lock.d.ts +9 -0
  38. package/dist/eval-lock.js +14 -2
  39. package/dist/eval.js +28 -0
  40. package/dist/fs-walk.d.ts +80 -0
  41. package/dist/fs-walk.js +148 -0
  42. package/dist/harness-assert.js +42 -3
  43. package/dist/harness-test.js +31 -2
  44. package/dist/judge.js +4 -0
  45. package/dist/leaderboard.d.ts +16 -1
  46. package/dist/leaderboard.js +19 -2
  47. package/dist/load-hook.js +6 -1
  48. package/dist/mock-model.d.ts +58 -4
  49. package/dist/mock-model.js +133 -5
  50. package/dist/observe.d.ts +1 -1
  51. package/dist/observe.js +44 -1
  52. package/dist/plugin-loader.js +52 -13
  53. package/dist/run-hook.d.ts +58 -0
  54. package/dist/run-hook.js +70 -0
  55. package/dist/run-script.js +75 -0
  56. package/dist/scaffold-test.d.ts +3 -0
  57. package/dist/scaffold-test.js +10 -5
  58. package/dist/scan-behavioral.js +4 -0
  59. package/dist/scan-core.d.ts +22 -0
  60. package/dist/scan-core.js +46 -14
  61. package/dist/scan-files.js +19 -2
  62. package/dist/scan.d.ts +22 -5
  63. package/dist/scan.js +67 -13
  64. package/dist/skill-contract.d.ts +46 -0
  65. package/dist/skill-contract.js +201 -0
  66. package/dist/skill-refs.d.ts +67 -0
  67. package/dist/skill-refs.js +117 -0
  68. package/dist/test-coverage-files.d.ts +7 -1
  69. package/dist/test-coverage-files.js +66 -55
  70. package/dist/test-coverage.d.ts +155 -19
  71. package/dist/test-coverage.js +430 -91
  72. package/dist/testing.d.ts +8 -2
  73. package/dist/testing.js +19 -1
  74. package/dist/trigger-containment.d.ts +85 -0
  75. package/dist/trigger-containment.js +125 -0
  76. package/dist/ts-runner-caps.d.ts +16 -0
  77. package/dist/ts-runner-caps.js +43 -0
  78. package/dist/unit.d.ts +2 -2
  79. package/dist/unit.js +2 -1
  80. package/hooks/eval-lock-nudge.sh +14 -6
  81. package/package.json +2 -2
  82. package/skills/test-harness/SKILL.md +68 -2
package/dist/check.js CHANGED
@@ -5,6 +5,7 @@ exports.assertChecks = assertChecks;
5
5
  exports.tool = tool;
6
6
  exports.toolWith = toolWith;
7
7
  exports.notTool = notTool;
8
+ exports.onlyTools = onlyTools;
8
9
  exports.skill = skill;
9
10
  exports.output = output;
10
11
  exports.hookFired = hookFired;
@@ -57,6 +58,15 @@ const no = (message) => ({
57
58
  score: 0,
58
59
  message,
59
60
  });
61
+ /**
62
+ * `Bash(git:*)` → `Bash`. A declaration may carry a restriction; a trace never
63
+ * does, so the two only meet on the base name. Same one-liner as
64
+ * `core/tool-contract.ts` / `core/lethal-trifecta.ts`, kept local because the
65
+ * testing pillar does not otherwise depend on the linting core.
66
+ */
67
+ function baseTool(raw) {
68
+ return raw.split("(")[0].trim();
69
+ }
60
70
  function distinctToolNames(calls) {
61
71
  const names = [...new Set(calls.map((c) => c.name))];
62
72
  return names.length > 0 ? `[${names.join(", ")}]` : "no tools";
@@ -135,6 +145,87 @@ function notTool(name, args) {
135
145
  : { kind: "notTool", name },
136
146
  };
137
147
  }
148
+ /**
149
+ * The agent used **only** tools drawn from `allowed` — the white-list, and the
150
+ * missing half of a symmetry the file surface already has:
151
+ *
152
+ * | | "not this one" | "nothing but these" |
153
+ * | ----- | --------------- | ---------------------- |
154
+ * | files | `assertNoWrite` | `assertWroteOnly` |
155
+ * | tools | {@link notTool} | **`onlyTools`** (this) |
156
+ *
157
+ * Why the asymmetry mattered: `notTool` can only forbid the calls the test
158
+ * author thought of, and the set of undeclared tools is unbounded — so "this
159
+ * skill stayed inside the tools it declares" was not expressible, which is
160
+ * exactly the claim a `SKILL.md` frontmatter makes in prose. See
161
+ * `skillContract`, which builds this check from that declaration.
162
+ *
163
+ * **Fails closed on an empty trace.** A `Trace` records tool calls only when the
164
+ * run captured the stream (`transcript: true` on the harness tier; always on the
165
+ * eval tier), so an uncaptured run is indistinguishable from a tool-free one —
166
+ * and passing it would assert nothing. Same discipline as `assertWroteOnly`
167
+ * refusing a result that never recorded writes: "we didn't look" is not "it was
168
+ * clean".
169
+ *
170
+ * 🔴 A `Tool(restriction)` DECLARATION IS MATCHED BY ITS BASE NAME. `allowed-tools:
171
+ * Bash(git:*)` is the ordinary way to write a narrow grant, and `skillContract`
172
+ * hands that literal string here — while a trace only ever carries the base name
173
+ * `Bash`, because that is the tool the harness reports. Set membership therefore
174
+ * missed, and every legitimate `Bash` call was reported as outside the declared
175
+ * surface, against a declaration that literally lists Bash (measured 2026-08-11:
176
+ * `agent used tool(s) outside its declared set: Bash (declared: Bash(git:*),
177
+ * Read, Skill)`). A check that fires on its own happy path gets deleted, not
178
+ * debugged — and it would fire on exactly the authors who narrowed their grant.
179
+ *
180
+ * ⚠️ AND THE RESTRICTION ITSELF IS NOT VERIFIED HERE, deliberately. A trace names
181
+ * the tool, not the grant it was matched against, so `Bash(git:*)` can be held to
182
+ * "no tool outside the declared set" and no further; whether the command really
183
+ * was a `git` one is a claim this layer has no evidence for. Narrowing beyond the
184
+ * base name is `disallowed-tools:` + `lethal-trifecta`'s job (see
185
+ * `skill-contract.ts`), which reads the grant instead of the run. The same
186
+ * `split("(")` normalization the rest of the codebase already applies to tool
187
+ * declarations — `core/tool-contract.ts`, `core/lethal-trifecta.ts`,
188
+ * `core/delegation-trifecta.ts`.
189
+ *
190
+ * ⚠️ SPANS THE WHOLE TRACE, deliberately. It answers "did this RUN stay inside
191
+ * these tools?", which is the right question for a harness that drives one thing.
192
+ * It is the wrong question when the trace has setup calls before the surface
193
+ * under test activated, drives several skills, or dispatches a subagent whose
194
+ * nested calls the parser folds into the same flat list — and reporting those as
195
+ * a violation is an accusation about the wrong code.
196
+ *
197
+ * `skillContract` therefore does NOT use this directly: it wraps it in a window
198
+ * bounded by its own `Skill` activation (see `duringSkill` in
199
+ * `src/skill-contract.ts`). If you are checking one skill, prefer the contract;
200
+ * reach for this when the claim really is about the whole run.
201
+ */
202
+ function onlyTools(allowed) {
203
+ const allow = new Set(allowed.map(baseTool));
204
+ // The declaration is echoed VERBATIM: an author who wrote `Bash(git:*)` must
205
+ // read back what they wrote, not the normalized form the matching used.
206
+ const declared = [...new Set(allowed)].join(", ") || "none";
207
+ return {
208
+ kind: "onlyTools",
209
+ eval: (t) => {
210
+ if (t.toolCalls.length === 0)
211
+ return no(`onlyTools cannot run: this trace recorded no tool calls, so passing ` +
212
+ `it would assert nothing. Tool calls are parsed from the stream — ` +
213
+ `pass \`transcript: true\` on the harness tier (the eval tier always ` +
214
+ `captures it). If the run genuinely used no tools there is no tool ` +
215
+ `surface to constrain, and this check does not apply.`);
216
+ // Report EVERY distinct offender, not just the first: a skill reaching for
217
+ // three undeclared tools should not take three runs to discover that.
218
+ const offenders = [
219
+ ...new Set(t.toolCalls.map((c) => c.name).filter((name) => !allow.has(name))),
220
+ ];
221
+ if (offenders.length === 0)
222
+ return ok(`agent used only declared tool(s): ${declared}`);
223
+ return no(`agent used tool(s) outside its declared set: ${offenders.join(", ")} ` +
224
+ `(declared: ${declared})`);
225
+ },
226
+ toJSON: () => ({ kind: "onlyTools", allowed: [...allowed] }),
227
+ };
228
+ }
138
229
  /** A skill resolved to this id (`<plugin>:<skill>`) without erroring. */
139
230
  function skill(id) {
140
231
  return {