vigiles 19.0.0 → 19.0.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.
@@ -83,9 +83,41 @@ function normalizeHookRef(hookPath, cwd = process.cwd()) {
83
83
  */
84
84
  function managesHook(entry, hookPath) {
85
85
  const ref = normalizeHookRef(hookPath);
86
- return entry.hooks.some((h) => h.command
87
- .split(/\s+/)
88
- .some((token) => token !== "" && normalizeHookRef(token) === ref));
86
+ return entry.hooks.some((h) => h.command.split(/\s+/).some((token) => {
87
+ const bare = bareToken(token);
88
+ return bare !== "" && normalizeHookRef(bare) === ref;
89
+ }));
90
+ }
91
+ /**
92
+ * A command token reduced to the PATH it names, so two spellings of the same
93
+ * hook file compare equal.
94
+ *
95
+ * 🔴 BOTH STRIPS ARE REGRESSIONS, MEASURED IN A CONSUMER REPO 2026-08-21, and
96
+ * they compound: a settings.json wired the Claude-Code-recommended way carries
97
+ * BOTH a quote and the project-dir variable, so `managesHook` saw
98
+ * `"$CLAUDE_PROJECT_DIR/.claude/hooks/x.hook.ts"`, canonicalized it to
99
+ * something under the cwd that resembles nothing, and reported "not managed by
100
+ * this hook". Recompiling then APPENDED its own block beside the existing one.
101
+ * Twelve hooks, twelve duplicates, and the duplicate is the WORSE of the two:
102
+ * it spells the path relative to the cwd, so it dies with exit 2 the moment the
103
+ * agent runs from a subdirectory — while the healthy copy beside it keeps
104
+ * working, which is why this survived a full day unnoticed.
105
+ *
106
+ * - QUOTES. A path with a space MUST be quoted, so the quoted form is not an
107
+ * exotic spelling — it is the correct one. Comparing it raw could never match.
108
+ * - `$CLAUDE_PROJECT_DIR`. It is defined as the project root, which is exactly
109
+ * what `normalizeHookRef` resolves relative paths against, so stripping the
110
+ * prefix makes the two spellings the same path by definition rather than by
111
+ * guess. `${CLAUDE_PROJECT_DIR}` is the same variable in brace syntax.
112
+ *
113
+ * Nothing else is stripped. A token this function does not recognise is left
114
+ * alone and simply fails to match, which is the pre-existing behaviour: the
115
+ * cost of a miss here is a duplicate block, and the cost of an over-match is
116
+ * deleting a hook the user wrote themselves.
117
+ */
118
+ function bareToken(token) {
119
+ const unquoted = token.replace(/^["']/, "").replace(/["']$/, "");
120
+ return unquoted.replace(/^\$\{?CLAUDE_PROJECT_DIR\}?[/\\]/, "");
89
121
  }
90
122
  /**
91
123
  * Idempotently merge a compiled hook's block into an existing `settings.json`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "19.0.0",
3
+ "version": "19.0.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",