vigiles 5.2.0 → 7.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 +99 -48
- package/dist/action-gate.js +1 -1
- package/dist/adapters/claude-code/agent-runtime.d.ts +64 -4
- package/dist/adapters/claude-code/agent-runtime.js +131 -17
- package/dist/adapters/claude-code/dialect.d.ts +34 -0
- package/dist/adapters/claude-code/dialect.js +46 -33
- package/dist/adapters/claude-code/effect-region.js +1 -1
- package/dist/adapters/claude-code/skill-runtime.d.ts +1 -1
- package/dist/adapters/claude-code/skill-runtime.js +1 -9
- package/dist/adapters/claude-code/typed-spec.d.ts +58 -0
- package/dist/adapters/claude-code/typed-spec.js +55 -0
- package/dist/adapters/codex/hook-protocol.js +3 -0
- package/dist/adapters/codex/mock-model.js +1 -1
- package/dist/claude-code.d.ts +1 -0
- package/dist/claude-code.js +8 -1
- package/dist/cli-commands.d.ts +19 -0
- package/dist/cli-commands.js +51 -0
- package/dist/cli.js +735 -76
- package/dist/core/bash-effects.d.ts +12 -0
- package/dist/core/bash-effects.js +31 -0
- package/dist/core/capability-diff.d.ts +46 -0
- package/dist/core/capability-diff.js +97 -0
- 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/guards.d.ts +126 -0
- package/dist/core/guards.js +309 -0
- package/dist/core/harness-driver.d.ts +1 -1
- package/dist/core/hook-program.d.ts +459 -0
- package/dist/core/hook-program.js +468 -0
- package/dist/core/hook-protocol.d.ts +7 -0
- package/dist/core/hook-providers.d.ts +138 -0
- package/dist/core/hook-providers.js +155 -0
- package/dist/core/hook-spec.d.ts +74 -0
- package/dist/core/hook-spec.js +130 -0
- package/dist/core/inline.js +1 -1
- package/dist/core/mcp-tool.d.ts +12 -0
- package/dist/core/mcp-tool.js +20 -0
- package/dist/core/mcp.d.ts +13 -0
- package/dist/core/mcp.js +67 -0
- package/dist/core/spec.d.ts +290 -8
- package/dist/core/spec.js +118 -3
- package/dist/core/types.d.ts +8 -0
- package/dist/dialect-drift.d.ts +65 -0
- package/dist/dialect-drift.js +216 -0
- package/dist/eval.d.ts +40 -5
- package/dist/eval.js +59 -5
- package/dist/guardrail-check.d.ts +85 -0
- package/dist/guardrail-check.js +152 -0
- package/dist/harness-assert.d.ts +10 -0
- package/dist/harness-assert.js +30 -0
- package/dist/hook-install.d.ts +43 -0
- package/dist/hook-install.js +91 -0
- package/dist/hook.d.ts +52 -0
- package/dist/hook.js +98 -0
- package/dist/leaderboard.d.ts +6 -0
- package/dist/leaderboard.js +43 -1
- package/dist/linting.d.ts +9 -5
- package/dist/linting.js +17 -5
- package/dist/optimize.js +1 -1
- package/dist/scaffold-test.d.ts +28 -0
- package/dist/scaffold-test.js +134 -15
- package/dist/scan-behavioral.d.ts +60 -0
- package/dist/scan-behavioral.js +239 -1
- package/dist/scan.d.ts +14 -0
- package/dist/scan.js +33 -1
- package/dist/score-explainer.js +1 -1
- package/dist/self-command-refs.d.ts +21 -0
- package/dist/self-command-refs.js +125 -0
- package/dist/testing.d.ts +5 -3
- package/dist/testing.js +37 -23
- package/dist/tool-intercept.d.ts +4 -4
- package/dist/tool-intercept.js +5 -5
- package/dist/unit.d.ts +2 -0
- package/dist/unit.js +8 -1
- package/hooks/refs-nudge.sh +1 -1
- package/package.json +5 -3
package/dist/harness-assert.js
CHANGED
|
@@ -8,6 +8,8 @@ exports.assertNotCreated = assertNotCreated;
|
|
|
8
8
|
exports.assertServedTurns = assertServedTurns;
|
|
9
9
|
exports.assertHookBlocked = assertHookBlocked;
|
|
10
10
|
exports.assertHookAllowed = assertHookAllowed;
|
|
11
|
+
exports.assertHookDenies = assertHookDenies;
|
|
12
|
+
exports.assertHookAllows = assertHookAllows;
|
|
11
13
|
exports.egressHosts = egressHosts;
|
|
12
14
|
exports.assertNoEgress = assertNoEgress;
|
|
13
15
|
exports.assertEgressOnly = assertEgressOnly;
|
|
@@ -58,6 +60,7 @@ exports.assertTriggerRate = assertTriggerRate;
|
|
|
58
60
|
* vitest and jest, so the same object supports both.
|
|
59
61
|
*/
|
|
60
62
|
const harness_test_js_1 = require("./harness-test.js");
|
|
63
|
+
const hook_program_js_1 = require("./core/hook-program.js");
|
|
61
64
|
const check_js_1 = require("./check.js");
|
|
62
65
|
const agent_result_js_1 = require("./adapters/claude-code/agent-result.js");
|
|
63
66
|
const stats_js_1 = require("./stats.js");
|
|
@@ -137,6 +140,33 @@ function assertHookAllowed(r) {
|
|
|
137
140
|
fail(`expected the hook to allow, but it blocked (exit ${String(r.exitCode)}, decision ${String(r.decision)})`);
|
|
138
141
|
}
|
|
139
142
|
}
|
|
143
|
+
/** Render a {@link HookProgramOutcome} for an assertion message. */
|
|
144
|
+
function describeOutcome(o) {
|
|
145
|
+
if (o.kind === "decision")
|
|
146
|
+
return `${o.decision.kind} (a gate decision)`;
|
|
147
|
+
if (o.kind === "injection")
|
|
148
|
+
return `an injection`;
|
|
149
|
+
return `${o.reaction.kind} (a reaction)`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Assert a COMPILED hook (a `vigiles/hook` program) denies an event — evaluated
|
|
153
|
+
* in-process, no subprocess, no model. The cheapest way to test a gate's logic:
|
|
154
|
+
* pass the hook's default export and a raw event. (For the wired-into-the-real-CLI
|
|
155
|
+
* check, use {@link assertHookBlocked} over `runHook`.)
|
|
156
|
+
*/
|
|
157
|
+
function assertHookDenies(hook, event) {
|
|
158
|
+
const o = (0, hook_program_js_1.runHookProgram)(hook, event);
|
|
159
|
+
if (o.kind !== "decision" || o.decision.kind !== "deny") {
|
|
160
|
+
fail(`expected the hook to deny, got ${describeOutcome(o)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Assert a COMPILED hook allows an event (in-process). The twin of {@link assertHookDenies}. */
|
|
164
|
+
function assertHookAllows(hook, event) {
|
|
165
|
+
const o = (0, hook_program_js_1.runHookProgram)(hook, event);
|
|
166
|
+
if (o.kind !== "decision" || o.decision.kind !== "allow") {
|
|
167
|
+
fail(`expected the hook to allow, got ${describeOutcome(o)}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
140
170
|
const hostPort = (e) => `${e.host}:${String(e.port)}`;
|
|
141
171
|
/** The `host:port` strings a run attempted, e.g. `["registry.npmjs.org:443"]`. */
|
|
142
172
|
function egressHosts(r) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** The agnostic, committed home for hook SOURCE — one dir, cross-adapter. */
|
|
2
|
+
export declare const HOOKS_DIR = ".vigiles/hooks";
|
|
3
|
+
/** The committed home for registered context-provider SOURCE (v2). */
|
|
4
|
+
export declare const PROVIDERS_DIR = ".vigiles/providers";
|
|
5
|
+
/** Discover hook source files under {@link HOOKS_DIR} (stamps excluded). */
|
|
6
|
+
export declare function discoverHookFiles(cwd: string): string[];
|
|
7
|
+
/** Discover registered-provider source files under {@link PROVIDERS_DIR}. */
|
|
8
|
+
export declare function discoverProviderFiles(cwd: string): string[];
|
|
9
|
+
interface CommandHook {
|
|
10
|
+
readonly type: "command";
|
|
11
|
+
readonly command: string;
|
|
12
|
+
}
|
|
13
|
+
interface HookEntry {
|
|
14
|
+
readonly matcher?: string;
|
|
15
|
+
readonly hooks: readonly CommandHook[];
|
|
16
|
+
}
|
|
17
|
+
/** The CC-shaped structured block a compiled hook program carries. */
|
|
18
|
+
export type CompiledHooks = Record<string, readonly HookEntry[]>;
|
|
19
|
+
interface SettingsJson {
|
|
20
|
+
hooks?: Record<string, HookEntry[]>;
|
|
21
|
+
[k: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Idempotently merge a compiled hook's block into an existing `settings.json`
|
|
25
|
+
* object. Entries managed by THIS hook file (the runtime command references
|
|
26
|
+
* `hookPath`) are replaced; every unrelated entry — including the user's own
|
|
27
|
+
* hand-written hooks — is preserved.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeHooksJson(existing: SettingsJson, compiled: CompiledHooks, hookPath: string): SettingsJson;
|
|
30
|
+
interface TomlHookEntry {
|
|
31
|
+
matcher?: string;
|
|
32
|
+
command: string;
|
|
33
|
+
}
|
|
34
|
+
interface ConfigToml {
|
|
35
|
+
hooks?: Record<string, TomlHookEntry[]>;
|
|
36
|
+
[k: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/** The TOML sibling of {@link mergeHooksJson} (Codex `[[hooks.<event>]]`). */
|
|
39
|
+
export declare function mergeHooksToml(existing: ConfigToml, compiled: CompiledHooks, hookPath: string): ConfigToml;
|
|
40
|
+
/** Serialize a merged config back to its on-disk text (with trailing newline). */
|
|
41
|
+
export declare function serializeConfig(merged: Record<string, unknown>, format: "json" | "toml"): string;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=hook-install.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROVIDERS_DIR = exports.HOOKS_DIR = void 0;
|
|
4
|
+
exports.discoverHookFiles = discoverHookFiles;
|
|
5
|
+
exports.discoverProviderFiles = discoverProviderFiles;
|
|
6
|
+
exports.mergeHooksJson = mergeHooksJson;
|
|
7
|
+
exports.mergeHooksToml = mergeHooksToml;
|
|
8
|
+
exports.serializeConfig = serializeConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Hook installation — the bridge from a typed hook program to a wired harness,
|
|
11
|
+
* folded into `vigiles compile` (there is no stray `compile-hook` verb; the
|
|
12
|
+
* cohesive-cli-surface rule).
|
|
13
|
+
*
|
|
14
|
+
* The typed hook program is harness-NEUTRAL — it imports `vigiles/hook` and
|
|
15
|
+
* compiles to whatever harness — so its SOURCE lives in the agnostic,
|
|
16
|
+
* committed {@link HOOKS_DIR} (`.vigiles/hooks/`), never in a harness's own
|
|
17
|
+
* `.claude/`. `compile` discovers each hook there, compiles it, and MERGES the
|
|
18
|
+
* result into the active harness's native config (`.claude/settings.json` JSON
|
|
19
|
+
* / `config.toml` TOML) — so the harness is actually wired, not handed a
|
|
20
|
+
* paste-this block. The merge is idempotent: an entry is keyed by the runtime
|
|
21
|
+
* command's hook PATH, so recompiling updates in place and never duplicates,
|
|
22
|
+
* while a user's own hand-written hooks are preserved untouched. One source dir
|
|
23
|
+
* also means basenames are unique, so the stamp can key on the basename safely.
|
|
24
|
+
*/
|
|
25
|
+
const node_fs_1 = require("node:fs");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
const toml_1 = require("@iarna/toml");
|
|
28
|
+
/** The agnostic, committed home for hook SOURCE — one dir, cross-adapter. */
|
|
29
|
+
exports.HOOKS_DIR = ".vigiles/hooks";
|
|
30
|
+
/** The committed home for registered context-provider SOURCE (v2). */
|
|
31
|
+
exports.PROVIDERS_DIR = ".vigiles/providers";
|
|
32
|
+
/** A JS/TS hook source file (the `.json` stamp sidecar is never matched). */
|
|
33
|
+
const HOOK_SOURCE_RE = /\.(?:mjs|cjs|js|mts|cts|ts)$/;
|
|
34
|
+
/** List JS/TS source files under `dir` (relative to cwd), stamps excluded. */
|
|
35
|
+
function discoverSources(cwd, dir) {
|
|
36
|
+
const abs = (0, node_path_1.join)(cwd, dir);
|
|
37
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
38
|
+
return [];
|
|
39
|
+
return (0, node_fs_1.readdirSync)(abs)
|
|
40
|
+
.filter((f) => HOOK_SOURCE_RE.test(f) && !f.endsWith(".d.ts"))
|
|
41
|
+
.sort()
|
|
42
|
+
.map((f) => (0, node_path_1.join)(dir, f));
|
|
43
|
+
}
|
|
44
|
+
/** Discover hook source files under {@link HOOKS_DIR} (stamps excluded). */
|
|
45
|
+
function discoverHookFiles(cwd) {
|
|
46
|
+
return discoverSources(cwd, exports.HOOKS_DIR);
|
|
47
|
+
}
|
|
48
|
+
/** Discover registered-provider source files under {@link PROVIDERS_DIR}. */
|
|
49
|
+
function discoverProviderFiles(cwd) {
|
|
50
|
+
return discoverSources(cwd, exports.PROVIDERS_DIR);
|
|
51
|
+
}
|
|
52
|
+
/** True when an entry's command routes through the runtime for `hookPath`. */
|
|
53
|
+
function managesHook(entry, hookPath) {
|
|
54
|
+
return entry.hooks.some((h) => h.command.includes(hookPath));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Idempotently merge a compiled hook's block into an existing `settings.json`
|
|
58
|
+
* object. Entries managed by THIS hook file (the runtime command references
|
|
59
|
+
* `hookPath`) are replaced; every unrelated entry — including the user's own
|
|
60
|
+
* hand-written hooks — is preserved.
|
|
61
|
+
*/
|
|
62
|
+
function mergeHooksJson(existing, compiled, hookPath) {
|
|
63
|
+
const hooks = { ...(existing.hooks ?? {}) };
|
|
64
|
+
for (const [event, entries] of Object.entries(compiled)) {
|
|
65
|
+
const kept = (hooks[event] ?? []).filter((e) => !managesHook(e, hookPath));
|
|
66
|
+
hooks[event] = [...kept, ...entries];
|
|
67
|
+
}
|
|
68
|
+
return { ...existing, hooks };
|
|
69
|
+
}
|
|
70
|
+
/** Flatten a CC-shaped entry to Codex's flat `{matcher?, command}` form. */
|
|
71
|
+
function toTomlEntries(entries) {
|
|
72
|
+
return entries.flatMap((e) => e.hooks.map((h) => e.matcher === undefined
|
|
73
|
+
? { command: h.command }
|
|
74
|
+
: { matcher: e.matcher, command: h.command }));
|
|
75
|
+
}
|
|
76
|
+
/** The TOML sibling of {@link mergeHooksJson} (Codex `[[hooks.<event>]]`). */
|
|
77
|
+
function mergeHooksToml(existing, compiled, hookPath) {
|
|
78
|
+
const hooks = { ...(existing.hooks ?? {}) };
|
|
79
|
+
for (const [event, entries] of Object.entries(compiled)) {
|
|
80
|
+
const kept = (hooks[event] ?? []).filter((e) => !e.command.includes(hookPath));
|
|
81
|
+
hooks[event] = [...kept, ...toTomlEntries(entries)];
|
|
82
|
+
}
|
|
83
|
+
return { ...existing, hooks };
|
|
84
|
+
}
|
|
85
|
+
/** Serialize a merged config back to its on-disk text (with trailing newline). */
|
|
86
|
+
function serializeConfig(merged, format) {
|
|
87
|
+
return format === "toml"
|
|
88
|
+
? (0, toml_1.stringify)(merged).trimEnd() + "\n"
|
|
89
|
+
: JSON.stringify(merged, null, 2) + "\n";
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=hook-install.js.map
|
package/dist/hook.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vigiles/hook` — the **closed vocabulary** for authoring a compiled hook.
|
|
3
|
+
*
|
|
4
|
+
* A hook today is opaque shell (`bash guard.sh`): un-analyzable, and the author
|
|
5
|
+
* hand-writes the fragile parts (exit code, JSON field, a `grep` matcher) that
|
|
6
|
+
* cause the #1 verified hook pain — FALSE CONFIDENCE (a guardrail that looks
|
|
7
|
+
* like it blocks but silently doesn't). Invert it: author a **pure typed
|
|
8
|
+
* function** `(event) => Decision` against THIS surface, and `vigiles
|
|
9
|
+
* compile` emits the harness protocol for you. The whole false-confidence
|
|
10
|
+
* class becomes UNREPRESENTABLE — you never write the exit code or the field.
|
|
11
|
+
*
|
|
12
|
+
* The roles, each with its own output type so a category mistake is a `tsc`
|
|
13
|
+
* error, not a silent no-op:
|
|
14
|
+
* - `defineHook` / `defineFileGate` — a **gate** returns a `Decision`
|
|
15
|
+
* (`allow`/`deny`/`ask`); `deny` is the only thing that blocks.
|
|
16
|
+
* - `definePromptGate` — a **prompt gate** (UserPromptSubmit) sees the prompt
|
|
17
|
+
* TEXT and may `deny` to block it (a security filter).
|
|
18
|
+
* - `defineStopGate` — a **stop gate** (Stop/SubagentStop) may `deny` to keep
|
|
19
|
+
* the agent going (gate-until-tests-pass).
|
|
20
|
+
* - `defineInject` — an **inject** returns an `Injection` (context text); it
|
|
21
|
+
* has no `deny`, so "block on a SessionStart hook" won't compile.
|
|
22
|
+
* - `defineReact` — a **react** (PostToolUse) returns a `Reaction`; it sees the
|
|
23
|
+
* tool RESPONSE, its `run(cmd)` is effect-classified at construction, and it
|
|
24
|
+
* can't block (the tool already ran).
|
|
25
|
+
*
|
|
26
|
+
* Every gate takes a `mode`: `enforce` (default, blocks) or `observe` (the
|
|
27
|
+
* shadow/rollout mode — records what it WOULD block, never blocks). `observe` is
|
|
28
|
+
* harness-neutral (it just exits 0 + writes a local record).
|
|
29
|
+
*
|
|
30
|
+
* Matching is AST-backed (`command.runs("git push", { force })`), so it catches
|
|
31
|
+
* `cd x && git push -f` that the native `Bash(git:*)` glob misses.
|
|
32
|
+
*
|
|
33
|
+
* A gate may also decide on EXTERNAL STATE by declaring `needs` (e.g.
|
|
34
|
+
* `needs: ["git.branch"]`): the trusted runtime gathers those read-only facts and
|
|
35
|
+
* hands them in as `e.ctx` — the hook still does zero I/O, and reading an
|
|
36
|
+
* undeclared fact is a `tsc` error. Built-ins:
|
|
37
|
+
* `git.branch`/`git.isDirty`/`git.root`/`cwd`/`os.platform`/`env.isCI`. For a
|
|
38
|
+
* one-off off-catalog fact, the lightweight opt-out is an
|
|
39
|
+
* inline `provide(name, cmd)` (read-only) or `dangerously(name, cmd)` (the loud
|
|
40
|
+
* escape) right in `needs`. See `research/hook-context-providers.md`.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ Honest scope: compile/verify fix the hook's AUTHORING + LOGIC. They do NOT
|
|
43
|
+
* change DELIVERY — Claude Code's own subagent-bypass (#34692) means a
|
|
44
|
+
* PreToolUse hook (compiled or hand-written) does not fire for a subagent's
|
|
45
|
+
* tool calls. A gate is a strong default, never an unbypassable wall. See
|
|
46
|
+
* `docs/compiled-hooks.md`.
|
|
47
|
+
*/
|
|
48
|
+
export { defineHook, defineFileGate, definePromptGate, defineStopGate, tool, tools, allow, deny, ask, commandView, pathView, gateAction, hookMode, defineInject, inject, defineReact, run, notice, nothing, responseView, decideProgram, decideFileGate, decidePromptGate, decideStopGate, runInject, runReact, runHookProgram, decisionExitCode, dispatchKind, hookRouting, hookNeeds, compileHookProgram, checkHookImports, stampHook, verifyHookStamp, HookCompileError, } from "./core/hook-program.js";
|
|
49
|
+
export type { Decision, HookMode, GateAction, CommandView, PathView, ResponseView, BashToolEvent, FileToolEvent, PromptEvent, StopEvent, ReactEvent, SessionEvent, HookProgram, FileGateHook, PromptGateHook, StopGateHook, InjectHook, ReactHook, AnyHook, DispatchKind, Injection, Reaction, RunReaction, CompiledHookProgram, CompileHookOptions, RawHookEvent, HookProgramOutcome, } from "./core/hook-program.js";
|
|
50
|
+
export { provide, dangerously, defineProvider, provider, } from "./core/hook-providers.js";
|
|
51
|
+
export type { ProviderName, ProviderResults, HookCtx, NeedSpec, InlineProvider, RegisteredProvider, RegisteredRef, ProviderRegistry, } from "./core/hook-providers.js";
|
|
52
|
+
//# sourceMappingURL=hook.d.ts.map
|
package/dist/hook.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provider = exports.defineProvider = exports.dangerously = exports.provide = exports.HookCompileError = exports.verifyHookStamp = exports.stampHook = exports.checkHookImports = exports.compileHookProgram = exports.hookNeeds = exports.hookRouting = exports.dispatchKind = exports.decisionExitCode = exports.runHookProgram = exports.runReact = exports.runInject = exports.decideStopGate = exports.decidePromptGate = exports.decideFileGate = exports.decideProgram = exports.responseView = exports.nothing = exports.notice = exports.run = exports.defineReact = exports.inject = exports.defineInject = exports.hookMode = exports.gateAction = exports.pathView = exports.commandView = exports.ask = exports.deny = exports.allow = exports.tools = exports.tool = exports.defineStopGate = exports.definePromptGate = exports.defineFileGate = exports.defineHook = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `vigiles/hook` — the **closed vocabulary** for authoring a compiled hook.
|
|
6
|
+
*
|
|
7
|
+
* A hook today is opaque shell (`bash guard.sh`): un-analyzable, and the author
|
|
8
|
+
* hand-writes the fragile parts (exit code, JSON field, a `grep` matcher) that
|
|
9
|
+
* cause the #1 verified hook pain — FALSE CONFIDENCE (a guardrail that looks
|
|
10
|
+
* like it blocks but silently doesn't). Invert it: author a **pure typed
|
|
11
|
+
* function** `(event) => Decision` against THIS surface, and `vigiles
|
|
12
|
+
* compile` emits the harness protocol for you. The whole false-confidence
|
|
13
|
+
* class becomes UNREPRESENTABLE — you never write the exit code or the field.
|
|
14
|
+
*
|
|
15
|
+
* The roles, each with its own output type so a category mistake is a `tsc`
|
|
16
|
+
* error, not a silent no-op:
|
|
17
|
+
* - `defineHook` / `defineFileGate` — a **gate** returns a `Decision`
|
|
18
|
+
* (`allow`/`deny`/`ask`); `deny` is the only thing that blocks.
|
|
19
|
+
* - `definePromptGate` — a **prompt gate** (UserPromptSubmit) sees the prompt
|
|
20
|
+
* TEXT and may `deny` to block it (a security filter).
|
|
21
|
+
* - `defineStopGate` — a **stop gate** (Stop/SubagentStop) may `deny` to keep
|
|
22
|
+
* the agent going (gate-until-tests-pass).
|
|
23
|
+
* - `defineInject` — an **inject** returns an `Injection` (context text); it
|
|
24
|
+
* has no `deny`, so "block on a SessionStart hook" won't compile.
|
|
25
|
+
* - `defineReact` — a **react** (PostToolUse) returns a `Reaction`; it sees the
|
|
26
|
+
* tool RESPONSE, its `run(cmd)` is effect-classified at construction, and it
|
|
27
|
+
* can't block (the tool already ran).
|
|
28
|
+
*
|
|
29
|
+
* Every gate takes a `mode`: `enforce` (default, blocks) or `observe` (the
|
|
30
|
+
* shadow/rollout mode — records what it WOULD block, never blocks). `observe` is
|
|
31
|
+
* harness-neutral (it just exits 0 + writes a local record).
|
|
32
|
+
*
|
|
33
|
+
* Matching is AST-backed (`command.runs("git push", { force })`), so it catches
|
|
34
|
+
* `cd x && git push -f` that the native `Bash(git:*)` glob misses.
|
|
35
|
+
*
|
|
36
|
+
* A gate may also decide on EXTERNAL STATE by declaring `needs` (e.g.
|
|
37
|
+
* `needs: ["git.branch"]`): the trusted runtime gathers those read-only facts and
|
|
38
|
+
* hands them in as `e.ctx` — the hook still does zero I/O, and reading an
|
|
39
|
+
* undeclared fact is a `tsc` error. Built-ins:
|
|
40
|
+
* `git.branch`/`git.isDirty`/`git.root`/`cwd`/`os.platform`/`env.isCI`. For a
|
|
41
|
+
* one-off off-catalog fact, the lightweight opt-out is an
|
|
42
|
+
* inline `provide(name, cmd)` (read-only) or `dangerously(name, cmd)` (the loud
|
|
43
|
+
* escape) right in `needs`. See `research/hook-context-providers.md`.
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ Honest scope: compile/verify fix the hook's AUTHORING + LOGIC. They do NOT
|
|
46
|
+
* change DELIVERY — Claude Code's own subagent-bypass (#34692) means a
|
|
47
|
+
* PreToolUse hook (compiled or hand-written) does not fire for a subagent's
|
|
48
|
+
* tool calls. A gate is a strong default, never an unbypassable wall. See
|
|
49
|
+
* `docs/compiled-hooks.md`.
|
|
50
|
+
*/
|
|
51
|
+
var hook_program_js_1 = require("./core/hook-program.js");
|
|
52
|
+
// gate vocabulary
|
|
53
|
+
Object.defineProperty(exports, "defineHook", { enumerable: true, get: function () { return hook_program_js_1.defineHook; } });
|
|
54
|
+
Object.defineProperty(exports, "defineFileGate", { enumerable: true, get: function () { return hook_program_js_1.defineFileGate; } });
|
|
55
|
+
Object.defineProperty(exports, "definePromptGate", { enumerable: true, get: function () { return hook_program_js_1.definePromptGate; } });
|
|
56
|
+
Object.defineProperty(exports, "defineStopGate", { enumerable: true, get: function () { return hook_program_js_1.defineStopGate; } });
|
|
57
|
+
Object.defineProperty(exports, "tool", { enumerable: true, get: function () { return hook_program_js_1.tool; } });
|
|
58
|
+
Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return hook_program_js_1.tools; } });
|
|
59
|
+
Object.defineProperty(exports, "allow", { enumerable: true, get: function () { return hook_program_js_1.allow; } });
|
|
60
|
+
Object.defineProperty(exports, "deny", { enumerable: true, get: function () { return hook_program_js_1.deny; } });
|
|
61
|
+
Object.defineProperty(exports, "ask", { enumerable: true, get: function () { return hook_program_js_1.ask; } });
|
|
62
|
+
Object.defineProperty(exports, "commandView", { enumerable: true, get: function () { return hook_program_js_1.commandView; } });
|
|
63
|
+
Object.defineProperty(exports, "pathView", { enumerable: true, get: function () { return hook_program_js_1.pathView; } });
|
|
64
|
+
Object.defineProperty(exports, "gateAction", { enumerable: true, get: function () { return hook_program_js_1.gateAction; } });
|
|
65
|
+
Object.defineProperty(exports, "hookMode", { enumerable: true, get: function () { return hook_program_js_1.hookMode; } });
|
|
66
|
+
// inject vocabulary
|
|
67
|
+
Object.defineProperty(exports, "defineInject", { enumerable: true, get: function () { return hook_program_js_1.defineInject; } });
|
|
68
|
+
Object.defineProperty(exports, "inject", { enumerable: true, get: function () { return hook_program_js_1.inject; } });
|
|
69
|
+
// react vocabulary
|
|
70
|
+
Object.defineProperty(exports, "defineReact", { enumerable: true, get: function () { return hook_program_js_1.defineReact; } });
|
|
71
|
+
Object.defineProperty(exports, "run", { enumerable: true, get: function () { return hook_program_js_1.run; } });
|
|
72
|
+
Object.defineProperty(exports, "notice", { enumerable: true, get: function () { return hook_program_js_1.notice; } });
|
|
73
|
+
Object.defineProperty(exports, "nothing", { enumerable: true, get: function () { return hook_program_js_1.nothing; } });
|
|
74
|
+
Object.defineProperty(exports, "responseView", { enumerable: true, get: function () { return hook_program_js_1.responseView; } });
|
|
75
|
+
// runtime + decode (used by the `vigiles hook-runtime run-program` runtime and tests)
|
|
76
|
+
Object.defineProperty(exports, "decideProgram", { enumerable: true, get: function () { return hook_program_js_1.decideProgram; } });
|
|
77
|
+
Object.defineProperty(exports, "decideFileGate", { enumerable: true, get: function () { return hook_program_js_1.decideFileGate; } });
|
|
78
|
+
Object.defineProperty(exports, "decidePromptGate", { enumerable: true, get: function () { return hook_program_js_1.decidePromptGate; } });
|
|
79
|
+
Object.defineProperty(exports, "decideStopGate", { enumerable: true, get: function () { return hook_program_js_1.decideStopGate; } });
|
|
80
|
+
Object.defineProperty(exports, "runInject", { enumerable: true, get: function () { return hook_program_js_1.runInject; } });
|
|
81
|
+
Object.defineProperty(exports, "runReact", { enumerable: true, get: function () { return hook_program_js_1.runReact; } });
|
|
82
|
+
Object.defineProperty(exports, "runHookProgram", { enumerable: true, get: function () { return hook_program_js_1.runHookProgram; } });
|
|
83
|
+
Object.defineProperty(exports, "decisionExitCode", { enumerable: true, get: function () { return hook_program_js_1.decisionExitCode; } });
|
|
84
|
+
Object.defineProperty(exports, "dispatchKind", { enumerable: true, get: function () { return hook_program_js_1.dispatchKind; } });
|
|
85
|
+
Object.defineProperty(exports, "hookRouting", { enumerable: true, get: function () { return hook_program_js_1.hookRouting; } });
|
|
86
|
+
Object.defineProperty(exports, "hookNeeds", { enumerable: true, get: function () { return hook_program_js_1.hookNeeds; } });
|
|
87
|
+
// compile + integrity
|
|
88
|
+
Object.defineProperty(exports, "compileHookProgram", { enumerable: true, get: function () { return hook_program_js_1.compileHookProgram; } });
|
|
89
|
+
Object.defineProperty(exports, "checkHookImports", { enumerable: true, get: function () { return hook_program_js_1.checkHookImports; } });
|
|
90
|
+
Object.defineProperty(exports, "stampHook", { enumerable: true, get: function () { return hook_program_js_1.stampHook; } });
|
|
91
|
+
Object.defineProperty(exports, "verifyHookStamp", { enumerable: true, get: function () { return hook_program_js_1.verifyHookStamp; } });
|
|
92
|
+
Object.defineProperty(exports, "HookCompileError", { enumerable: true, get: function () { return hook_program_js_1.HookCompileError; } });
|
|
93
|
+
var hook_providers_js_1 = require("./core/hook-providers.js");
|
|
94
|
+
Object.defineProperty(exports, "provide", { enumerable: true, get: function () { return hook_providers_js_1.provide; } });
|
|
95
|
+
Object.defineProperty(exports, "dangerously", { enumerable: true, get: function () { return hook_providers_js_1.dangerously; } });
|
|
96
|
+
Object.defineProperty(exports, "defineProvider", { enumerable: true, get: function () { return hook_providers_js_1.defineProvider; } });
|
|
97
|
+
Object.defineProperty(exports, "provider", { enumerable: true, get: function () { return hook_providers_js_1.provider; } });
|
|
98
|
+
//# sourceMappingURL=hook.js.map
|
package/dist/leaderboard.d.ts
CHANGED
|
@@ -32,4 +32,10 @@ export declare function scoreReport(r: ScanReport): {
|
|
|
32
32
|
export declare function rankPlugins(dirs: readonly string[]): PluginScore[];
|
|
33
33
|
/** Format a ranked leaderboard as human-readable text. */
|
|
34
34
|
export declare function formatLeaderboard(scores: readonly PluginScore[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Format a ranked leaderboard as a Markdown table — the PUBLISHABLE form (a README,
|
|
37
|
+
* a gist, the leaderboard site). Shows the top 2 deductions per plugin; the full
|
|
38
|
+
* breakdown is in `--json`. Sibling of the plain-text {@link formatLeaderboard}.
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatLeaderboardMarkdown(scores: readonly PluginScore[]): string;
|
|
35
41
|
//# sourceMappingURL=leaderboard.d.ts.map
|
package/dist/leaderboard.js
CHANGED
|
@@ -16,8 +16,27 @@ exports.gradeFor = gradeFor;
|
|
|
16
16
|
exports.scoreReport = scoreReport;
|
|
17
17
|
exports.rankPlugins = rankPlugins;
|
|
18
18
|
exports.formatLeaderboard = formatLeaderboard;
|
|
19
|
+
exports.formatLeaderboardMarkdown = formatLeaderboardMarkdown;
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
19
21
|
const node_path_1 = require("node:path");
|
|
20
22
|
const scan_js_1 = require("./scan.js");
|
|
23
|
+
/** The plugin's declared name (`.claude-plugin/plugin.json`), for a real label in
|
|
24
|
+
* the ranking instead of a SHA-pinned dir basename. Falls back to the basename. */
|
|
25
|
+
function pluginLabel(dir) {
|
|
26
|
+
const p = (0, node_path_1.join)(dir, ".claude-plugin", "plugin.json");
|
|
27
|
+
if ((0, node_fs_1.existsSync)(p)) {
|
|
28
|
+
try {
|
|
29
|
+
const name = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"))
|
|
30
|
+
.name;
|
|
31
|
+
if (typeof name === "string" && name.length > 0)
|
|
32
|
+
return name;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// fall through to the basename
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return (0, node_path_1.basename)(dir) || dir;
|
|
39
|
+
}
|
|
21
40
|
// Penalty weights — broken-at-runtime costs most, footguns less, nudges least.
|
|
22
41
|
const W_MISSING_HOOK = 15; // a hook script that doesn't exist → never runs
|
|
23
42
|
const W_NO_DESCRIPTION = 10; // a skill with no usable description → can't trigger
|
|
@@ -139,7 +158,7 @@ function rankPlugins(dirs) {
|
|
|
139
158
|
const { score, issues } = scoreReport(report);
|
|
140
159
|
return {
|
|
141
160
|
dir,
|
|
142
|
-
name: (
|
|
161
|
+
name: pluginLabel(dir),
|
|
143
162
|
score,
|
|
144
163
|
grade: gradeFor(score),
|
|
145
164
|
issues,
|
|
@@ -164,4 +183,27 @@ function formatLeaderboard(scores) {
|
|
|
164
183
|
out.push("", "Structural health only (no model). Weights: missing hook -15, no-description", "skill -10, broken intra-plugin ref -8, agent-without-tool-contract -5,", "untested surface -3.");
|
|
165
184
|
return out.join("\n");
|
|
166
185
|
}
|
|
186
|
+
const LEADERBOARD_METHOD = "_Structural health only (deterministic, no model): missing hook −15, " +
|
|
187
|
+
"no-description skill −10, broken intra-plugin ref −8, " +
|
|
188
|
+
"agent-without-tool-contract −5, untested surface −3. " +
|
|
189
|
+
"Behavioural columns (trigger-rate, collisions, egress) stack on top._";
|
|
190
|
+
/**
|
|
191
|
+
* Format a ranked leaderboard as a Markdown table — the PUBLISHABLE form (a README,
|
|
192
|
+
* a gist, the leaderboard site). Shows the top 2 deductions per plugin; the full
|
|
193
|
+
* breakdown is in `--json`. Sibling of the plain-text {@link formatLeaderboard}.
|
|
194
|
+
*/
|
|
195
|
+
function formatLeaderboardMarkdown(scores) {
|
|
196
|
+
const lines = [
|
|
197
|
+
`### Plugin health leaderboard (${String(scores.length)} scanned)`,
|
|
198
|
+
"",
|
|
199
|
+
"| # | grade | score | plugin | top issues |",
|
|
200
|
+
"| --: | :--: | --: | :-- | :-- |",
|
|
201
|
+
];
|
|
202
|
+
scores.forEach((s, i) => {
|
|
203
|
+
const issues = s.issues.length > 0 ? s.issues.slice(0, 2).join("; ") : "— clean";
|
|
204
|
+
lines.push(`| ${String(i + 1)} | ${s.grade} | ${String(s.score)} | \`${s.name}\` | ${issues} |`);
|
|
205
|
+
});
|
|
206
|
+
lines.push("", LEADERBOARD_METHOD);
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
}
|
|
167
209
|
//# sourceMappingURL=leaderboard.js.map
|
package/dist/linting.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `vigiles/linting` — Pillar 1 entry point: the **linting layer** for instruction
|
|
3
|
-
* files. Re-exports the spec builders/types
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* files. Re-exports the spec builders/types + the public compile entry points
|
|
4
|
+
* under one concern-named import. This is the canonical pillar-1 surface; the
|
|
5
|
+
* spec builders are also at the package root (`vigiles`).
|
|
6
|
+
*
|
|
7
|
+
* Curated (named, not `export *`) so the internal compiler validators, hash
|
|
8
|
+
* helpers, and the linter cross-reference ENGINE stay out of the public surface,
|
|
9
|
+
* the api reports, and the docs site (the CLI imports those from the source).
|
|
6
10
|
*/
|
|
7
11
|
export * from "./core/spec.js";
|
|
8
|
-
export
|
|
9
|
-
export
|
|
12
|
+
export { compileClaude, compileSkill, compileAgent, compileRailway, CompileError, } from "./core/compile.js";
|
|
13
|
+
export type { CompileClaudeOptions, CompileClaudeResult, CompileSkillResult, CompileAgentResult, CompileRailwayOptions, CompileRailwayResult, } from "./core/compile.js";
|
|
10
14
|
//# sourceMappingURL=linting.d.ts.map
|
package/dist/linting.js
CHANGED
|
@@ -14,13 +14,25 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.compileRailway = exports.compileAgent = exports.compileSkill = exports.compileClaude = void 0;
|
|
17
18
|
/**
|
|
18
19
|
* `vigiles/linting` — Pillar 1 entry point: the **linting layer** for instruction
|
|
19
|
-
* files. Re-exports the spec builders/types
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* files. Re-exports the spec builders/types + the public compile entry points
|
|
21
|
+
* under one concern-named import. This is the canonical pillar-1 surface; the
|
|
22
|
+
* spec builders are also at the package root (`vigiles`).
|
|
23
|
+
*
|
|
24
|
+
* Curated (named, not `export *`) so the internal compiler validators, hash
|
|
25
|
+
* helpers, and the linter cross-reference ENGINE stay out of the public surface,
|
|
26
|
+
* the api reports, and the docs site (the CLI imports those from the source).
|
|
22
27
|
*/
|
|
28
|
+
// The spec authoring builders (claude/enforce/guidance/file/cmd/agent/skill/…).
|
|
23
29
|
__exportStar(require("./core/spec.js"), exports);
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
// Compile: only the public entry points + their option/result types.
|
|
31
|
+
var compile_js_1 = require("./core/compile.js");
|
|
32
|
+
Object.defineProperty(exports, "compileClaude", { enumerable: true, get: function () { return compile_js_1.compileClaude; } });
|
|
33
|
+
Object.defineProperty(exports, "compileSkill", { enumerable: true, get: function () { return compile_js_1.compileSkill; } });
|
|
34
|
+
Object.defineProperty(exports, "compileAgent", { enumerable: true, get: function () { return compile_js_1.compileAgent; } });
|
|
35
|
+
Object.defineProperty(exports, "compileRailway", { enumerable: true, get: function () { return compile_js_1.compileRailway; } });
|
|
36
|
+
// core/linters is the cross-reference ENGINE (checkLinterRule/editDistance/…),
|
|
37
|
+
// consumed by compile — not part of the public authoring surface.
|
|
26
38
|
//# sourceMappingURL=linting.js.map
|
package/dist/optimize.js
CHANGED
|
@@ -66,7 +66,7 @@ const ACTION_LABEL = {
|
|
|
66
66
|
fix: "FIX",
|
|
67
67
|
differentiate: "DIFFERENTIATE",
|
|
68
68
|
};
|
|
69
|
-
const measureHint = (dir) => `\`vigiles
|
|
69
|
+
const measureHint = (dir) => `\`vigiles measure ${dir} --prompts=<file>\` — real-model, runs on your subscription`;
|
|
70
70
|
/** Render an optimization plan for the CLI. */
|
|
71
71
|
function formatOptimize(rep) {
|
|
72
72
|
const head = `Harness health: ${String(rep.score)}/100 (${rep.grade}) — ${rep.dir}`;
|
package/dist/scaffold-test.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
export type SurfaceKind = "skill" | "agent" | "hook";
|
|
2
2
|
/** The cheapest meaningful tier for a surface kind (mirrors the test-harness skill). */
|
|
3
3
|
export type TestTier = "unit" | "harness" | "eval";
|
|
4
|
+
/** One field of a subagent's `result()` contract, parsed from its compiled `.md`. */
|
|
5
|
+
export interface ContractField {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** The `OutputFieldType` literal: `"string" | "number" | "boolean" | "string[]"`. */
|
|
8
|
+
readonly type: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A subagent's typed `result(ok, err)` outcome contract — the typed-spec payoff
|
|
12
|
+
* the generator turns into a real `assertAgentOk` test (no LLM judge). Parsed from
|
|
13
|
+
* the `vigiles:ok` / `vigiles:err` blocks the compiler emits into the agent `.md`.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResultContract {
|
|
16
|
+
readonly ok: readonly ContractField[];
|
|
17
|
+
readonly err: readonly ContractField[];
|
|
18
|
+
}
|
|
4
19
|
/** What the generator needs to know about a surface to scaffold its test. */
|
|
5
20
|
export interface ScaffoldInput {
|
|
6
21
|
readonly kind: SurfaceKind;
|
|
@@ -14,6 +29,19 @@ export interface ScaffoldInput {
|
|
|
14
29
|
readonly userInvoked?: boolean;
|
|
15
30
|
/** A subagent's declared tool contract (drives the assertion hint); null = inherits all. */
|
|
16
31
|
readonly tools?: readonly string[] | null;
|
|
32
|
+
/**
|
|
33
|
+
* The side-effecting tools in the contract (`effectSurface(tools).sideEffecting`,
|
|
34
|
+
* computed by the CLI with the resolved dialect — kept harness-agnostic here). A
|
|
35
|
+
* non-empty list drives a generated SAFETY check: the agent's "hole" is mocked/denied
|
|
36
|
+
* and asserted to stay in its lane — the typed `tools` contract writing its own test.
|
|
37
|
+
*/
|
|
38
|
+
readonly sideEffectingTools?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* The subagent's `result()` contract parsed from its compiled `.md`. Present →
|
|
41
|
+
* the generator emits a deterministic `assertAgentOk` outcome test (no LLM judge);
|
|
42
|
+
* the single most-concrete "the typed spec wrote your test" payoff.
|
|
43
|
+
*/
|
|
44
|
+
readonly resultContract?: ResultContract | null;
|
|
17
45
|
/** How the CLI invokes the hook (e.g. `bash hooks/pre-edit.sh`); a TODO when unknown. */
|
|
18
46
|
readonly hookCommand?: string;
|
|
19
47
|
}
|