vigiles 26.0.0 → 26.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.
package/dist/cli.js CHANGED
@@ -1283,24 +1283,20 @@ async function checkSpecRefs(excludes, config, silent, dialect) {
1283
1283
  if (!sev)
1284
1284
  return { issues: 0, errors: 0 };
1285
1285
  const found = [];
1286
+ let knownAgents;
1286
1287
  for (const specPath of findSpecs(excludes)) {
1287
1288
  const target = specPath.replace(/\.spec\.ts$/, "");
1288
1289
  if (!(0, node_fs_1.existsSync)(target))
1289
1290
  continue; // never compiled — `compile` reports it
1290
1291
  const spec = await loadSpec(specPath);
1291
- if (!spec || spec._specType !== "claude")
1292
+ if (!spec)
1292
1293
  continue;
1293
1294
  try {
1294
- const { errors } = (0, compile_js_1.compileClaude)(spec, {
1295
- basePath: process.cwd(),
1296
- specFile: specPath,
1297
- dialect,
1298
- maxRules: config?.maxRules,
1299
- maxTokens: config?.maxTokens,
1300
- maxSectionLines: config?.maxSectionLines,
1301
- catalogOnly: config?.catalogOnly,
1302
- linters: config?.linters,
1303
- });
1295
+ // Railway is the one type whose validation needs the sibling agent names;
1296
+ // collected lazily so a repo with no railway spec never pays for the walk.
1297
+ if (spec._specType === "railway")
1298
+ knownAgents ??= await collectAgentNames(excludes);
1299
+ const errors = specCompileErrors(spec, specPath, dialect, config, knownAgents ?? []);
1304
1300
  for (const e of errors)
1305
1301
  found.push(`${target}: ${e.message} (from ${specPath})`);
1306
1302
  }
@@ -1319,6 +1315,53 @@ async function checkSpecRefs(excludes, config, silent, dialect) {
1319
1315
  }
1320
1316
  return { issues: found.length, errors: sev === "error" ? found.length : 0 };
1321
1317
  }
1318
+ /**
1319
+ * The ONE spec-type dispatcher, in the only shape both directions can share:
1320
+ * errors, no writing. `compile` reaches its compilers through the
1321
+ * `compile*ToFile` wrappers (which write and print); `checkSpecRefs` reaches the
1322
+ * SAME compilers through this, because a read must never write.
1323
+ *
1324
+ * Why it exists (#190): `checkSpecRefs` used to call `compileClaude` directly and
1325
+ * skip every other spec type, so a SKILL.md / subagent / railway artifact
1326
+ * committed with a since-deleted reference hashed cleanly against its own header
1327
+ * forever — `lint` green, `compile` red. Measured on this repo:
1328
+ * `examples/SKILL.md.spec.ts` names `skills/enforce-rules-format/SKILL.md`, which
1329
+ * does not exist (the skill lives under `.claude/skills/`), and `lint` reported
1330
+ * `hash valid`.
1331
+ *
1332
+ * A switch rather than a per-type "ref accessor": all four compilers already
1333
+ * return the same `errors: CompileError[]`, so there is nothing to normalize —
1334
+ * only the options differ. Exhaustive over `_specType`, so a FIFTH spec type is
1335
+ * a tsc error here instead of a silent skip, which is the failure this closes.
1336
+ */
1337
+ function specCompileErrors(spec, specPath, dialect, config, knownAgents) {
1338
+ const basePath = process.cwd();
1339
+ switch (spec._specType) {
1340
+ case "claude":
1341
+ return (0, compile_js_1.compileClaude)(spec, {
1342
+ basePath,
1343
+ specFile: specPath,
1344
+ dialect,
1345
+ maxRules: config?.maxRules,
1346
+ maxTokens: config?.maxTokens,
1347
+ maxSectionLines: config?.maxSectionLines,
1348
+ catalogOnly: config?.catalogOnly,
1349
+ linters: config?.linters,
1350
+ }).errors;
1351
+ case "skill":
1352
+ return (0, compile_js_1.compileSkill)(spec, { basePath, specFile: specPath, dialect })
1353
+ .errors;
1354
+ case "agent":
1355
+ return (0, compile_js_1.compileAgent)(spec, { basePath, specFile: specPath, dialect })
1356
+ .errors;
1357
+ case "railway":
1358
+ return (0, compile_js_1.compileRailway)(spec, { specFile: specPath, knownAgents }).errors;
1359
+ default:
1360
+ // A `pipeline` spec compiles through its underlying railway, so it has no
1361
+ // artifact of its own to re-derive refs for.
1362
+ return [];
1363
+ }
1364
+ }
1322
1365
  /**
1323
1366
  * Write a compiled artifact. Accepts ONLY a {@link StampedMarkdown}, so a body
1324
1367
  * that failed to compile cannot reach the disk — there is no stamp to pass.
@@ -690,7 +690,6 @@ function yamlScalar(value) {
690
690
  function renderSkillFrontmatter(spec, profile = "claude-code") {
691
691
  const fm = [
692
692
  "---",
693
- "",
694
693
  `name: ${yamlScalar(spec.name)}`,
695
694
  `description: ${yamlScalar(spec.description)}`,
696
695
  ];
@@ -724,7 +723,7 @@ function renderSkillFrontmatter(spec, profile = "claude-code") {
724
723
  fm.push(`disallowed-tools: [${spec.disallowedTools.join(", ")}]`);
725
724
  }
726
725
  }
727
- fm.push("", "---");
726
+ fm.push("---");
728
727
  return fm.join("\n");
729
728
  }
730
729
  /**
@@ -871,7 +870,15 @@ function compileSkill(spec, options = {}) {
871
870
  const warnings = checkInlineCode(sections, spec.maxInlineCodeLines ?? DEFAULT_MAX_INLINE_CODE_LINES);
872
871
  const marker = purityMarker(spec.purity);
873
872
  const content = renderSkillFrontmatter(spec, profile) +
874
- "\n\n" +
873
+ // ONE newline, not two: `placeIntegrityHeader` puts the stamp AFTER the
874
+ // frontmatter and supplies its own blank line on each side, so a second one
875
+ // here becomes two blank lines in the artifact — which `prettier --check`
876
+ // rejects, and a freshly compiled artifact then cannot pass `npm run check`.
877
+ // Fixed HERE rather than in the stamper: the hash is computed over this
878
+ // content (compile.ts `seal`), so trimming inside `placeIntegrityHeader`
879
+ // would hash one string and write another — measured, it broke integrity on
880
+ // all three frontmatter-bearing artifacts.
881
+ "\n" +
875
882
  (marker ? marker + "\n\n" : "") +
876
883
  sections.trim() +
877
884
  "\n";
@@ -913,7 +920,6 @@ function renderAgentFrontmatter(spec) {
913
920
  // {@link yamlScalar}.
914
921
  const fm = [
915
922
  "---",
916
- "",
917
923
  `name: ${yamlScalar(spec.name)}`,
918
924
  `description: ${yamlScalar(spec.description)}`,
919
925
  ];
@@ -927,7 +933,7 @@ function renderAgentFrontmatter(spec) {
927
933
  if (spec.disallowedTools && spec.disallowedTools.length > 0) {
928
934
  fm.push(`disallowedTools: ${spec.disallowedTools.join(", ")}`);
929
935
  }
930
- fm.push("", "---");
936
+ fm.push("---");
931
937
  return fm.join("\n");
932
938
  }
933
939
  /**
@@ -1070,7 +1076,15 @@ function compileAgent(spec, options) {
1070
1076
  const warnings = checkInlineCode(body, DEFAULT_MAX_INLINE_CODE_LINES);
1071
1077
  const marker = purityMarker(spec.purity);
1072
1078
  const content = renderAgentFrontmatter(spec) +
1073
- "\n\n" +
1079
+ // ONE newline, not two: `placeIntegrityHeader` puts the stamp AFTER the
1080
+ // frontmatter and supplies its own blank line on each side, so a second one
1081
+ // here becomes two blank lines in the artifact — which `prettier --check`
1082
+ // rejects, and a freshly compiled artifact then cannot pass `npm run check`.
1083
+ // Fixed HERE rather than in the stamper: the hash is computed over this
1084
+ // content (compile.ts `seal`), so trimming inside `placeIntegrityHeader`
1085
+ // would hash one string and write another — measured, it broke integrity on
1086
+ // all three frontmatter-bearing artifacts.
1087
+ "\n" +
1074
1088
  (marker ? marker + "\n\n" : "") +
1075
1089
  body.trim() +
1076
1090
  "\n";
package/dist/hook.js CHANGED
@@ -133,8 +133,20 @@ Object.defineProperty(exports, "isStateWrite", { enumerable: true, get: function
133
133
  Object.defineProperty(exports, "admissibleWrites", { enumerable: true, get: function () { return hook_state_js_1.admissibleWrites; } });
134
134
  Object.defineProperty(exports, "durationSeconds", { enumerable: true, get: function () { return hook_state_js_1.durationSeconds; } });
135
135
  Object.defineProperty(exports, "HookStateError", { enumerable: true, get: function () { return hook_state_js_1.HookStateError; } });
136
- // Operation-normalized leaf extraction — the robust matching primitive a
137
- // hardened guard is built on (see examples/harness/safe-bash-guard-v2.mjs).
136
+ // Operation-normalized leaf extraction — the primitive `runs()`, `touches()`
137
+ // and `pipesToShell()` match over, so a guard written against the closed
138
+ // vocabulary sees the OPERATION rather than the literal tokens.
139
+ //
140
+ // It is exported because `examples/harness/safe-bash-guard-v2.mjs` needs it —
141
+ // and that need is the finding, not the feature. v2 is an UNRUN experiment (no
142
+ // test executes it) covering three things the vocabulary cannot yet express: a
143
+ // flag with a value (`--index-url`), an env-assignment prefix, and a
144
+ // cross-leaf pipeline fact (`env | curl`). Reaching for this export is how a
145
+ // guard author escapes the closed vocabulary — the thing `checkHookImports`
146
+ // exists to prevent — so treat a new consumer as a request for vocabulary,
147
+ // not as the intended path. Measured 2026-09-03: on the 7 seeds plus every
148
+ // generated spelling (143 cases) v1 and v2 both block 143/143, so v2 is no
149
+ // longer the "hardened" one; see zernie/vigiles#193.
138
150
  var bash_effects_js_1 = require("./core/bash-effects.js");
139
151
  Object.defineProperty(exports, "leafCommandsNormalized", { enumerable: true, get: function () { return bash_effects_js_1.leafCommandsNormalized; } });
140
152
  //# sourceMappingURL=hook.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "26.0.0",
3
+ "version": "26.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",