vigiles 32.1.0 → 32.1.1
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/dist/cli-main.js +6 -5
- package/dist/core/hook-program.d.ts +67 -1
- package/dist/core/hook-program.js +73 -1
- package/dist/coverage-probe.d.ts +15 -0
- package/dist/coverage-probe.js +29 -0
- package/dist/hook-check-install.d.ts +3 -0
- package/dist/hook-check-install.js +57 -0
- package/dist/hook-check.d.ts +36 -0
- package/dist/hook-check.js +125 -0
- package/dist/hook-install.d.ts +0 -36
- package/dist/hook-install.js +0 -38
- package/dist/hook-runtime.js +17 -24
- package/dist/local-files.js +1 -0
- package/package.json +1 -1
package/dist/cli-main.js
CHANGED
|
@@ -69,6 +69,7 @@ const action_gate_js_1 = require("./action-gate.js");
|
|
|
69
69
|
const guards_js_1 = require("./core/guards.js");
|
|
70
70
|
const hook_program_js_1 = require("./core/hook-program.js");
|
|
71
71
|
const hook_install_js_1 = require("./hook-install.js");
|
|
72
|
+
const hook_check_install_js_1 = require("./hook-check-install.js");
|
|
72
73
|
const hook_providers_js_1 = require("./core/hook-providers.js");
|
|
73
74
|
const toml_1 = require("@iarna/toml");
|
|
74
75
|
// The named-state STORE. Lifted out of this file so a TEST can seed a fact
|
|
@@ -6017,12 +6018,11 @@ async function installHookFile(file, adapter, registeredProviders = []) {
|
|
|
6017
6018
|
// cannot load must block — the repo seized, every command refused including the repair.
|
|
6018
6019
|
//
|
|
6019
6020
|
// 🔴 AND LAUNCHED LOCALLY, NOT THROUGH `npx` — 193 ms against 2545 ms on a warm
|
|
6020
|
-
// cache, thirteen times, on every tool call. The
|
|
6021
|
-
// shell does when that
|
|
6022
|
-
//
|
|
6023
|
-
// and for why a gate and a nudge must answer differently.
|
|
6021
|
+
// cache, thirteen times, on every tool call. The `|| exit N` ending is what
|
|
6022
|
+
// the shell does when that file cannot start at all. `compileHookProgram`
|
|
6023
|
+
// adds it (see `hookRuntimeMissingExit`), so it is not written here.
|
|
6024
6024
|
gateCommand: `${(0, hook_install_js_1.hookRuntimeRef)(adapter.layout.projectRootTokens)} hook-runtime run-program ` +
|
|
6025
|
-
|
|
6025
|
+
(0, hook_install_js_1.hookGateRef)(ref, adapter.layout.projectRootTokens),
|
|
6026
6026
|
dialect: adapter.dialect,
|
|
6027
6027
|
hookProtocol: adapter.hookProtocol,
|
|
6028
6028
|
settings: adapter.layout.settings,
|
|
@@ -6171,6 +6171,7 @@ async function installHooks(hookFiles, harnessFlag, configHarness) {
|
|
|
6171
6171
|
}
|
|
6172
6172
|
}
|
|
6173
6173
|
}
|
|
6174
|
+
(0, hook_check_install_js_1.installHookCheck)(adapters);
|
|
6174
6175
|
return ok;
|
|
6175
6176
|
}
|
|
6176
6177
|
/**
|
|
@@ -408,7 +408,11 @@ export interface CompiledHookProgram {
|
|
|
408
408
|
* core depends only on these interfaces, never an adapter (core ⊄ adapter).
|
|
409
409
|
*/
|
|
410
410
|
export interface CompileHookOptions {
|
|
411
|
-
/**
|
|
411
|
+
/**
|
|
412
|
+
* The command the emitted block runs. The compiler adds `|| exit 2` (a hook
|
|
413
|
+
* that can block) or `|| exit 0` (a reminder-only hook) at the end; one that is
|
|
414
|
+
* already there is replaced.
|
|
415
|
+
*/
|
|
412
416
|
readonly gateCommand?: string;
|
|
413
417
|
/** Validate `hook.on` against this harness's hook-event catalog (a typo won't compile). */
|
|
414
418
|
readonly dialect?: HarnessDialect;
|
|
@@ -451,6 +455,45 @@ export type AnyHook = HookProgram<ErasedNeeds> | FileGateHook<ErasedNeeds> | Pro
|
|
|
451
455
|
*/
|
|
452
456
|
export type DispatchKind = "bash-gate" | "file-gate" | "prompt-gate" | "stop-gate" | "inject" | "react";
|
|
453
457
|
export declare function dispatchKind(hook: AnyHook): DispatchKind;
|
|
458
|
+
/**
|
|
459
|
+
* What the shell should do when a hook's command fails before the hook gives an
|
|
460
|
+
* answer: the runtime is not installed, or it starts but cannot load the hook.
|
|
461
|
+
* No hook code ran, so the hook cannot be asked. The answer has to be written
|
|
462
|
+
* into the command itself, and this function is the one place that decides it.
|
|
463
|
+
*
|
|
464
|
+
* There are two kinds of hook and they need different answers:
|
|
465
|
+
*
|
|
466
|
+
* - A gate (it can block a tool call) must exit 2. A gate that cannot run must
|
|
467
|
+
* not let calls through: a protection that silently stops working is worse
|
|
468
|
+
* than one that is known to be missing.
|
|
469
|
+
* - A nudge (it only adds a reminder: an inject or a react) must exit 0. If it
|
|
470
|
+
* blocked, one missing file could stop the whole session. This happened on
|
|
471
|
+
* 2026-08-10: merge-conflict markers in `package.json` stopped every hook from
|
|
472
|
+
* loading, the Bash gate then refused `git merge --abort` (the command that
|
|
473
|
+
* undoes the damage), and the session could not be repaired from inside.
|
|
474
|
+
*
|
|
475
|
+
* The kind is fixed by the type the author picked: a react hook has no `deny`,
|
|
476
|
+
* and an inject hook only returns text.
|
|
477
|
+
*
|
|
478
|
+
* Only this function decides. The runtime used to guess a second time, from the
|
|
479
|
+
* hook's file name ("inject" in the name meant nudge), and the two guesses
|
|
480
|
+
* disagreed (#312). Now the runtime always exits 2 when a hook cannot load, and
|
|
481
|
+
* the `|| exit N` ending (see {@link withRoleExit}) is the only thing that turns
|
|
482
|
+
* that 2 into a 0. It is shell, so a different runtime version cannot change it.
|
|
483
|
+
*
|
|
484
|
+
* This does not rescue a gate that cannot load: it still refuses every command,
|
|
485
|
+
* including the install that would fix it. It only stops a nudge doing the same.
|
|
486
|
+
*
|
|
487
|
+
* (Other tools disagree on this too: husky and lefthook skip, pre-commit fails.)
|
|
488
|
+
*/
|
|
489
|
+
export declare function hookRuntimeMissingExit(kind: DispatchKind): 0 | 2;
|
|
490
|
+
/**
|
|
491
|
+
* `command` with `|| exit 0` or `|| exit 2` (chosen by the kind of hook) as its
|
|
492
|
+
* last words. A `|| exit N` already there is replaced, not added to: the shell
|
|
493
|
+
* stops at the first `exit` it reaches, so a nudge wired `|| exit 2 || exit 0`
|
|
494
|
+
* would still block. Applying it twice gives the same result as applying it once.
|
|
495
|
+
*/
|
|
496
|
+
export declare function withRoleExit(command: string, kind: DispatchKind): string;
|
|
454
497
|
/** A gate's {@link HookMode} (`enforce` default); non-gate roles report `enforce` too. */
|
|
455
498
|
export declare function hookMode(hook: AnyHook): HookMode;
|
|
456
499
|
/** The context providers a hook declared via `needs` ([] if none / not a gate). */
|
|
@@ -1109,6 +1152,29 @@ export declare function isStampRepairEvent(event: RawHookEvent, hookFile: string
|
|
|
1109
1152
|
* loads, the gate decides normally and `git merge --abort` is an ordinary allowed
|
|
1110
1153
|
* command — through the gate, not around it.
|
|
1111
1154
|
*
|
|
1155
|
+
* ## Why running an install is not allowed either (#312, considered and rejected)
|
|
1156
|
+
*
|
|
1157
|
+
* The usual reason a hook cannot load is that nothing is installed yet (a fresh
|
|
1158
|
+
* container, or a merge that brought the wiring but not the package). The cure
|
|
1159
|
+
* is `npm ci`, which is exactly what the gate refuses. Letting a lockfile
|
|
1160
|
+
* install through was proposed and rejected, for four reasons:
|
|
1161
|
+
*
|
|
1162
|
+
* - Like `git merge --abort` and `vigiles compile` above, it would be allowed for
|
|
1163
|
+
* what it is meant to do, not for what it can do. An install runs the
|
|
1164
|
+
* lifecycle scripts of every package in the lockfile, which is more than the
|
|
1165
|
+
* `.git/hooks/*` scripts that got the git commands removed.
|
|
1166
|
+
* - "The gate is not running anyway" would equally justify allowing `ls`. It
|
|
1167
|
+
* does not single out `npm ci`.
|
|
1168
|
+
* - On a fresh container the runtime itself is missing, so none of this code
|
|
1169
|
+
* runs; the command's own `|| exit 2` decides.
|
|
1170
|
+
* - `npm ci` fails when the lockfile is behind `package.json`. Fixing that needs
|
|
1171
|
+
* plain `npm install`, which fetches versions the repo has not pinned.
|
|
1172
|
+
*
|
|
1173
|
+
* So the way out stays a file write. What is left over is described in
|
|
1174
|
+
* `docs/compiled-hooks.md` ("When a hook cannot load"). Removing the cause would
|
|
1175
|
+
* need hook files that live in the same git commit as their wiring; that is a
|
|
1176
|
+
* separate decision (#312).
|
|
1177
|
+
*
|
|
1112
1178
|
* ## What is accepted
|
|
1113
1179
|
*
|
|
1114
1180
|
* A write to the hook's own source, its stamp sidecar, or one of
|
|
@@ -12,6 +12,8 @@ exports.decideProgram = decideProgram;
|
|
|
12
12
|
exports.decisionExitCode = decisionExitCode;
|
|
13
13
|
exports.checkHookImports = checkHookImports;
|
|
14
14
|
exports.dispatchKind = dispatchKind;
|
|
15
|
+
exports.hookRuntimeMissingExit = hookRuntimeMissingExit;
|
|
16
|
+
exports.withRoleExit = withRoleExit;
|
|
15
17
|
exports.hookMode = hookMode;
|
|
16
18
|
exports.hookNeeds = hookNeeds;
|
|
17
19
|
exports.hookRouting = hookRouting;
|
|
@@ -686,6 +688,51 @@ function dispatchKind(hook) {
|
|
|
686
688
|
return hook.role === "gate" ? "file-gate" : hook.role;
|
|
687
689
|
return "bash-gate";
|
|
688
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* What the shell should do when a hook's command fails before the hook gives an
|
|
693
|
+
* answer: the runtime is not installed, or it starts but cannot load the hook.
|
|
694
|
+
* No hook code ran, so the hook cannot be asked. The answer has to be written
|
|
695
|
+
* into the command itself, and this function is the one place that decides it.
|
|
696
|
+
*
|
|
697
|
+
* There are two kinds of hook and they need different answers:
|
|
698
|
+
*
|
|
699
|
+
* - A gate (it can block a tool call) must exit 2. A gate that cannot run must
|
|
700
|
+
* not let calls through: a protection that silently stops working is worse
|
|
701
|
+
* than one that is known to be missing.
|
|
702
|
+
* - A nudge (it only adds a reminder: an inject or a react) must exit 0. If it
|
|
703
|
+
* blocked, one missing file could stop the whole session. This happened on
|
|
704
|
+
* 2026-08-10: merge-conflict markers in `package.json` stopped every hook from
|
|
705
|
+
* loading, the Bash gate then refused `git merge --abort` (the command that
|
|
706
|
+
* undoes the damage), and the session could not be repaired from inside.
|
|
707
|
+
*
|
|
708
|
+
* The kind is fixed by the type the author picked: a react hook has no `deny`,
|
|
709
|
+
* and an inject hook only returns text.
|
|
710
|
+
*
|
|
711
|
+
* Only this function decides. The runtime used to guess a second time, from the
|
|
712
|
+
* hook's file name ("inject" in the name meant nudge), and the two guesses
|
|
713
|
+
* disagreed (#312). Now the runtime always exits 2 when a hook cannot load, and
|
|
714
|
+
* the `|| exit N` ending (see {@link withRoleExit}) is the only thing that turns
|
|
715
|
+
* that 2 into a 0. It is shell, so a different runtime version cannot change it.
|
|
716
|
+
*
|
|
717
|
+
* This does not rescue a gate that cannot load: it still refuses every command,
|
|
718
|
+
* including the install that would fix it. It only stops a nudge doing the same.
|
|
719
|
+
*
|
|
720
|
+
* (Other tools disagree on this too: husky and lefthook skip, pre-commit fails.)
|
|
721
|
+
*/
|
|
722
|
+
function hookRuntimeMissingExit(kind) {
|
|
723
|
+
return kind === "inject" || kind === "react" ? 0 : 2;
|
|
724
|
+
}
|
|
725
|
+
/** A trailing `|| exit <number>`, the only ending this module writes. */
|
|
726
|
+
const ROLE_EXIT_SUFFIX = /\s*\|\|\s*exit\s+\d+\s*$/;
|
|
727
|
+
/**
|
|
728
|
+
* `command` with `|| exit 0` or `|| exit 2` (chosen by the kind of hook) as its
|
|
729
|
+
* last words. A `|| exit N` already there is replaced, not added to: the shell
|
|
730
|
+
* stops at the first `exit` it reaches, so a nudge wired `|| exit 2 || exit 0`
|
|
731
|
+
* would still block. Applying it twice gives the same result as applying it once.
|
|
732
|
+
*/
|
|
733
|
+
function withRoleExit(command, kind) {
|
|
734
|
+
return `${command.replace(ROLE_EXIT_SUFFIX, "")} || exit ${hookRuntimeMissingExit(kind)}`;
|
|
735
|
+
}
|
|
689
736
|
/** A gate's {@link HookMode} (`enforce` default); non-gate roles report `enforce` too. */
|
|
690
737
|
function hookMode(hook) {
|
|
691
738
|
return "mode" in hook && hook.mode ? hook.mode : "enforce";
|
|
@@ -942,7 +989,9 @@ function compileHookProgram(source, hook, opts = {}) {
|
|
|
942
989
|
.map((u) => `${u.name} ("${u.run}")`)
|
|
943
990
|
.join(", ")} — use dangerously(name, cmd) to acknowledge a side-effecting/undecidable command, or keep provide() only for a read-only one.`);
|
|
944
991
|
}
|
|
945
|
-
|
|
992
|
+
// The `|| exit N` ending is added here, not by the caller, so every caller gets
|
|
993
|
+
// the right one and none can forget it (#312).
|
|
994
|
+
const gateCommand = withRoleExit(opts.gateCommand ?? "npx vigiles hook-runtime run-program", dispatchKind(hook));
|
|
946
995
|
const matcher = styleMatcher(rawMatcher, opts.hookProtocol);
|
|
947
996
|
const entry = matcher === undefined
|
|
948
997
|
? { hooks: [{ type: "command", command: gateCommand }] }
|
|
@@ -1759,6 +1808,29 @@ function isStampRepairEvent(event, hookFile, repoRoot) {
|
|
|
1759
1808
|
* loads, the gate decides normally and `git merge --abort` is an ordinary allowed
|
|
1760
1809
|
* command — through the gate, not around it.
|
|
1761
1810
|
*
|
|
1811
|
+
* ## Why running an install is not allowed either (#312, considered and rejected)
|
|
1812
|
+
*
|
|
1813
|
+
* The usual reason a hook cannot load is that nothing is installed yet (a fresh
|
|
1814
|
+
* container, or a merge that brought the wiring but not the package). The cure
|
|
1815
|
+
* is `npm ci`, which is exactly what the gate refuses. Letting a lockfile
|
|
1816
|
+
* install through was proposed and rejected, for four reasons:
|
|
1817
|
+
*
|
|
1818
|
+
* - Like `git merge --abort` and `vigiles compile` above, it would be allowed for
|
|
1819
|
+
* what it is meant to do, not for what it can do. An install runs the
|
|
1820
|
+
* lifecycle scripts of every package in the lockfile, which is more than the
|
|
1821
|
+
* `.git/hooks/*` scripts that got the git commands removed.
|
|
1822
|
+
* - "The gate is not running anyway" would equally justify allowing `ls`. It
|
|
1823
|
+
* does not single out `npm ci`.
|
|
1824
|
+
* - On a fresh container the runtime itself is missing, so none of this code
|
|
1825
|
+
* runs; the command's own `|| exit 2` decides.
|
|
1826
|
+
* - `npm ci` fails when the lockfile is behind `package.json`. Fixing that needs
|
|
1827
|
+
* plain `npm install`, which fetches versions the repo has not pinned.
|
|
1828
|
+
*
|
|
1829
|
+
* So the way out stays a file write. What is left over is described in
|
|
1830
|
+
* `docs/compiled-hooks.md` ("When a hook cannot load"). Removing the cause would
|
|
1831
|
+
* need hook files that live in the same git commit as their wiring; that is a
|
|
1832
|
+
* separate decision (#312).
|
|
1833
|
+
*
|
|
1762
1834
|
* ## What is accepted
|
|
1763
1835
|
*
|
|
1764
1836
|
* A write to the hook's own source, its stamp sidecar, or one of
|
package/dist/coverage-probe.d.ts
CHANGED
|
@@ -106,6 +106,21 @@ import { type SurfaceProbe } from "./check-count.js";
|
|
|
106
106
|
* runner resolves refs against real discovered surfaces or drops them.
|
|
107
107
|
*/
|
|
108
108
|
export declare function commandRefs(command: string, env?: Record<string, string>): string[];
|
|
109
|
+
/**
|
|
110
|
+
* The files that a `hook-runtime run-program` command needs to exist: the hook
|
|
111
|
+
* file it names, and the runtime script when the command starts it by path
|
|
112
|
+
* (`node "$DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program …`).
|
|
113
|
+
*
|
|
114
|
+
* Narrower than {@link commandRefs}, which lists every program a command runs.
|
|
115
|
+
* This lists only what a compiled hook depends on, because the start-of-session
|
|
116
|
+
* check (`hook-check.ts`) reports these files as missing. `bash scripts/build.sh`
|
|
117
|
+
* is not one of them; reporting it missing in a fresh clone would be a false
|
|
118
|
+
* alarm that `npm install` does not fix.
|
|
119
|
+
*
|
|
120
|
+
* Like `commandRefs`, it only counts a command that really starts vigiles, so
|
|
121
|
+
* `echo vigiles hook-runtime run-program x.mjs` names nothing.
|
|
122
|
+
*/
|
|
123
|
+
export declare function runProgramFiles(command: string): string[];
|
|
109
124
|
/** The transcript shape this module reads — structural, so it imports no tier. */
|
|
110
125
|
export interface ProbeableTrace {
|
|
111
126
|
readonly toolCalls: readonly {
|
package/dist/coverage-probe.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.commandRefs = commandRefs;
|
|
4
|
+
exports.runProgramFiles = runProgramFiles;
|
|
4
5
|
exports.traceRefs = traceRefs;
|
|
5
6
|
exports.probeCommand = probeCommand;
|
|
6
7
|
exports.probeTrace = probeTrace;
|
|
@@ -586,6 +587,34 @@ function commandRefs(command, env) {
|
|
|
586
587
|
}
|
|
587
588
|
return [...out];
|
|
588
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* The files that a `hook-runtime run-program` command needs to exist: the hook
|
|
592
|
+
* file it names, and the runtime script when the command starts it by path
|
|
593
|
+
* (`node "$DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program …`).
|
|
594
|
+
*
|
|
595
|
+
* Narrower than {@link commandRefs}, which lists every program a command runs.
|
|
596
|
+
* This lists only what a compiled hook depends on, because the start-of-session
|
|
597
|
+
* check (`hook-check.ts`) reports these files as missing. `bash scripts/build.sh`
|
|
598
|
+
* is not one of them; reporting it missing in a fresh clone would be a false
|
|
599
|
+
* alarm that `npm install` does not fix.
|
|
600
|
+
*
|
|
601
|
+
* Like `commandRefs`, it only counts a command that really starts vigiles, so
|
|
602
|
+
* `echo vigiles hook-runtime run-program x.mjs` names nothing.
|
|
603
|
+
*/
|
|
604
|
+
function runProgramFiles(command) {
|
|
605
|
+
const out = new Set();
|
|
606
|
+
for (const argv of (0, bash_effects_js_1.leafArgvSource)(command)) {
|
|
607
|
+
const head = argv[0] ?? "";
|
|
608
|
+
const entry = SCRIPT_RE.test(head) ? head : interpretedScript(argv);
|
|
609
|
+
const hook = runProgramRef(argv, entry);
|
|
610
|
+
if (hook === undefined)
|
|
611
|
+
continue;
|
|
612
|
+
if (entry !== undefined)
|
|
613
|
+
out.add(entry);
|
|
614
|
+
out.add(hook);
|
|
615
|
+
}
|
|
616
|
+
return [...out];
|
|
617
|
+
}
|
|
589
618
|
/**
|
|
590
619
|
* The tools that DISPATCH a subagent. Both spellings, because the live CLI names
|
|
591
620
|
* it `Agent` and older docs name it `Task` — the same pair `parseSubagents`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installHookCheck = installHookCheck;
|
|
4
|
+
/**
|
|
5
|
+
* Writes the start-of-session check described in `src/hook-check.ts` and adds its
|
|
6
|
+
* entry to each harness's settings. `vigiles compile` calls this once, after it
|
|
7
|
+
* has merged the hooks.
|
|
8
|
+
*
|
|
9
|
+
* The list of files comes from the settings as they are now, not from the hooks
|
|
10
|
+
* this run compiled. So compiling one hook does not make the check forget the
|
|
11
|
+
* others, and compiling twice changes nothing. The entry is found again by the
|
|
12
|
+
* script's path, so a recompile replaces it in place and leaves any
|
|
13
|
+
* `SessionStart` hook the user wrote alone.
|
|
14
|
+
*/
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_path_1 = require("node:path");
|
|
17
|
+
const hook_check_js_1 = require("./hook-check.js");
|
|
18
|
+
const hook_install_js_1 = require("./hook-install.js");
|
|
19
|
+
function isShellAdapter(adapter) {
|
|
20
|
+
return adapter.shellHooks;
|
|
21
|
+
}
|
|
22
|
+
/** One harness's settings as a plain object, or `undefined` if the file does not exist yet. */
|
|
23
|
+
function readSettings(adapter, cwd) {
|
|
24
|
+
const file = (0, node_path_1.resolve)(cwd, adapter.layout.settingsPath);
|
|
25
|
+
return (0, node_fs_1.existsSync)(file)
|
|
26
|
+
? adapter.layout.settings.parse((0, node_fs_1.readFileSync)(file, "utf-8"))
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Add the check's `SessionStart` entry to one harness's settings, or replace it where it already is. */
|
|
30
|
+
function registerCheck(adapter, existing, cwd) {
|
|
31
|
+
const ref = (0, hook_install_js_1.hookGateRef)(hook_check_js_1.HOOK_CHECK_REF, adapter.layout.projectRootTokens);
|
|
32
|
+
const merged = adapter.hookProtocol.mergeRegistrations(existing, {
|
|
33
|
+
SessionStart: [
|
|
34
|
+
{ hooks: [{ type: "command", command: `sh ${ref} || exit 0` }] },
|
|
35
|
+
],
|
|
36
|
+
}, hook_check_js_1.HOOK_CHECK_REF);
|
|
37
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(cwd, adapter.layout.settingsPath), adapter.layout.settings.render(merged));
|
|
38
|
+
}
|
|
39
|
+
function installHookCheck(adapters, cwd = process.cwd()) {
|
|
40
|
+
const found = adapters.filter(isShellAdapter).flatMap((adapter) => {
|
|
41
|
+
const settings = readSettings(adapter, cwd);
|
|
42
|
+
return settings === undefined ? [] : [{ adapter, settings }];
|
|
43
|
+
});
|
|
44
|
+
const { paths, unsafe } = (0, hook_check_js_1.checkPaths)(found.flatMap((f) => (0, hook_check_js_1.commandsIn)(f.settings.hooks)));
|
|
45
|
+
for (const path of unsafe) {
|
|
46
|
+
console.warn(`⚠ hook-check: ${path} has characters the check cannot quote safely, so it is NOT covered.`);
|
|
47
|
+
}
|
|
48
|
+
if (paths.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const script = (0, node_path_1.resolve)(cwd, hook_check_js_1.HOOK_CHECK_REF);
|
|
51
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(script), { recursive: true });
|
|
52
|
+
(0, node_fs_1.writeFileSync)(script, (0, hook_check_js_1.renderHookCheck)(paths));
|
|
53
|
+
for (const { adapter, settings } of found)
|
|
54
|
+
registerCheck(adapter, settings, cwd);
|
|
55
|
+
console.log(`✓ ${hook_check_js_1.HOOK_CHECK_REF} → SessionStart notice for ${paths.length} wired hook file(s)`);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=hook-check-install.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Where the generated script is written. It is committed, next to the hook sources. */
|
|
2
|
+
export declare const HOOK_CHECK_REF = ".vigiles/hook-check.sh";
|
|
3
|
+
/**
|
|
4
|
+
* Every `command` string in a parsed hooks block. It looks for the key `command`
|
|
5
|
+
* instead of following one layout, because the layout depends on the harness
|
|
6
|
+
* (Claude Code nests commands under a matcher block, Codex's TOML is flat).
|
|
7
|
+
*/
|
|
8
|
+
export declare function commandsIn(node: unknown): string[];
|
|
9
|
+
export interface CheckPaths {
|
|
10
|
+
/** Paths relative to the project, sorted, no duplicates: what the script tests. */
|
|
11
|
+
readonly paths: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Paths the check found but cannot safely write into the script. `compile`
|
|
14
|
+
* warns about each one, because a path left out is a hook the check does not
|
|
15
|
+
* cover.
|
|
16
|
+
*/
|
|
17
|
+
readonly unsafe: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The files that the compiled-hook commands in `commands` need, as paths relative
|
|
21
|
+
* to the project. Two kinds of path are left out on purpose: absolute paths
|
|
22
|
+
* (another machine's checkout will not have them) and paths that use any variable
|
|
23
|
+
* except the project root, such as `$HOME` or a plugin root (we cannot resolve
|
|
24
|
+
* them here).
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkPaths(commands: readonly string[]): CheckPaths;
|
|
27
|
+
/**
|
|
28
|
+
* The text of the script. The same paths always give the same text, so a
|
|
29
|
+
* recompile that finds the same hooks writes the same bytes.
|
|
30
|
+
*
|
|
31
|
+
* The script prints JSON with an `additionalContext` field instead of plain text.
|
|
32
|
+
* That form is confirmed for the start of a session on both Claude Code and
|
|
33
|
+
* Codex; plain text is only confirmed on Claude Code.
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderHookCheck(paths: readonly string[]): string;
|
|
36
|
+
//# sourceMappingURL=hook-check.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOOK_CHECK_REF = void 0;
|
|
4
|
+
exports.commandsIn = commandsIn;
|
|
5
|
+
exports.checkPaths = checkPaths;
|
|
6
|
+
exports.renderHookCheck = renderHookCheck;
|
|
7
|
+
/**
|
|
8
|
+
* A check that runs when a session starts and says "run npm install" if files
|
|
9
|
+
* that the repo's hooks need are missing.
|
|
10
|
+
*
|
|
11
|
+
* Why it exists (#312). A compiled hook lives in two places: the wiring in
|
|
12
|
+
* `.claude/settings.json` (stored in git) and the files that wiring runs (in
|
|
13
|
+
* `node_modules`, stored by npm). A fresh container, a merge that brings new
|
|
14
|
+
* wiring, or a dependency update can change one without the other. Today the
|
|
15
|
+
* first sign is a hook refusing the first command. This check notices one step
|
|
16
|
+
* earlier and names the fix.
|
|
17
|
+
*
|
|
18
|
+
* What it does not do: fix anything. If a hook that can block commands has
|
|
19
|
+
* missing files, it still refuses every shell command, including the install.
|
|
20
|
+
* The check only says so before the first command is tried, so a person can run
|
|
21
|
+
* the install from a terminal outside the session. Removing the cause would need
|
|
22
|
+
* hook files stored in git next to their wiring; that is a separate decision
|
|
23
|
+
* (#312).
|
|
24
|
+
*
|
|
25
|
+
* Three rules shape the code:
|
|
26
|
+
*
|
|
27
|
+
* - It must work when nothing is installed. So the script is plain POSIX `sh`
|
|
28
|
+
* using only built-in commands (no node, no vigiles, no grep), and it is
|
|
29
|
+
* committed, so it arrives together with the wiring.
|
|
30
|
+
* - It only prints and always exits 0. We have not verified what exit code 2
|
|
31
|
+
* does at session start, and a check that could fail would be one more way to
|
|
32
|
+
* break the session it is meant to help. It is wired `|| exit 0`, like any
|
|
33
|
+
* reminder.
|
|
34
|
+
* - It lists only what a compiled hook needs: the hook files named by
|
|
35
|
+
* `hook-runtime run-program` commands in the settings, plus the local runtime
|
|
36
|
+
* when it is started by path. Reporting any other script (say
|
|
37
|
+
* `scripts/build.sh`) as missing in a fresh clone would be a false alarm,
|
|
38
|
+
* because `npm install` does not create it.
|
|
39
|
+
*
|
|
40
|
+
* The list is read from the settings after `compile` has merged into them. So it
|
|
41
|
+
* also covers hooks that another tool wrote there, and it does not shrink when
|
|
42
|
+
* `compile` is run on just one hook. A hook added after the last `compile` is
|
|
43
|
+
* not covered until the next one.
|
|
44
|
+
*
|
|
45
|
+
* Limits: it checks that a file exists, not that it loads. A package that is
|
|
46
|
+
* present but no longer exports what a hook imports passes this check and still
|
|
47
|
+
* breaks. A path is recognised by the same pattern the coverage code uses, which
|
|
48
|
+
* has no room for spaces or quotes, so a hook under such a path is not covered,
|
|
49
|
+
* and the check cannot say so.
|
|
50
|
+
*/
|
|
51
|
+
const coverage_probe_js_1 = require("./coverage-probe.js");
|
|
52
|
+
/** Where the generated script is written. It is committed, next to the hook sources. */
|
|
53
|
+
exports.HOOK_CHECK_REF = ".vigiles/hook-check.sh";
|
|
54
|
+
/**
|
|
55
|
+
* Every `command` string in a parsed hooks block. It looks for the key `command`
|
|
56
|
+
* instead of following one layout, because the layout depends on the harness
|
|
57
|
+
* (Claude Code nests commands under a matcher block, Codex's TOML is flat).
|
|
58
|
+
*/
|
|
59
|
+
function commandsIn(node) {
|
|
60
|
+
if (Array.isArray(node))
|
|
61
|
+
return node.flatMap(commandsIn);
|
|
62
|
+
if (typeof node !== "object" || node === null)
|
|
63
|
+
return [];
|
|
64
|
+
return Object.entries(node).flatMap(([key, value]) => key === "command" && typeof value === "string"
|
|
65
|
+
? [value]
|
|
66
|
+
: commandsIn(value));
|
|
67
|
+
}
|
|
68
|
+
/** How a command may spell the project root. It is removed to leave a relative path. */
|
|
69
|
+
const PROJECT_ROOT_PREFIX = /^\$(?:\{CLAUDE_PROJECT_DIR\}|CLAUDE_PROJECT_DIR)\//;
|
|
70
|
+
/** The characters a path may contain. It is written into a shell string and a JSON string. */
|
|
71
|
+
const SAFE_PATH = /^[A-Za-z0-9._@+/-]+$/;
|
|
72
|
+
/**
|
|
73
|
+
* The files that the compiled-hook commands in `commands` need, as paths relative
|
|
74
|
+
* to the project. Two kinds of path are left out on purpose: absolute paths
|
|
75
|
+
* (another machine's checkout will not have them) and paths that use any variable
|
|
76
|
+
* except the project root, such as `$HOME` or a plugin root (we cannot resolve
|
|
77
|
+
* them here).
|
|
78
|
+
*/
|
|
79
|
+
function checkPaths(commands) {
|
|
80
|
+
const relative = commands
|
|
81
|
+
.flatMap(coverage_probe_js_1.runProgramFiles)
|
|
82
|
+
.map((file) => file.replace(PROJECT_ROOT_PREFIX, ""))
|
|
83
|
+
.filter((file) => !file.startsWith("/") && !file.includes("$"));
|
|
84
|
+
const unique = [...new Set(relative)].sort();
|
|
85
|
+
return {
|
|
86
|
+
paths: unique.filter((p) => SAFE_PATH.test(p)),
|
|
87
|
+
unsafe: unique.filter((p) => !SAFE_PATH.test(p)),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The text of the script. The same paths always give the same text, so a
|
|
92
|
+
* recompile that finds the same hooks writes the same bytes.
|
|
93
|
+
*
|
|
94
|
+
* The script prints JSON with an `additionalContext` field instead of plain text.
|
|
95
|
+
* That form is confirmed for the start of a session on both Claude Code and
|
|
96
|
+
* Codex; plain text is only confirmed on Claude Code.
|
|
97
|
+
*/
|
|
98
|
+
function renderHookCheck(paths) {
|
|
99
|
+
const list = paths.map((p) => ` '${p}'`).join(" \\\n");
|
|
100
|
+
return `#!/bin/sh
|
|
101
|
+
# Generated by \`vigiles compile\`. Do not edit; the next compile rewrites it.
|
|
102
|
+
#
|
|
103
|
+
# Start-of-session notice (vigiles #312). If a file that a compiled hook runs is
|
|
104
|
+
# not installed, it says so in the first turn. It uses only plain sh and shell
|
|
105
|
+
# built-ins (no node, no node_modules, no vigiles), because "not installed" is
|
|
106
|
+
# exactly the situation it reports. It only prints and always exits 0.
|
|
107
|
+
#
|
|
108
|
+
# It does not fix anything. A hook that can block commands, with its files
|
|
109
|
+
# missing, refuses every shell command including the install, so the install has
|
|
110
|
+
# to be run from a terminal outside the session.
|
|
111
|
+
root="\${CLAUDE_PROJECT_DIR:-.}"
|
|
112
|
+
missing=""
|
|
113
|
+
for p in \\
|
|
114
|
+
${list}
|
|
115
|
+
do
|
|
116
|
+
[ -e "$root/$p" ] || missing="$missing $p"
|
|
117
|
+
done
|
|
118
|
+
if [ -n "$missing" ]; then
|
|
119
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\\n' \\
|
|
120
|
+
"vigiles: hook files that this repo runs are not installed:$missing. Run npm ci (or npm install) from a terminal outside this session before anything else. A blocking hook whose files are missing refuses every shell command in a session, the install included; a reminder-only hook that cannot load is skipped. See docs/compiled-hooks.md, When a hook cannot load."
|
|
121
|
+
fi
|
|
122
|
+
exit 0
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=hook-check.js.map
|
package/dist/hook-install.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DispatchKind } from "./core/hook-program.js";
|
|
2
1
|
/** The agnostic, committed home for hook SOURCE — one dir, cross-adapter. */
|
|
3
2
|
export declare const HOOKS_DIR = ".vigiles/hooks";
|
|
4
3
|
/** The committed home for registered context-provider SOURCE (v2). */
|
|
@@ -73,41 +72,6 @@ export declare function normalizeHookRef(hookPath: string, cwd?: string): string
|
|
|
73
72
|
* it can be given — see {@link hookGateRef} for the same fallback.
|
|
74
73
|
*/
|
|
75
74
|
export declare function hookRuntimeRef(projectRootTokens: readonly string[] | undefined): string;
|
|
76
|
-
/**
|
|
77
|
-
* What the shell must do when the runtime above CANNOT START — a missing
|
|
78
|
-
* `node_modules/vigiles`, an unreadable file, an interpreter that dies before a
|
|
79
|
-
* single line of ours runs. No code of ours executes in that case, so the policy
|
|
80
|
-
* has to be expressed in the emitted command or not at all.
|
|
81
|
-
*
|
|
82
|
-
* 🔴 THIS IS NOT A NEW POLICY. `runHookProgramCommand`'s load-failure branch has
|
|
83
|
-
* decided it since 2026-08: *"an inject's purpose is to ADD context, not to
|
|
84
|
-
* ENFORCE a decision … Gates (file, bash, prompt, stop) remain conservative and
|
|
85
|
-
* fail closed."* That branch only reaches failures that happen AFTER the runtime
|
|
86
|
-
* starts. This carries the same rule one layer out, to the failures that happen
|
|
87
|
-
* before it.
|
|
88
|
-
*
|
|
89
|
-
* WHY THE SPLIT, RATHER THAN ONE ANSWER FOR EVERYTHING — the two failures are
|
|
90
|
-
* not comparable:
|
|
91
|
-
*
|
|
92
|
-
* A GATE THAT SILENTLY PASSES IS WORSE THAN NO GATE. Its whole value is the
|
|
93
|
-
* refusal, and a harness that reports protection it is not providing is the
|
|
94
|
-
* one state worse than admitting it has none. So a gate whose runtime is
|
|
95
|
-
* missing exits 2: loud, blocking, and the cause is on stderr.
|
|
96
|
-
*
|
|
97
|
-
* A NUDGE THAT BLOCKS COSTS THE WHOLE REPOSITORY. Measured here 2026-08-10:
|
|
98
|
-
* merge-conflict markers in `package.json` stopped every hook loading, the
|
|
99
|
-
* Bash gate then refused `git merge --abort` — the one command that undoes the
|
|
100
|
-
* cause — and the session could not be repaired from inside. A reminder is
|
|
101
|
-
* never worth that, so a nudge exits 0 and says nothing it cannot say.
|
|
102
|
-
*
|
|
103
|
-
* The role is not a flag someone can flip: `Reaction` has no `deny` and an
|
|
104
|
-
* inject returns context, so "nudge" is a fact about the TYPE the author chose.
|
|
105
|
-
*
|
|
106
|
-
* (Industry does not agree on one answer either — husky and lefthook skip,
|
|
107
|
-
* pre-commit fails. Which is itself the argument for deciding by role instead
|
|
108
|
-
* of picking one and imposing it on both.)
|
|
109
|
-
*/
|
|
110
|
-
export declare function hookRuntimeMissingExit(kind: DispatchKind): 0 | 2;
|
|
111
75
|
export declare function hookGateRef(ref: string, projectRootTokens: readonly string[] | undefined): string;
|
|
112
76
|
/**
|
|
113
77
|
* Idempotently merge a compiled hook's block into an existing `settings.json`
|
package/dist/hook-install.js
CHANGED
|
@@ -5,7 +5,6 @@ exports.discoverHookFiles = discoverHookFiles;
|
|
|
5
5
|
exports.discoverProviderFiles = discoverProviderFiles;
|
|
6
6
|
exports.normalizeHookRef = normalizeHookRef;
|
|
7
7
|
exports.hookRuntimeRef = hookRuntimeRef;
|
|
8
|
-
exports.hookRuntimeMissingExit = hookRuntimeMissingExit;
|
|
9
8
|
exports.hookGateRef = hookGateRef;
|
|
10
9
|
exports.mergeHooksJson = mergeHooksJson;
|
|
11
10
|
exports.mergeHooksToml = mergeHooksToml;
|
|
@@ -122,43 +121,6 @@ function hookRuntimeRef(projectRootTokens) {
|
|
|
122
121
|
const token = projectRootTokens?.[0];
|
|
123
122
|
return token === undefined ? `node ${rel}` : `node "${token}/${rel}"`;
|
|
124
123
|
}
|
|
125
|
-
/**
|
|
126
|
-
* What the shell must do when the runtime above CANNOT START — a missing
|
|
127
|
-
* `node_modules/vigiles`, an unreadable file, an interpreter that dies before a
|
|
128
|
-
* single line of ours runs. No code of ours executes in that case, so the policy
|
|
129
|
-
* has to be expressed in the emitted command or not at all.
|
|
130
|
-
*
|
|
131
|
-
* 🔴 THIS IS NOT A NEW POLICY. `runHookProgramCommand`'s load-failure branch has
|
|
132
|
-
* decided it since 2026-08: *"an inject's purpose is to ADD context, not to
|
|
133
|
-
* ENFORCE a decision … Gates (file, bash, prompt, stop) remain conservative and
|
|
134
|
-
* fail closed."* That branch only reaches failures that happen AFTER the runtime
|
|
135
|
-
* starts. This carries the same rule one layer out, to the failures that happen
|
|
136
|
-
* before it.
|
|
137
|
-
*
|
|
138
|
-
* WHY THE SPLIT, RATHER THAN ONE ANSWER FOR EVERYTHING — the two failures are
|
|
139
|
-
* not comparable:
|
|
140
|
-
*
|
|
141
|
-
* A GATE THAT SILENTLY PASSES IS WORSE THAN NO GATE. Its whole value is the
|
|
142
|
-
* refusal, and a harness that reports protection it is not providing is the
|
|
143
|
-
* one state worse than admitting it has none. So a gate whose runtime is
|
|
144
|
-
* missing exits 2: loud, blocking, and the cause is on stderr.
|
|
145
|
-
*
|
|
146
|
-
* A NUDGE THAT BLOCKS COSTS THE WHOLE REPOSITORY. Measured here 2026-08-10:
|
|
147
|
-
* merge-conflict markers in `package.json` stopped every hook loading, the
|
|
148
|
-
* Bash gate then refused `git merge --abort` — the one command that undoes the
|
|
149
|
-
* cause — and the session could not be repaired from inside. A reminder is
|
|
150
|
-
* never worth that, so a nudge exits 0 and says nothing it cannot say.
|
|
151
|
-
*
|
|
152
|
-
* The role is not a flag someone can flip: `Reaction` has no `deny` and an
|
|
153
|
-
* inject returns context, so "nudge" is a fact about the TYPE the author chose.
|
|
154
|
-
*
|
|
155
|
-
* (Industry does not agree on one answer either — husky and lefthook skip,
|
|
156
|
-
* pre-commit fails. Which is itself the argument for deciding by role instead
|
|
157
|
-
* of picking one and imposing it on both.)
|
|
158
|
-
*/
|
|
159
|
-
function hookRuntimeMissingExit(kind) {
|
|
160
|
-
return kind === "inject" || kind === "react" ? 0 : 2;
|
|
161
|
-
}
|
|
162
124
|
function hookGateRef(ref, projectRootTokens) {
|
|
163
125
|
const token = projectRootTokens?.[0];
|
|
164
126
|
return token === undefined ? ref : `"${token}/${ref}"`;
|
package/dist/hook-runtime.js
CHANGED
|
@@ -432,12 +432,16 @@ async function runHookProgramCommand(file) {
|
|
|
432
432
|
// the author are different from a `deny`'s: name the real cause, and leave a
|
|
433
433
|
// way back.
|
|
434
434
|
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
435
|
+
// A hook that cannot be loaded always exits 2 here, whatever kind of hook it
|
|
436
|
+
// is. We cannot tell: the hook did not load, so there is nothing to ask
|
|
437
|
+
// whether it is a gate (can block) or a nudge (reminder only). This code used
|
|
438
|
+
// to guess from the file name (`file.includes("inject")`), and the guess
|
|
439
|
+
// disagreed with the `|| exit N` ending that `vigiles compile` writes on the
|
|
440
|
+
// command: a nudge without "inject" in its name blocked the session, and a
|
|
441
|
+
// gate with it let every command through (#312).
|
|
442
|
+
// Now only that ending decides. A nudge is wired `|| exit 0`, so the shell
|
|
443
|
+
// turns our 2 into a 0 and it degrades; a gate is wired `|| exit 2` and
|
|
444
|
+
// blocks. A command with no such ending is treated as a gate, the safe default.
|
|
441
445
|
//
|
|
442
446
|
// Escapes, both announced loudly on stderr:
|
|
443
447
|
// - the stale-stamp one (an edit to the hook itself / `vigiles compile`),
|
|
@@ -476,11 +480,13 @@ async function runHookProgramCommand(file) {
|
|
|
476
480
|
announceRepairEscape(file, cause);
|
|
477
481
|
return;
|
|
478
482
|
}
|
|
479
|
-
// Log the error, but for inject hooks, degrade gracefully (exit 0).
|
|
480
483
|
const errorMsg = `vigiles: hook ${file} ${cause}.\n` +
|
|
481
484
|
`vigiles: this is the state of the HARNESS, not a decision about your ` +
|
|
482
485
|
`command — the gate never ran. Blocking anyway (a gate that cannot run ` +
|
|
483
486
|
`must not pass traffic).\n` +
|
|
487
|
+
`vigiles: if this hook is a NUDGE (an inject or a react), it must not ` +
|
|
488
|
+
`block: its wiring should end in \`|| exit 0\`, which \`vigiles compile\` ` +
|
|
489
|
+
`writes. A hook wired by hand with no such ending is treated as a gate.\n` +
|
|
484
490
|
`vigiles: the way out is a FILE WRITE, not a command — under a tool that ` +
|
|
485
491
|
`WRITES (Write/Edit/MultiEdit); a Read of the same path repairs nothing ` +
|
|
486
492
|
`and is refused. Fix whichever of ` +
|
|
@@ -493,24 +499,11 @@ async function runHookProgramCommand(file) {
|
|
|
493
499
|
`repair this failure.\n` +
|
|
494
500
|
`vigiles: no command is allowed, deliberately. \`git merge --abort\` and ` +
|
|
495
501
|
`\`git checkout\` RUN \`.git/hooks/*\` (measured: reference-transaction, ` +
|
|
496
|
-
`post-checkout),
|
|
497
|
-
`resolver that just failed
|
|
502
|
+
`post-checkout), \`vigiles compile\` loads the hook through the same ` +
|
|
503
|
+
`resolver that just failed, and an install runs the lifecycle scripts of ` +
|
|
504
|
+
`every package in the lockfile.`;
|
|
498
505
|
console.error(errorMsg);
|
|
499
|
-
|
|
500
|
-
// a more robust approach would parse the stamp or metadata, but that requires
|
|
501
|
-
// the hook to load. Inject hooks typically have "inject" in the name; fall
|
|
502
|
-
// back to blocking (exit 2) for safety on gates.
|
|
503
|
-
const isLikelyInject = file.includes("inject");
|
|
504
|
-
if (isLikelyInject) {
|
|
505
|
-
// Inject hook: degrade gracefully. Log the error but don't wedge the session.
|
|
506
|
-
console.error(`vigiles: ${file} is an inject hook; degrading gracefully (no context injected).`);
|
|
507
|
-
process.exit(0);
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
// Gate hook: fail closed.
|
|
511
|
-
process.exit(2);
|
|
512
|
-
}
|
|
513
|
-
return;
|
|
506
|
+
process.exit(2);
|
|
514
507
|
}
|
|
515
508
|
verifyStampOrRefuse(file, event);
|
|
516
509
|
switch ((0, hook_program_js_1.dispatchKind)(program)) {
|
package/dist/local-files.js
CHANGED
|
@@ -100,6 +100,7 @@ exports.LOCAL_FILES = [
|
|
|
100
100
|
*/
|
|
101
101
|
exports.COMMITTED_PATHS = [
|
|
102
102
|
"hooks", // hook sources + their stamps (`hook-install.ts`)
|
|
103
|
+
"hook-check.sh", // the start-of-session check, generated by compile (`hook-check.ts`)
|
|
103
104
|
"providers", // registered hook-context providers
|
|
104
105
|
"eval-locks", // committed eval staleness stamps (`eval-lock.ts`)
|
|
105
106
|
"eval-baseline.json", // the committed regression baseline (`eval-baseline.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "32.1.
|
|
3
|
+
"version": "32.1.1",
|
|
4
4
|
"description": "Audit, test and measure the harness your AI agent runs on — grade your CLAUDE.md / AGENTS.md, skills, subagents and hooks, run them against a scripted model, and measure whether they actually fire.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|