vigiles 15.0.3 → 15.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.
@@ -30,6 +30,22 @@ export declare function computePerFileHashes(inputFiles: string[], basePath: str
30
30
  * Recurses so that nested targets (e.g. `.github/copilot-instructions.md`,
31
31
  * which lives at `.vigiles/.github/copilot-instructions.md.inputs.json`)
32
32
  * are not silently skipped.
33
+ *
34
+ * 🔴 A DIRECTORY SYMLINK IS NOT DESCENDED INTO — this walk classified entries
35
+ * with a bare `statSync().isDirectory()`, which FOLLOWS a link, so a link inside
36
+ * `.vigiles/` pointing at a directory OUTSIDE it was walked (manifests from a
37
+ * foreign tree entering the audit under reconstructed target names), and a cycle
38
+ * — `.vigiles/loop -> .vigiles`, or a link to any ancestor — re-walked the same
39
+ * tree once per lap. Measured 2026-08-13 by reverting this call: ONE manifest was
40
+ * reported FORTY-ONE times, as `Real.md`, `loop/Real.md`, `loop/loop/Real.md` …
41
+ * until the kernel's link limit stopped it. Unlike the loader's walk it did not
42
+ * throw, and that is not a mitigation: the `catch` below swallowed the `ELOOP` and
43
+ * turned a crash into a silently multiplied audit. Third instance of the class in
44
+ * this repo; the decision is {@link entryOf} in `fs-walk.ts`, which carries the
45
+ * full rationale: a symlinked FILE is still read (it cannot recurse, and a
46
+ * manifest reached through one is a real manifest), termination is by
47
+ * construction rather than a visited-set, and an unreadable entry is `"skip"`
48
+ * rather than a throw — which is exactly what the `try`/`continue` here did.
33
49
  */
34
50
  export declare function iterateSidecars(basePath: string, fn: (target: string, manifest: SidecarManifest) => void): void;
35
51
  //# sourceMappingURL=sidecar.d.ts.map
@@ -18,6 +18,7 @@ exports.computePerFileHashes = computePerFileHashes;
18
18
  exports.iterateSidecars = iterateSidecars;
19
19
  const node_fs_1 = require("node:fs");
20
20
  const node_path_1 = require("node:path");
21
+ const fs_walk_js_1 = require("../fs-walk.js");
21
22
  const hash_js_1 = require("./hash.js");
22
23
  const SIDECAR_ROOT = ".vigiles";
23
24
  const MANIFEST_SUFFIX = ".inputs.json";
@@ -60,6 +61,22 @@ function computePerFileHashes(inputFiles, basePath) {
60
61
  * Recurses so that nested targets (e.g. `.github/copilot-instructions.md`,
61
62
  * which lives at `.vigiles/.github/copilot-instructions.md.inputs.json`)
62
63
  * are not silently skipped.
64
+ *
65
+ * 🔴 A DIRECTORY SYMLINK IS NOT DESCENDED INTO — this walk classified entries
66
+ * with a bare `statSync().isDirectory()`, which FOLLOWS a link, so a link inside
67
+ * `.vigiles/` pointing at a directory OUTSIDE it was walked (manifests from a
68
+ * foreign tree entering the audit under reconstructed target names), and a cycle
69
+ * — `.vigiles/loop -> .vigiles`, or a link to any ancestor — re-walked the same
70
+ * tree once per lap. Measured 2026-08-13 by reverting this call: ONE manifest was
71
+ * reported FORTY-ONE times, as `Real.md`, `loop/Real.md`, `loop/loop/Real.md` …
72
+ * until the kernel's link limit stopped it. Unlike the loader's walk it did not
73
+ * throw, and that is not a mitigation: the `catch` below swallowed the `ELOOP` and
74
+ * turned a crash into a silently multiplied audit. Third instance of the class in
75
+ * this repo; the decision is {@link entryOf} in `fs-walk.ts`, which carries the
76
+ * full rationale: a symlinked FILE is still read (it cannot recurse, and a
77
+ * manifest reached through one is a real manifest), termination is by
78
+ * construction rather than a visited-set, and an unreadable entry is `"skip"`
79
+ * rather than a throw — which is exactly what the `try`/`continue` here did.
63
80
  */
64
81
  function iterateSidecars(basePath, fn) {
65
82
  const root = (0, node_path_1.resolve)(basePath, SIDECAR_ROOT);
@@ -77,17 +94,15 @@ function walk(dir, root, basePath, fn) {
77
94
  }
78
95
  for (const entry of entries) {
79
96
  const fullPath = (0, node_path_1.resolve)(dir, entry);
80
- let isDir;
81
- try {
82
- isDir = (0, node_fs_1.statSync)(fullPath).isDirectory();
83
- }
84
- catch {
85
- continue;
86
- }
87
- if (isDir) {
97
+ const { kind } = (0, fs_walk_js_1.entryOf)(fullPath);
98
+ if (kind === "dir") {
88
99
  walk(fullPath, root, basePath, fn);
89
100
  continue;
90
101
  }
102
+ // "skip" covers what the old bare `statSync` handled by throwing (a dangling
103
+ // link, an unreadable entry) AND the symlinked directory it silently followed.
104
+ if (kind === "skip")
105
+ continue;
91
106
  if (!entry.endsWith(MANIFEST_SUFFIX))
92
107
  continue;
93
108
  // Reconstruct the target name from the path relative to .vigiles/
package/dist/hook.d.ts CHANGED
@@ -45,9 +45,11 @@
45
45
  * tool calls. A gate is a strong default, never an unbypassable wall. See
46
46
  * `docs/compiled-hooks.md`.
47
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";
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, injectionOf, outcomeWrites, matchesTool, invalidToolPatterns, compileHookProgram, checkHookImports, stampHook, verifyHookStamp, HookCompileError, } from "./core/hook-program.js";
49
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
50
  export { provide, dangerously, defineProvider, provider, } from "./core/hook-providers.js";
51
+ export { state, record, stateFact, isValidStateKey, isStateNeed, isStateWrite, admissibleWrites, durationSeconds, HookStateError, } from "./core/hook-state.js";
52
+ export type { StateFact, StateEntry, StateNeed, StateWrite, Duration, } from "./core/hook-state.js";
51
53
  export type { ProviderName, ProviderResults, HookCtx, NeedSpec, InlineProvider, RegisteredProvider, RegisteredRef, ProviderRegistry, } from "./core/hook-providers.js";
52
54
  export { leafCommandsNormalized } from "./core/bash-effects.js";
53
55
  export type { NormalizedLeaf, LeafRedirect } from "./core/bash-effects.js";
package/dist/hook.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.leafCommandsNormalized = 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;
3
+ exports.isStateWrite = exports.isStateNeed = exports.isValidStateKey = exports.stateFact = exports.record = exports.state = exports.provider = exports.defineProvider = exports.dangerously = exports.provide = exports.HookCompileError = exports.verifyHookStamp = exports.stampHook = exports.checkHookImports = exports.compileHookProgram = exports.invalidToolPatterns = exports.matchesTool = exports.outcomeWrites = exports.injectionOf = 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
+ exports.leafCommandsNormalized = exports.HookStateError = exports.durationSeconds = exports.admissibleWrites = void 0;
4
5
  /**
5
6
  * `vigiles/hook` — the **closed vocabulary** for authoring a compiled hook.
6
7
  *
@@ -84,6 +85,10 @@ Object.defineProperty(exports, "decisionExitCode", { enumerable: true, get: func
84
85
  Object.defineProperty(exports, "dispatchKind", { enumerable: true, get: function () { return hook_program_js_1.dispatchKind; } });
85
86
  Object.defineProperty(exports, "hookRouting", { enumerable: true, get: function () { return hook_program_js_1.hookRouting; } });
86
87
  Object.defineProperty(exports, "hookNeeds", { enumerable: true, get: function () { return hook_program_js_1.hookNeeds; } });
88
+ Object.defineProperty(exports, "injectionOf", { enumerable: true, get: function () { return hook_program_js_1.injectionOf; } });
89
+ Object.defineProperty(exports, "outcomeWrites", { enumerable: true, get: function () { return hook_program_js_1.outcomeWrites; } });
90
+ Object.defineProperty(exports, "matchesTool", { enumerable: true, get: function () { return hook_program_js_1.matchesTool; } });
91
+ Object.defineProperty(exports, "invalidToolPatterns", { enumerable: true, get: function () { return hook_program_js_1.invalidToolPatterns; } });
87
92
  // compile + integrity
88
93
  Object.defineProperty(exports, "compileHookProgram", { enumerable: true, get: function () { return hook_program_js_1.compileHookProgram; } });
89
94
  Object.defineProperty(exports, "checkHookImports", { enumerable: true, get: function () { return hook_program_js_1.checkHookImports; } });
@@ -95,6 +100,18 @@ Object.defineProperty(exports, "provide", { enumerable: true, get: function () {
95
100
  Object.defineProperty(exports, "dangerously", { enumerable: true, get: function () { return hook_providers_js_1.dangerously; } });
96
101
  Object.defineProperty(exports, "defineProvider", { enumerable: true, get: function () { return hook_providers_js_1.defineProvider; } });
97
102
  Object.defineProperty(exports, "provider", { enumerable: true, get: function () { return hook_providers_js_1.provider; } });
103
+ // Runtime-owned named state: `state(k)` in `needs` reads a fact, `record(k)` in a
104
+ // return value declares one. The whole design note lives in `core/hook-state.ts`.
105
+ var hook_state_js_1 = require("./core/hook-state.js");
106
+ Object.defineProperty(exports, "state", { enumerable: true, get: function () { return hook_state_js_1.state; } });
107
+ Object.defineProperty(exports, "record", { enumerable: true, get: function () { return hook_state_js_1.record; } });
108
+ Object.defineProperty(exports, "stateFact", { enumerable: true, get: function () { return hook_state_js_1.stateFact; } });
109
+ Object.defineProperty(exports, "isValidStateKey", { enumerable: true, get: function () { return hook_state_js_1.isValidStateKey; } });
110
+ Object.defineProperty(exports, "isStateNeed", { enumerable: true, get: function () { return hook_state_js_1.isStateNeed; } });
111
+ Object.defineProperty(exports, "isStateWrite", { enumerable: true, get: function () { return hook_state_js_1.isStateWrite; } });
112
+ Object.defineProperty(exports, "admissibleWrites", { enumerable: true, get: function () { return hook_state_js_1.admissibleWrites; } });
113
+ Object.defineProperty(exports, "durationSeconds", { enumerable: true, get: function () { return hook_state_js_1.durationSeconds; } });
114
+ Object.defineProperty(exports, "HookStateError", { enumerable: true, get: function () { return hook_state_js_1.HookStateError; } });
98
115
  // Operation-normalized leaf extraction — the robust matching primitive a
99
116
  // hardened guard is built on (see examples/harness/safe-bash-guard-v2.mjs).
100
117
  var bash_effects_js_1 = require("./core/bash-effects.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "15.0.3",
3
+ "version": "15.2.0",
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",