vigiles 27.1.1 → 27.1.3
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/core/frontmatter-read.js +104 -8
- package/package.json +1 -1
|
@@ -124,19 +124,115 @@ function salvageField(block, key) {
|
|
|
124
124
|
.replace(/["']$/, "")
|
|
125
125
|
.trim() || undefined);
|
|
126
126
|
}
|
|
127
|
-
/** Salvage a list field from the raw block (single-line key only).
|
|
127
|
+
/** Salvage a list field from the raw block (single-line key only).
|
|
128
|
+
*
|
|
129
|
+
* THE WRAPPER QUOTES COME OFF HERE, not in `splitList`, so both paths hand the
|
|
130
|
+
* tokenizer the SAME shape. On valid YAML js-yaml has already removed a scalar's
|
|
131
|
+
* surrounding quotes; only this regex salvage hands them through, and leaving
|
|
132
|
+
* that asymmetry in place made `tools: "Bash(git status *), Read"` tokenize as
|
|
133
|
+
* `Bash(git` · `status` · `*)` · `Read` — the opening YAML quote read as a shell
|
|
134
|
+
* quote, so the parens stopped counting as structure and the grant's own spaces
|
|
135
|
+
* split it. That is the phantom-tool failure #217 warned about, in the
|
|
136
|
+
* false-EXPOSED direction (`bashGrantIsUnbounded` answers "unbounded" when it
|
|
137
|
+
* cannot recognise a grant), and it was a REGRESSION: the older comma-only
|
|
138
|
+
* parser recovered this input correctly.
|
|
139
|
+
*
|
|
140
|
+
* Only a MATCHED pair is stripped, and only when it wraps the whole value — an
|
|
141
|
+
* unbalanced leading quote is left alone rather than guessed at. */
|
|
128
142
|
function salvageList(block, key) {
|
|
129
143
|
const match = new RegExp(`^${key}:[ \\t]*(.*)$`, "m").exec(block);
|
|
130
144
|
if (!match)
|
|
131
145
|
return null;
|
|
132
|
-
return splitList(match[1]);
|
|
146
|
+
return splitList(stripWrapperQuotes(match[1]));
|
|
133
147
|
}
|
|
134
|
-
/**
|
|
148
|
+
/** Remove ONE matched pair of quotes wrapping an entire scalar. */
|
|
149
|
+
function stripWrapperQuotes(raw) {
|
|
150
|
+
const v = raw.trim();
|
|
151
|
+
const q = v[0];
|
|
152
|
+
if ((q === '"' || q === "'") && v.length >= 2 && v[v.length - 1] === q) {
|
|
153
|
+
return v.slice(1, -1);
|
|
154
|
+
}
|
|
155
|
+
return v;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Split a tool-list string into trimmed, de-quoted tokens.
|
|
159
|
+
*
|
|
160
|
+
* WHITESPACE IS A SEPARATOR, BUT ONLY AT PAREN DEPTH 0 — and that qualifier is
|
|
161
|
+
* the whole rule. Claude Code documents three equivalent spellings of
|
|
162
|
+
* `allowed-tools:`/`disallowed-tools:` (a comma-separated string, a
|
|
163
|
+
* SPACE-separated string, a YAML list), and its own documented example is
|
|
164
|
+
* space-separated with spaces INSIDE the tokens:
|
|
165
|
+
*
|
|
166
|
+
* allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
|
|
167
|
+
*
|
|
168
|
+
* Until #217 this split on `,` alone, so a space-separated fence arrived as ONE
|
|
169
|
+
* token that matches no built-in name. That failed in the direction that misleads:
|
|
170
|
+
* a fence the harness really enforces was reported as closing no lethal-trifecta
|
|
171
|
+
* leg, so an author who wrote a correct fence was told it did nothing. Reported
|
|
172
|
+
* with a reproduction by @vlad-ryzhkov, measured on a 22-skill harness where the
|
|
173
|
+
* separator alone moved Safety 81 → 80 → 81.
|
|
174
|
+
*
|
|
175
|
+
* THE NAIVE FIX IS WORSE THAN THE BUG, which is why this is a tokenizer and not
|
|
176
|
+
* `split(/[,\s]+/)`: that shreds `Bash(git push *)` into `Bash(git`, `push`, `*)`,
|
|
177
|
+
* and since `bashGrantIsUnbounded()` answers "unbounded" when it cannot recognise
|
|
178
|
+
* a grant, a BOUNDED grant would start reading as an unbounded one. That is the
|
|
179
|
+
* false-exposed side of the trade `core/lethal-trifecta.ts` already reasons about
|
|
180
|
+
* — a scarier verdict for a reason the author cannot see anywhere in their file.
|
|
181
|
+
*
|
|
182
|
+
* Quotes are stripped per token AFTER splitting, never treated as a delimiter:
|
|
183
|
+
* `allowed-tools: "Read Write Glob"` is a YAML-quoted scalar whose VALUE is a
|
|
184
|
+
* space-separated list, and Claude Code splits it. Treating the quotes as token
|
|
185
|
+
* boundaries would keep exactly the bug this fixes for every skill that quotes
|
|
186
|
+
* its list.
|
|
187
|
+
*/
|
|
135
188
|
function splitList(raw) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
189
|
+
const out = [];
|
|
190
|
+
let token = "";
|
|
191
|
+
let depth = 0;
|
|
192
|
+
let quote = null;
|
|
193
|
+
let escaped = false;
|
|
194
|
+
const flush = () => {
|
|
195
|
+
const t = token.trim().replace(/^["']|["']$/g, "");
|
|
196
|
+
if (t.length > 0)
|
|
197
|
+
out.push(t);
|
|
198
|
+
token = "";
|
|
199
|
+
};
|
|
200
|
+
for (const ch of raw.trim().replace(/^\[|\]$/g, "")) {
|
|
201
|
+
if (escaped) {
|
|
202
|
+
escaped = false;
|
|
203
|
+
token += ch;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === "\\" && quote !== "'") {
|
|
207
|
+
escaped = true;
|
|
208
|
+
token += ch;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (quote === null && (ch === '"' || ch === "'"))
|
|
212
|
+
quote = ch;
|
|
213
|
+
else if (quote === ch)
|
|
214
|
+
quote = null;
|
|
215
|
+
// Depth tracks STRUCTURE, so a parenthesis inside a quoted string is a
|
|
216
|
+
// character, not a bracket. Without this, `Bash(printf '( %s' foo) Read`
|
|
217
|
+
// leaves depth at 1 after the grant closes and swallows `Read` into the
|
|
218
|
+
// same token — on a subagent that denies a tool the author granted, which
|
|
219
|
+
// is the exact failure this function exists to fix.
|
|
220
|
+
else if (quote === null) {
|
|
221
|
+
if (ch === "(")
|
|
222
|
+
depth++;
|
|
223
|
+
else if (ch === ")")
|
|
224
|
+
depth = Math.max(0, depth - 1);
|
|
225
|
+
}
|
|
226
|
+
// Quotes deliberately do NOT suppress splitting: `allowed-tools: "Read
|
|
227
|
+
// Write Glob"` is a YAML-quoted scalar whose VALUE is a space-separated
|
|
228
|
+
// list, and Claude Code splits it. The quotes come off per token below.
|
|
229
|
+
if (depth === 0 && (ch === "," || /\s/.test(ch))) {
|
|
230
|
+
flush();
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
token += ch;
|
|
234
|
+
}
|
|
235
|
+
flush();
|
|
236
|
+
return out;
|
|
141
237
|
}
|
|
142
238
|
//# sourceMappingURL=frontmatter-read.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "27.1.
|
|
3
|
+
"version": "27.1.3",
|
|
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",
|