vigiles 16.1.1 → 16.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.
@@ -52,10 +52,12 @@ const hook_block_ineffective_js_1 = require("./core/hook-block-ineffective.js");
52
52
  const hook_matcher_js_1 = require("./core/hook-matcher.js");
53
53
  const test_coverage_files_js_1 = require("./test-coverage-files.js");
54
54
  const coverage_evidence_js_1 = require("./coverage-evidence.js");
55
+ const source_refs_js_1 = require("./core/source-refs.js");
55
56
  const scan_core_js_1 = require("./scan-core.js");
56
57
  // Zero imports of its own — pure string work, safe in the browser engine.
57
58
  const skill_refs_js_1 = require("./skill-refs.js");
58
59
  const merge_conflict_js_1 = require("./core/merge-conflict.js");
60
+ const scan_core_js_2 = require("./scan-core.js");
59
61
  /**
60
62
  * The synthetic absolute root every path in a browser scan resolves against. A
61
63
  * pure, deterministic string (never `process.cwd()`), so `join`/`relative` stay
@@ -284,9 +286,10 @@ function materializeSurfaces(files, layout, acc, repoName) {
284
286
  // ---------------------------------------------------------------------------
285
287
  // Dangling intra-plugin refs (mirrors plugin-loader.ts danglingRefs)
286
288
  // ---------------------------------------------------------------------------
287
- const INTRA_REF_EXTS = "md|sh|cmd|mjs|cjs|js|ts|py|rb|txt|json";
289
+ // Extensions + BOTH token boundaries live in core/source-refs.ts, so this and
290
+ // its disk twin (plugin-loader.ts) cannot disagree and neither can omit one.
288
291
  function intraRefRe(layout) {
289
- return new RegExp(`(?:${layout.intraRefDirs.join("|")})/[A-Za-z0-9._/-]+\\.(?:${INTRA_REF_EXTS})`, "g");
292
+ return (0, source_refs_js_1.intraRefPattern)(layout.intraRefDirs);
290
293
  }
291
294
  const NON_PLUGIN_VARS = new Set([
292
295
  "CLAUDE_PROJECT_DIR",
@@ -295,10 +298,14 @@ const NON_PLUGIN_VARS = new Set([
295
298
  "PWD",
296
299
  "OLDPWD",
297
300
  ]);
298
- /** Mirror of plugin-loader.ts `isPluginRooted`. */
301
+ /** Mirror of plugin-loader.ts `isPluginRooted`, including its boundary test:
302
+ * a match preceded by a path-segment character landed INSIDE a longer name
303
+ * (`claude-agents/`) and refers to nothing. */
299
304
  function isPluginRooted(content, idx) {
300
- if (idx === 0 || content[idx - 1] !== "/")
301
- return true;
305
+ // Start-of-content is handled by `startsAtSeparator` itself (`idx <= 0`), not
306
+ // by a guard here — see the twin in plugin-loader.ts.
307
+ if (content[idx - 1] !== "/")
308
+ return (0, source_refs_js_1.startsAtSeparator)(content, idx);
302
309
  const seg = /([^\s"'`(=:/]*)$/.exec(content.slice(0, idx - 1))?.[1] ?? "";
303
310
  const varName = /^\$\{?(\w+)\}?$/.exec(seg)?.[1];
304
311
  if (varName !== undefined)
@@ -306,19 +313,6 @@ function isPluginRooted(content, idx) {
306
313
  return false;
307
314
  }
308
315
  const DOC_SOURCE_RE = /\.(?:md|markdown|mdx|txt|rst)$/i;
309
- // A `.sh`/`.bash`/`.cmd` SCRIPT's own `#`-led comments are prose, not code — a
310
- // usage comment (`# bash skills/<plugin>/hooks/setup.sh`, written as it would be
311
- // invoked from the REPO CHECKOUT root) is not a real file operation any more than
312
- // a path mentioned in a doc file is (issue #110). Mirror of plugin-loader.ts
313
- // `SCRIPT_COMMENT_RE`.
314
- const SCRIPT_COMMENT_RE = /\.(?:sh|bash|cmd)$/i;
315
- /** Mirror of plugin-loader.ts `stripShellComments`. */
316
- function stripShellComments(content) {
317
- return content
318
- .split("\n")
319
- .filter((line) => !line.trimStart().startsWith("#"))
320
- .join("\n");
321
- }
322
316
  /** The plugin's executable (non-prose) source-file CONTENTS under the surface dirs. */
323
317
  function executableContents(files, layout) {
324
318
  const out = [];
@@ -332,7 +326,8 @@ function executableContents(files, layout) {
332
326
  continue;
333
327
  if (byteLen(content) > MAX_SKILL_FILE_BYTES)
334
328
  continue;
335
- out.push(SCRIPT_COMMENT_RE.test(k) ? stripShellComments(content) : content);
329
+ // A full-line comment is prose in EVERY language, not only in shell.
330
+ out.push((0, source_refs_js_1.stripFullLineComments)(k, content));
336
331
  }
337
332
  }
338
333
  return out;
@@ -475,7 +470,8 @@ function scanFiles(files, layout = layout_js_1.claudeCodeLayout, dialect = diale
475
470
  const hookRegs = (0, hook_normalize_js_1.normalizeHooks)(loaded.settings.hooks);
476
471
  const { hooks, inline, manual } = (0, scan_core_js_1.scanHooks)(hookRegs, exports.BROWSER_ROOT, lay.pluginRootToken, exists);
477
472
  const eventNames = (0, hook_normalize_js_1.hookEventNames)(loaded.settings.hooks);
478
- const hookEventIssues = (0, hook_events_js_1.confidentHookEventIssues)((0, hook_events_js_1.verifyHookEvents)(eventNames, dialect));
473
+ const allHookEventIssues = (0, hook_events_js_1.verifyHookEvents)(eventNames, dialect);
474
+ const hookEventIssues = (0, hook_events_js_1.scoredIssues)(allHookEventIssues);
479
475
  const instructions = loaded.files[lay.instructionFile] !== undefined
480
476
  ? {
481
477
  file: lay.instructionFile,
@@ -534,6 +530,7 @@ function scanFiles(files, layout = layout_js_1.claudeCodeLayout, dialect = diale
534
530
  sources: loaded.sources,
535
531
  })).map(skill_refs_js_1.formatSkillRefIssue),
536
532
  hookEventIssues,
533
+ vocabularyNotes: (0, scan_core_js_2.collectVocabularyNotes)(allHookEventIssues, agents),
537
534
  frontmatterIssues: remap((0, scan_core_js_1.frontmatterIssuesFor)(loaded.files, cls)),
538
535
  frontmatterValueIssues: remap((0, scan_core_js_1.frontmatterValueIssuesFor)(loaded.files, cls)),
539
536
  skillMetaIssues: remap((0, scan_core_js_1.skillMetaIssuesFor)(loaded.files, cls)),
package/dist/scan.d.ts CHANGED
@@ -78,6 +78,12 @@ export interface ScanAgent {
78
78
  readonly toolIssues: readonly ToolIssue[];
79
79
  /** MCP tool entries naming a server the plugin doesn't declare (can't resolve). */
80
80
  readonly mcpToolIssues: readonly McpToolIssue[];
81
+ /**
82
+ * ADVISORY tool findings — a real tool withheld only under a condition vigiles
83
+ * cannot see, or a name not in its capture. Surfaced via `vocabularyNotes`,
84
+ * never scored. Optional: absence is not a claim of zero.
85
+ */
86
+ readonly toolNotes?: readonly ToolIssue[];
81
87
  /** `disallowedTools:` block-list entries that are typos of a real tool (block nothing). */
82
88
  readonly disallowedToolIssues: readonly ToolIssue[];
83
89
  /**
@@ -102,6 +108,12 @@ export interface ScanAgent {
102
108
  readonly trifecta: TrifectaFinding | null;
103
109
  }
104
110
  /** A lethal-trifecta finding tagged with the surface (subagent/skill) that holds it. */
111
+ /** One advisory vocabulary finding, tagged with the surface that carries it. */
112
+ export interface VocabularyNote {
113
+ /** Where it was found — a hook event key, or an agent's path. */
114
+ readonly where: string;
115
+ readonly message: string;
116
+ }
105
117
  export interface ScanTrifectaFinding {
106
118
  readonly path: string;
107
119
  readonly kind: "subagent" | "skill";
@@ -218,6 +230,18 @@ export interface ScanReport {
218
230
  readonly skillRefIssues?: readonly string[];
219
231
  /** Hooks registered under an event name the harness doesn't define (typo / dead). */
220
232
  readonly hookEventIssues: readonly HookEventIssue[];
233
+ /**
234
+ * ADVISORY vocabulary findings — names vigiles could not confirm, and real
235
+ * names the platform withholds only under a condition it cannot see. Surfaced
236
+ * and NEVER scored, which is the point: the two failure modes this replaced
237
+ * were silence (which reads as approval and hides vigiles's own staleness) and
238
+ * an error (which blames the user for our gap). Neither is a verdict, so these
239
+ * get a third channel instead of a grade.
240
+ *
241
+ * OPTIONAL because absence is not a claim: a producer predating this field
242
+ * reports nothing rather than reporting zero.
243
+ */
244
+ readonly vocabularyNotes?: readonly VocabularyNote[];
221
245
  /** Skills/agents missing a required frontmatter field (name; agents also description). */
222
246
  readonly frontmatterIssues: readonly FrontmatterIssue[];
223
247
  /** Agent frontmatter fields with an invalid value (a typo of a real model/color). */
package/dist/scan.js CHANGED
@@ -153,7 +153,8 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
153
153
  // object keys only and returns [] for an array. We don't interpret a format we
154
154
  // don't own.
155
155
  const eventNames = (0, hook_normalize_js_1.hookEventNames)(loaded.settings.hooks);
156
- const hookEventIssues = (0, hook_events_js_1.confidentHookEventIssues)((0, hook_events_js_1.verifyHookEvents)(eventNames, dialect));
156
+ const allHookEventIssues = (0, hook_events_js_1.verifyHookEvents)(eventNames, dialect);
157
+ const hookEventIssues = (0, hook_events_js_1.scoredIssues)(allHookEventIssues);
157
158
  const instructions = loaded.files[lay.instructionFile] !== undefined
158
159
  ? {
159
160
  file: lay.instructionFile,
@@ -211,6 +212,7 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
211
212
  sources: loaded.sources,
212
213
  })).map(skill_refs_js_1.formatSkillRefIssue),
213
214
  hookEventIssues,
215
+ vocabularyNotes: (0, scan_core_js_1.collectVocabularyNotes)(allHookEventIssues, agents),
214
216
  frontmatterIssues: remap((0, scan_core_js_1.frontmatterIssuesFor)(loaded.files, cls)),
215
217
  frontmatterValueIssues: remap((0, scan_core_js_1.frontmatterValueIssuesFor)(loaded.files, cls)),
216
218
  skillMetaIssues: remap((0, scan_core_js_1.skillMetaIssuesFor)(loaded.files, cls)),
@@ -468,6 +470,11 @@ function formatScanReport(r) {
468
470
  out.push(...section("Hooks", hookLines, r.hooks.length + r.inlineHooks));
469
471
  out.push(...section("Broken references", r.danglingRefs.map((ref) => ` ✗ ${ref} (referenced but MISSING)`)));
470
472
  out.push(...section("Hook events", r.hookEventIssues.map((i) => ` ✗ ${i.message}`)));
473
+ // Advisory, never scored — a name vigiles cannot confirm, or a real one the
474
+ // platform withholds only under a condition it cannot see. Printed with `·`
475
+ // rather than `✗` so it reads as a note about vigiles's knowledge, not a
476
+ // defect in the repo being audited.
477
+ out.push(...section("Vocabulary notes (advisory, not graded)", (r.vocabularyNotes ?? []).map((n) => ` · ${n.where}: ${n.message}`)));
471
478
  out.push(...section("Frontmatter", [
472
479
  ...r.frontmatterIssues.map((i) => ` ✗ ${i.message}`),
473
480
  ...r.frontmatterValueIssues.map((i) => ` ✗ ${i.message}`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "16.1.1",
3
+ "version": "16.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",