vigiles 26.0.1 → 26.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 +5 -4
- package/dist/adapters/claude-code/hook-condition.d.ts +46 -0
- package/dist/adapters/claude-code/hook-condition.js +142 -0
- package/dist/adapters/claude-code/hook-protocol.js +5 -0
- package/dist/audit-report.template.html +2 -2
- package/dist/cli.js +67 -115
- package/dist/core/bash-effects.d.ts +22 -0
- package/dist/core/bash-effects.js +10 -0
- package/dist/core/command-files.d.ts +107 -0
- package/dist/core/command-files.js +407 -0
- package/dist/core/hook-condition.d.ts +96 -0
- package/dist/core/hook-condition.js +63 -0
- package/dist/core/hook-matcher.d.ts +50 -0
- package/dist/core/hook-matcher.js +77 -2
- package/dist/core/hook-normalize.d.ts +51 -0
- package/dist/core/hook-normalize.js +61 -1
- package/dist/core/hook-program.d.ts +62 -1
- package/dist/core/hook-program.js +15 -1
- package/dist/core/hook-protocol.d.ts +16 -0
- package/dist/core/linters.js +97 -58
- package/dist/core/shell-vars.d.ts +74 -0
- package/dist/core/shell-vars.js +270 -0
- package/dist/core/skill-resources.d.ts +22 -1
- package/dist/core/skill-resources.js +2 -1
- package/dist/doc-test-script-coverage.d.ts +52 -0
- package/dist/doc-test-script-coverage.js +66 -0
- package/dist/guardrail-check.d.ts +29 -0
- package/dist/guardrail-check.js +69 -10
- package/dist/harness-assert.d.ts +8 -5
- package/dist/harness-assert.js +8 -5
- package/dist/harness-resolve-hooks.mjs +14 -37
- package/dist/hook-state-store.d.ts +143 -0
- package/dist/hook-state-store.js +241 -0
- package/dist/hook.d.ts +3 -1
- package/dist/hook.js +3 -1
- package/dist/run-hook.d.ts +33 -1
- package/dist/run-hook.js +46 -2
- package/dist/run-script.d.ts +94 -0
- package/dist/run-script.js +47 -26
- package/dist/scan-core.js +21 -3
- package/dist/score-core.d.ts +21 -1
- package/dist/score-core.js +30 -6
- package/dist/self-resolve.d.mts +20 -0
- package/dist/self-resolve.mjs +75 -0
- package/dist/spec-hooks.d.mts +10 -0
- package/dist/spec-hooks.mjs +17 -0
- package/dist/test.d.ts +5 -0
- package/dist/test.js +23 -2
- package/dist/verify-plugin-guards.d.ts +194 -0
- package/dist/verify-plugin-guards.js +822 -0
- package/package.json +1 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { type NonCommandHookAction } from "./core/hook-normalize.js";
|
|
2
|
+
export type { NonCommandHookAction } from "./core/hook-normalize.js";
|
|
3
|
+
import type { HarnessAdapter } from "./core/adapter.js";
|
|
4
|
+
import { type DisasterCategory, type DisasterEvent, type GuardrailResult } from "./guardrail-check.js";
|
|
5
|
+
import type { RunHookOptions } from "./run-hook.js";
|
|
6
|
+
/** The hook a sweep looked at, as its config declares it. */
|
|
7
|
+
export interface SweptHook {
|
|
8
|
+
/** The event it registers under, e.g. `"PreToolUse"`. */
|
|
9
|
+
readonly event: string;
|
|
10
|
+
/** Its tool matcher, or `null` when it declares none (matches everything). */
|
|
11
|
+
readonly matcher: string | null;
|
|
12
|
+
/** Its condition as written (Claude Code's `if`), or `null` when unconditional. */
|
|
13
|
+
readonly condition: string | null;
|
|
14
|
+
/** The command, with the harness's plugin-root token already expanded. */
|
|
15
|
+
readonly command: string;
|
|
16
|
+
/**
|
|
17
|
+
* Its position in the flattened registration list, so two hooks sharing a
|
|
18
|
+
* command are still distinguishable in a report. Stable for one sweep of one
|
|
19
|
+
* directory; not an identity across versions.
|
|
20
|
+
*/
|
|
21
|
+
readonly index: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* What happened to one hook. A DISCRIMINATED UNION, not a result list plus three
|
|
25
|
+
* nullable fields: `results` exists on exactly the outcome that has them, so
|
|
26
|
+
* "read the score of a hook that was never run" is a type error rather than a
|
|
27
|
+
* `0/7` someone quotes.
|
|
28
|
+
*/
|
|
29
|
+
export type SweptHookOutcome = {
|
|
30
|
+
/** The battery reached this hook; `results` holds one entry per event. */
|
|
31
|
+
readonly status: "measured";
|
|
32
|
+
readonly hook: SweptHook;
|
|
33
|
+
/** One result per battery event, in catalog order. */
|
|
34
|
+
readonly results: readonly GuardrailResult[];
|
|
35
|
+
/** Ids of the events it denied. */
|
|
36
|
+
readonly blocked: readonly string[];
|
|
37
|
+
/** Ids it ran on and let through. */
|
|
38
|
+
readonly allowed: readonly string[];
|
|
39
|
+
/** Ids the harness would never have handed it (condition did not match). */
|
|
40
|
+
readonly notRun: readonly string[];
|
|
41
|
+
} | {
|
|
42
|
+
/**
|
|
43
|
+
* The battery does not apply to this hook — a different event, or a matcher
|
|
44
|
+
* that selects none of the battery's tools. NOT a score of zero.
|
|
45
|
+
*/
|
|
46
|
+
readonly status: "not-applicable";
|
|
47
|
+
readonly hook: SweptHook;
|
|
48
|
+
/** One line naming which of the two it is, and against what. */
|
|
49
|
+
readonly reason: string;
|
|
50
|
+
} | {
|
|
51
|
+
/**
|
|
52
|
+
* The command names a variable nothing has set, so the program we would run
|
|
53
|
+
* is not the program the harness runs. Refusing is the honest answer; the
|
|
54
|
+
* fix is in the caller's hands (pass `env`).
|
|
55
|
+
*/
|
|
56
|
+
readonly status: "unresolved";
|
|
57
|
+
readonly hook: SweptHook;
|
|
58
|
+
/** One line naming the unset variables. */
|
|
59
|
+
readonly reason: string;
|
|
60
|
+
};
|
|
61
|
+
/** The whole sweep. */
|
|
62
|
+
export interface PluginGuardReport {
|
|
63
|
+
/** The directory swept, resolved to an absolute path. */
|
|
64
|
+
readonly dir: string;
|
|
65
|
+
/** The adapter that read it, e.g. `"claude-code"`. */
|
|
66
|
+
readonly harness: string;
|
|
67
|
+
/** The event each disaster was delivered as (default `"PreToolUse"`). */
|
|
68
|
+
readonly event: string;
|
|
69
|
+
/** The battery that was used, so a report says what it measured against. */
|
|
70
|
+
readonly events: readonly DisasterEvent[];
|
|
71
|
+
/** One outcome per declared COMMAND hook, in config order. */
|
|
72
|
+
readonly hooks: readonly SweptHookOutcome[];
|
|
73
|
+
/**
|
|
74
|
+
* Declared actions this tier cannot drive because they are not commands —
|
|
75
|
+
* `prompt`, `http`, `mcp_tool`, `agent`.
|
|
76
|
+
*
|
|
77
|
+
* 🔴 THEY USED TO BE DROPPED, AND DROPPING THEM MANUFACTURED THE FALSE EMPTY
|
|
78
|
+
* `notes` exists to prevent. A repository whose hooks are all `prompt` actions
|
|
79
|
+
* has declared guards; it was reported as declaring none, in the words of the
|
|
80
|
+
* one sentence this report writes to be sure nobody reads an empty result as a
|
|
81
|
+
* clean bill of health. Not measured is a limit of the tier and says so; not
|
|
82
|
+
* declared is an accusation about the repository, and it was not true.
|
|
83
|
+
*
|
|
84
|
+
* They carry no score and never will here — a shell battery cannot drive a
|
|
85
|
+
* prompt — so they are a separate list rather than a fourth outcome status
|
|
86
|
+
* with an invented command.
|
|
87
|
+
*/
|
|
88
|
+
readonly unmeasurable: readonly NonCommandHookAction[];
|
|
89
|
+
/**
|
|
90
|
+
* Why the sweep measured less than a reader might assume — no COMMAND hooks
|
|
91
|
+
* declared, a harness with no shell hooks, every hook on another event, or an
|
|
92
|
+
* action this tier cannot drive. Empty only when at least one hook was
|
|
93
|
+
* measured AND nothing was left undrivable: an undrivable action is a gap in
|
|
94
|
+
* COVERAGE, so it is said even when other hooks scored.
|
|
95
|
+
*
|
|
96
|
+
* 🔴 THIS IS THE EMPTY CASE'S VOICE. `hooks: []` on its own reads as a clean
|
|
97
|
+
* bill of health, which is the exact false confidence the battery exists to
|
|
98
|
+
* remove — so a sweep that measured nothing always says so in words.
|
|
99
|
+
*/
|
|
100
|
+
readonly notes: readonly string[];
|
|
101
|
+
}
|
|
102
|
+
/** Options for {@link experimental_verifyPluginGuards}. */
|
|
103
|
+
export interface VerifyPluginGuardsOptions extends Omit<RunHookOptions, "condition" | "protocol"> {
|
|
104
|
+
/**
|
|
105
|
+
* The harness to read the repo as. Defaults to Claude Code, so an existing
|
|
106
|
+
* Claude Code repo needs nothing. The condition grammar and the block protocol
|
|
107
|
+
* both come from this adapter's `hookProtocol`.
|
|
108
|
+
*/
|
|
109
|
+
readonly adapter?: HarnessAdapter;
|
|
110
|
+
/** Restrict the battery to these categories (default: the whole catalog). */
|
|
111
|
+
readonly categories?: readonly DisasterCategory[];
|
|
112
|
+
/** Override the battery entirely. */
|
|
113
|
+
readonly events?: readonly DisasterEvent[];
|
|
114
|
+
/** The event each disaster is delivered as (default `"PreToolUse"`). */
|
|
115
|
+
readonly event?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Run the disaster battery against every hook a plugin or repo declares, using
|
|
119
|
+
* each hook's OWN event, matcher and condition, and report per hook.
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { experimental_verifyPluginGuards } from "vigiles";
|
|
123
|
+
*
|
|
124
|
+
* const report = experimental_verifyPluginGuards(".");
|
|
125
|
+
* for (const h of report.hooks) {
|
|
126
|
+
* if (h.status === "measured")
|
|
127
|
+
* console.log(`${h.blocked.length}/${h.results.length} ${h.hook.command}`);
|
|
128
|
+
* else console.log(`⊘ ${h.status} ${h.hook.command} — ${h.reason}`);
|
|
129
|
+
* }
|
|
130
|
+
* for (const note of report.notes) console.log(note);
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* Nothing here needs a model or a key. The hooks it finds are the ones the
|
|
134
|
+
* harness would load, so a hook that is present on disk but not registered is
|
|
135
|
+
* absent from the report by construction — which is the correct answer, and the
|
|
136
|
+
* one you would not get by globbing `hooks/*.sh`.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ It RUNS each reachable hook. A hook is a program you did not necessarily
|
|
139
|
+
* write, so point this at a repo whose hooks you are willing to execute, or pass
|
|
140
|
+
* `trusted: false` / `sandbox: "auto"` (inherited from {@link RunHookOptions}) to
|
|
141
|
+
* confine them. `verifyGuardrail` has always had the same property; sweeping a
|
|
142
|
+
* whole plugin makes it worth saying out loud.
|
|
143
|
+
*
|
|
144
|
+
* @experimental Days old, with no consumer outside this repository. The REPORT
|
|
145
|
+
* SHAPE is the part most likely to move — specifically whether `not-applicable`
|
|
146
|
+
* stays one status or splits by cause, and whether the per-hook counts stay id
|
|
147
|
+
* arrays. The prefix comes off when that shape survives sweeping several real
|
|
148
|
+
* third-party repos unchanged; see docs/experimental.md.
|
|
149
|
+
*
|
|
150
|
+
* @param dir - the plugin or repo root to read hooks from.
|
|
151
|
+
*/
|
|
152
|
+
export declare function experimental_verifyPluginGuards(dir: string, opts?: VerifyPluginGuardsOptions): PluginGuardReport;
|
|
153
|
+
/**
|
|
154
|
+
* Render a {@link PluginGuardReport} as terminal text.
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* import {
|
|
158
|
+
* experimental_verifyPluginGuards,
|
|
159
|
+
* experimental_formatPluginGuardReport,
|
|
160
|
+
* } from "vigiles";
|
|
161
|
+
*
|
|
162
|
+
* console.log(
|
|
163
|
+
* experimental_formatPluginGuardReport(experimental_verifyPluginGuards(".")),
|
|
164
|
+
* );
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* NEUTRAL, the same way {@link formatGuardrailReport} is: it reports what each
|
|
168
|
+
* hook blocks without deciding whether that was the hook's job. A repo's config
|
|
169
|
+
* never says which of its hooks is meant to be a bash-safety guard, so a verdict
|
|
170
|
+
* here would be invented rather than read.
|
|
171
|
+
*
|
|
172
|
+
* 🔴 A HOOK THE BATTERY NEVER REACHED IS NEVER GIVEN A NUMBER. A `measured` hook
|
|
173
|
+
* prints `blocks n/7`; a `not-applicable` or `unresolved` one prints its REASON
|
|
174
|
+
* under a `⊘` heading and no count at all, because a rendered `0/7` is the same
|
|
175
|
+
* false confidence the discriminated union exists to prevent, reintroduced one
|
|
176
|
+
* layer up where the type system can no longer see it. For the same reason the
|
|
177
|
+
* report's `notes` are printed FIRST and in full: a sweep that measured nothing
|
|
178
|
+
* has to say so in words, since an output with no rows reads as a clean bill of
|
|
179
|
+
* health.
|
|
180
|
+
*
|
|
181
|
+
* MANY HOOKS STAY READABLE by grouping the unmeasured half BY REASON — a repo
|
|
182
|
+
* with thirty hooks usually has two or three distinct reasons — and naming at
|
|
183
|
+
* most {@link HOOKS_PER_REASON} hooks per reason before counting the rest. The
|
|
184
|
+
* measured half is never collapsed: those are the hooks you came for.
|
|
185
|
+
*
|
|
186
|
+
* @experimental It renders {@link PluginGuardReport}, whose SHAPE is the part
|
|
187
|
+
* most likely to move (see {@link experimental_verifyPluginGuards}), so a stable
|
|
188
|
+
* name here would promise a stability its only input does not have. The prefix
|
|
189
|
+
* comes off with the same change that takes it off the report.
|
|
190
|
+
*
|
|
191
|
+
* @param report - a sweep from {@link experimental_verifyPluginGuards}.
|
|
192
|
+
*/
|
|
193
|
+
export declare function experimental_formatPluginGuardReport(report: PluginGuardReport): string;
|
|
194
|
+
//# sourceMappingURL=verify-plugin-guards.d.ts.map
|