vigiles 12.1.0 → 12.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.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: debug-my-harness
3
+ description: Diagnose why an agent harness misbehaved by reading the local flight-recorder ledger (.vigiles/runs.jsonl) — which skills fired or got hijacked, which hooks blocked or wrongly allowed, which subagent tool-contract violations happened, and how a skill's trigger rate moved. Use when asked why a skill stopped firing, why a hook didn't block, why the wrong skill ran, or to debug/investigate what the harness actually did. NOT for writing new rules (use strengthen) or editing the spec (use edit-spec).
4
+ ---
5
+
6
+ Diagnose harness misbehavior from the **flight recorder** — the local, append-only ledger
7
+ at `.vigiles/runs.jsonl` that vigiles writes as your harness runs. It records what actually
8
+ happened, so you debug from evidence instead of guessing.
9
+
10
+ ## What's in the ledger
11
+
12
+ One JSON record per line, each with a `kind`:
13
+
14
+ - `hook` — a compiled-hook gate decision: `{event, decision: allow|deny|ask, mode: enforce|observe, rule, cmd, reason}`.
15
+ - `agent` — a subagent tool-contract decision: `{name, tool, allowed, reason}` (a `false` = the agent went outside its lane).
16
+ - `skill` — a skill activation: `{name, fired}`.
17
+ - `eval` — a measured metric: `{name, metric, value}` (e.g. trigger-rate recall/precision).
18
+ - `capability-diff` — a blast-radius change: `{pr, added, removed, widened}`.
19
+
20
+ ## Instructions
21
+
22
+ ### Step 1: Read the ledger
23
+
24
+ Read `.vigiles/runs.jsonl` (JSONL — one record per line; tolerate a torn last line). If it's
25
+ absent or empty, say so — there's nothing recorded yet; suggest running the harness (or
26
+ `vigiles audit`) first. Do NOT fabricate records.
27
+
28
+ ### Step 2: Answer the specific question, evidence-first
29
+
30
+ Match the user's question to the ledger:
31
+
32
+ - **"Why did skill X stop firing / why does the wrong one run?"** — count `skill` fires by
33
+ name over time. If X's fire-rate dropped, look for a sibling that fired on the same kinds
34
+ of prompts (a **selection collision**) and check their descriptions for overlap. Recommend
35
+ differentiating or merging the descriptions.
36
+ - **"Why didn't my hook block that?"** — find `hook` records for the event. A `decision:
37
+ allow` on something that should be denied, or `mode: observe` (shadow, never blocks), or
38
+ the absence of any record, tells you which. Recommend flipping `observe`→`enforce` or
39
+ fixing the gate logic.
40
+ - **"Did a subagent misbehave?"** — list `agent` records with `allowed: false`: the agent
41
+ reached for a tool outside its declared contract. Point at the contract to tighten or widen.
42
+ - **"Is it getting worse?"** — compare `eval` metric values (recall/precision) across runs;
43
+ a downward trend is drift (often after a harness/model upgrade).
44
+
45
+ ### Step 3: Recommend a fix, tied to the evidence
46
+
47
+ Prefer **promoting an ignored-but-decidable rule from prose to a deterministic gate**: a
48
+ repeated `agent` violation or a rule the agent keeps breaking → a compiled hook or a tighter
49
+ tool-contract (the `strengthen` skill can help). A description collision → differentiate the
50
+ skill descriptions. Always cite the specific records you based the diagnosis on.
51
+
52
+ ### Step 4: Offer the next step
53
+
54
+ If the fix is a spec change, hand off to `edit-spec`. If it's promoting guidance to a linter
55
+ rule, hand off to `strengthen`. If a behavioral claim needs measuring (does the skill fire
56
+ now?), hand off to `test-harness` (`measureTriggerRate`).
@@ -1,74 +0,0 @@
1
- /** A hook's declared side-effect posture. */
2
- export type HookEffect = "observe" | "mutate";
3
- /** A declared hook (the typed source the compiler reads). */
4
- export interface HookSpec {
5
- /** The harness event, e.g. "PreToolUse" | "PostToolUse". */
6
- readonly event: string;
7
- /** The tool name(s) the hook matches, e.g. ["Bash"] or ["Edit", "Write"]. */
8
- readonly match: readonly string[];
9
- /** The `tool_input` fields the hook extracts (must exist on the matched tools). */
10
- readonly reads: readonly string[];
11
- /** Observe-only (a gate/checker) or allowed to mutate (an action runner). */
12
- readonly effect: HookEffect;
13
- /** Optional command the hook runs — classified against {@link effect}. */
14
- readonly run?: string;
15
- }
16
- /** Build a hook spec (the untyped on-ramp; see `hookFor` for the typed one). */
17
- export declare const hook: (spec: HookSpec) => HookSpec;
18
- /** tool name → the `tool_input` fields it carries (injected; harness-specific). */
19
- export type ToolFieldCatalog = Record<string, readonly string[]>;
20
- export interface HookIssue {
21
- readonly severity: "error" | "warning";
22
- readonly message: string;
23
- }
24
- export interface ValidateHookOptions {
25
- /** The per-tool `tool_input` field catalog for the active harness. */
26
- readonly toolFields: ToolFieldCatalog;
27
- /** The harness's known event names (optional — skips the event check if absent). */
28
- readonly events?: readonly string[];
29
- }
30
- /**
31
- * Validate a hook spec — the compiler half. Flags wrong-field extraction (Check 1)
32
- * and effect misdeclaration (Check 2), plus an unknown event when `events` is given.
33
- * Pure; the same checks `compileHook` enforces before emitting.
34
- */
35
- export declare function validateHook(spec: HookSpec, opts: ValidateHookOptions): HookIssue[];
36
- /** A settings `hooks` block keyed by the spec's event (the generated artifact). */
37
- export interface CompiledHook {
38
- readonly hooks: Record<string, readonly {
39
- readonly matcher: string;
40
- readonly hooks: readonly {
41
- readonly type: "command";
42
- readonly command: string;
43
- }[];
44
- }[]>;
45
- /** field → the extraction expression a generated hook would use (the typed read). */
46
- readonly extractions: Record<string, string>;
47
- }
48
- export declare class HookCompileError extends Error {
49
- }
50
- /**
51
- * Compile a validated hook to its settings block. Refuses (throws) on any error-level
52
- * issue — "an unsafe hook doesn't compile" — so a wrong-field read or a mutating
53
- * observe-hook never ships. The `extractions` map shows the correctly-typed field
54
- * reads the generated hook uses (here as jq paths) — never a hand-typed `jq` string.
55
- */
56
- export declare function compileHook(spec: HookSpec, opts: ValidateHookOptions & {
57
- gateCommand?: string;
58
- }): CompiledHook;
59
- /**
60
- * The typed source for a hook over a field map `M` (tool → field-union) and the
61
- * matched tool(s) `T`. `reads` is constrained to the fields of `T` — a field absent
62
- * from the matched tool is a tsc error. `M` stays unconstrained so a plain interface
63
- * (no index signature) works as the field map.
64
- */
65
- export interface TypedHookSpec<M, T extends keyof M & string> {
66
- readonly event: string;
67
- readonly match: readonly T[];
68
- readonly reads: readonly Extract<M[T], string>[];
69
- readonly effect: HookEffect;
70
- readonly run?: string;
71
- }
72
- /** Build a typed hook; `reads` outside the matched tool's field-union won't compile. */
73
- export declare function hookFor<M, T extends keyof M & string>(spec: TypedHookSpec<M, T>): HookSpec;
74
- //# sourceMappingURL=hook-spec.d.ts.map
@@ -1,130 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HookCompileError = exports.hook = void 0;
4
- exports.validateHook = validateHook;
5
- exports.compileHook = compileHook;
6
- exports.hookFor = hookFor;
7
- /**
8
- * SPIKE — typed, effect-classified HOOKS (the "type-safe bash" angle).
9
- *
10
- * A Claude Code / Codex hook today is hand-written shell in settings.json. Two
11
- * silent footguns dominate, and both are exactly what a COMPILER catches:
12
- *
13
- * 1. WRONG-FIELD EXTRACTION (silent no-op). A PreToolUse event's `tool_input`
14
- * shape depends on the matched tool — `Bash` carries `command`, `Edit`/`Write`
15
- * carry `file_path`. A hook that matches `Bash` but does
16
- * `jq '.tool_input.file_path'` extracts EMPTY forever and never fires; nothing
17
- * tells you. `validateHook` makes "read a field the matched tool never provides"
18
- * an error, and the typed `hookFor` builder makes it a tsc error at edit time.
19
- *
20
- * 2. EFFECT MISDECLARATION ("type-safe bash"). A hook declared observe-only (a
21
- * read-only gate/checker) that actually RUNS a mutating command — `eslint --fix`,
22
- * `git push` — is a side effect masquerading as an observation. We already own a
23
- * deterministic Bash-effect classifier (`bash-effects.ts`), so the command's
24
- * effect class becomes a TYPE: an `effect: "observe"` hook whose `run` command
25
- * classifies as side-effecting/undecidable does NOT compile. No other plugin
26
- * tool has the classifier to make that judgment.
27
- *
28
- * Pure core, harness-agnostic: the per-tool field catalog is INJECTED (a Codex hook
29
- * has different fields), and the Bash classifier is the harness-neutral one. The CC
30
- * field catalog + the typed edit-time builder live in the test (no CC literal in core).
31
- *
32
- * NOT wired to the CLI/public API — a spike to see whether the compile step earns its
33
- * keep on the hook surface. See research/harness-protocol-flow-moat.md.
34
- */
35
- const bash_effects_js_1 = require("./bash-effects.js");
36
- /** Build a hook spec (the untyped on-ramp; see `hookFor` for the typed one). */
37
- const hook = (spec) => spec;
38
- exports.hook = hook;
39
- /** Which matched tools actually carry `field`. */
40
- function toolsProviding(field, match, toolFields) {
41
- return match.filter((t) => (toolFields[t] ?? []).includes(field));
42
- }
43
- /** Check 1 — every read field exists on the matched tool(s). */
44
- function fieldIssues(spec, toolFields) {
45
- const out = [];
46
- for (const field of spec.reads) {
47
- const providers = toolsProviding(field, spec.match, toolFields);
48
- if (providers.length === 0) {
49
- out.push({
50
- severity: "error",
51
- message: `reads "${field}" but no matched tool (${spec.match.join("|")}) provides it — the extraction is always empty (silent no-op)`,
52
- });
53
- }
54
- else if (providers.length < spec.match.length) {
55
- const missing = spec.match.filter((t) => !providers.includes(t));
56
- out.push({
57
- severity: "warning",
58
- message: `reads "${field}", which ${missing.join("/")} does not carry — empty on those events`,
59
- });
60
- }
61
- }
62
- return out;
63
- }
64
- /** Check 2 — an observe-only hook's command must be provably read-only. */
65
- function effectIssues(spec) {
66
- if (spec.effect !== "observe" || spec.run === undefined)
67
- return [];
68
- const cls = (0, bash_effects_js_1.classifyBashCommand)(spec.run);
69
- if (cls === "read-only")
70
- return [];
71
- return [
72
- {
73
- severity: "error",
74
- message: `declared observe-only but its command is ${cls}: "${spec.run}" — an observe hook must not mutate (use effect:"mutate" or a read-only command)`,
75
- },
76
- ];
77
- }
78
- /**
79
- * Validate a hook spec — the compiler half. Flags wrong-field extraction (Check 1)
80
- * and effect misdeclaration (Check 2), plus an unknown event when `events` is given.
81
- * Pure; the same checks `compileHook` enforces before emitting.
82
- */
83
- function validateHook(spec, opts) {
84
- const out = [];
85
- if (opts.events && !opts.events.includes(spec.event)) {
86
- out.push({
87
- severity: "error",
88
- message: `unknown event "${spec.event}" — it will never fire`,
89
- });
90
- }
91
- out.push(...fieldIssues(spec, opts.toolFields));
92
- out.push(...effectIssues(spec));
93
- return out;
94
- }
95
- class HookCompileError extends Error {
96
- }
97
- exports.HookCompileError = HookCompileError;
98
- /**
99
- * Compile a validated hook to its settings block. Refuses (throws) on any error-level
100
- * issue — "an unsafe hook doesn't compile" — so a wrong-field read or a mutating
101
- * observe-hook never ships. The `extractions` map shows the correctly-typed field
102
- * reads the generated hook uses (here as jq paths) — never a hand-typed `jq` string.
103
- */
104
- function compileHook(spec, opts) {
105
- const issues = validateHook(spec, opts);
106
- const errors = issues.filter((i) => i.severity === "error");
107
- if (errors.length > 0) {
108
- throw new HookCompileError(`hook does not compile:\n ${errors.map((e) => e.message).join("\n ")}`);
109
- }
110
- const command = opts.gateCommand ?? "npx vigiles hook-runtime guard";
111
- const extractions = {};
112
- for (const field of spec.reads)
113
- extractions[field] = `.tool_input.${field}`;
114
- return {
115
- hooks: {
116
- [spec.event]: [
117
- {
118
- matcher: spec.match.join("|"),
119
- hooks: [{ type: "command", command }],
120
- },
121
- ],
122
- },
123
- extractions,
124
- };
125
- }
126
- /** Build a typed hook; `reads` outside the matched tool's field-union won't compile. */
127
- function hookFor(spec) {
128
- return spec;
129
- }
130
- //# sourceMappingURL=hook-spec.js.map