vigiles 4.0.1 → 4.0.2

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.
Files changed (54) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/dist/adapter-conformance.js +1 -1
  4. package/dist/check.d.ts +132 -0
  5. package/dist/check.js +318 -0
  6. package/dist/cli.js +130 -52
  7. package/dist/core/compile.d.ts +1 -1
  8. package/dist/core/compile.js +1 -1
  9. package/dist/core/compose.d.ts +1 -1
  10. package/dist/core/compose.js +1 -1
  11. package/dist/core/generate-schema.d.ts +1 -1
  12. package/dist/core/generate-schema.js +4 -4
  13. package/dist/core/linters.js +2 -2
  14. package/dist/core/orphans.js +57 -14
  15. package/dist/core/proofs.js +1 -1
  16. package/dist/core/refs.d.ts +1 -1
  17. package/dist/core/refs.js +2 -2
  18. package/dist/core/sidecar.d.ts +1 -1
  19. package/dist/core/sidecar.js +1 -1
  20. package/dist/core/spec.d.ts +1 -1
  21. package/dist/core/spec.js +1 -1
  22. package/dist/core/types.d.ts +1 -1
  23. package/dist/core/validate.js +2 -2
  24. package/dist/e2e.d.ts +10 -13
  25. package/dist/e2e.js +10 -17
  26. package/dist/eval.d.ts +217 -2
  27. package/dist/eval.js +428 -18
  28. package/dist/harness-assert.d.ts +3 -0
  29. package/dist/harness-assert.js +16 -0
  30. package/dist/harness-test.d.ts +46 -0
  31. package/dist/harness-test.js +102 -0
  32. package/dist/integration.d.ts +8 -0
  33. package/dist/integration.js +10 -0
  34. package/dist/jest.d.ts +3 -1
  35. package/dist/jest.js +3 -2
  36. package/dist/run-hook.d.ts +22 -0
  37. package/dist/run-hook.js +28 -0
  38. package/dist/scan.d.ts +1 -1
  39. package/dist/scan.js +1 -1
  40. package/dist/setup-plan.d.ts +5 -1
  41. package/dist/setup-plan.js +11 -1
  42. package/dist/test-coverage.js +8 -1
  43. package/dist/testing.d.ts +2 -0
  44. package/dist/testing.js +7 -0
  45. package/dist/unit.d.ts +4 -2
  46. package/dist/unit.js +7 -1
  47. package/dist/vitest.d.mts +3 -1
  48. package/hooks/refs-nudge.sh +1 -1
  49. package/package.json +3 -2
  50. package/skills/edit-spec/SKILL.md +21 -10
  51. package/skills/linter-docs/SKILL.md +23 -0
  52. package/skills/migrate-to-spec/SKILL.md +1 -1
  53. package/skills/strengthen/SKILL.md +1 -2
  54. package/skills/generate-rule/SKILL.md +0 -64
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ function findSpecs(pattern) {
54
54
  return (0, glob_1.globSync)(glob, {
55
55
  // `dot: true` so specs that live in a sync tool's source slot (e.g.
56
56
  // `.ruler/AGENTS.md.spec.ts`, the redirect target) are discovered by
57
- // compile/audit/the recompile hook — not just root-level specs.
57
+ // compile/lint/the recompile hook — not just root-level specs.
58
58
  dot: true,
59
59
  ignore: [...IGNORE_NODE_MODULES, "dist/**", ".git/**"],
60
60
  cwd: process.cwd(),
@@ -310,7 +310,7 @@ function verifyHashes(filePaths, silent = false) {
310
310
  const fullPath = (0, node_path_1.resolve)(process.cwd(), filePath);
311
311
  // If the file doesn't exist at all (typo, deleted), that's an error —
312
312
  // not a "no hash" informational message. Without this check, a scoped
313
- // audit like `vigiles audit typo.md` would silently exit clean.
313
+ // lint like `vigiles lint typo.md` would silently exit clean.
314
314
  if (!(0, node_fs_1.existsSync)(fullPath)) {
315
315
  log(`\n✗ ${filePath} — file not found`);
316
316
  if (!silent) {
@@ -387,7 +387,7 @@ function check(filePaths, silent = false) {
387
387
  hashErrors: hashes.errorCount,
388
388
  // `validateSpecs` only returns a boolean today, so we collapse
389
389
  // failures to 1 until it starts reporting counts. Kept in its own
390
- // counter so audit's "stale hash — run vigiles compile" remediation
390
+ // counter so lint's "stale hash — run vigiles compile" remediation
391
391
  // doesn't misreport a require-spec / other validation failure.
392
392
  validationErrors: specsValid ? 0 : 1,
393
393
  };
@@ -403,7 +403,7 @@ async function findDuplicateRules(threshold = 0.3, silent = false, scopeFiles) {
403
403
  console.log(msg);
404
404
  };
405
405
  const allSpecs = findSpecs();
406
- // If audit was invoked with explicit file arguments, only scan the specs
406
+ // If lint was invoked with explicit file arguments, only scan the specs
407
407
  // for those files — otherwise an unrelated duplicate elsewhere in the
408
408
  // repo would fail a targeted CI check (e.g. `vigiles lint path/foo.md`).
409
409
  //
@@ -551,7 +551,7 @@ async function verifyMarkdownMcpRefs(files, silent) {
551
551
  return errors;
552
552
  }
553
553
  /** Exit codes: 0 clean, 1 warnings only, 2 hard errors. */
554
- function auditExitCode(report) {
554
+ function lintExitCode(report) {
555
555
  if (report.hashErrors > 0 ||
556
556
  report.validationErrors > 0 ||
557
557
  report.inlineErrors > 0 ||
@@ -718,7 +718,7 @@ function verifyFrontmatterRules(filePath, silent, exclude, linterOptions) {
718
718
  *
719
719
  * Spec mode is the source of truth when it exists, so a literal
720
720
  * `<!-- vigiles:enforce ... -->` snippet that survived into compiled
721
- * markdown (or an example in a spec-managed file) must not trip audit. A
721
+ * markdown (or an example in a spec-managed file) must not trip lint. A
722
722
  * file is spec-managed iff it has a sibling `<file>.spec.ts` OR its own
723
723
  * `<!-- vigiles:sha256:... compiled from <spec> -->` header. A rule
724
724
  * declared both inline and in frontmatter is verified once (inline wins as
@@ -768,14 +768,14 @@ function verifyMarkdownModeRules(files, silent, config) {
768
768
  return totals;
769
769
  }
770
770
  /**
771
- * Unified audit command: verify hashes, report coverage gaps, detect duplicates,
771
+ * Unified lint command: verify hashes, report coverage gaps, detect duplicates,
772
772
  * suggest improvements.
773
773
  *
774
774
  * Flags:
775
775
  * --summary Print a single-line summary (for SessionStart hooks)
776
776
  * --json Print structured JSON report (for CI integration)
777
777
  */
778
- async function audit(restArgs, flags, config) {
778
+ async function runLint(restArgs, flags, config) {
779
779
  const summary = flags.includes("--summary");
780
780
  const json = flags.includes("--json");
781
781
  const silent = summary || json;
@@ -801,7 +801,7 @@ async function audit(restArgs, flags, config) {
801
801
  console.log("\nLinter rule coverage:\n");
802
802
  const coverage = discover(silent);
803
803
  // 3. Duplicate rule detection (NCD). Scope to the requested files when
804
- // audit was invoked with explicit paths, so targeted CI checks don't
804
+ // lint was invoked with explicit paths, so targeted CI checks don't
805
805
  // fail on unrelated duplicates elsewhere in the repo.
806
806
  if (!silent)
807
807
  console.log("\nDuplicate rule detection:\n");
@@ -878,15 +878,15 @@ async function audit(restArgs, flags, config) {
878
878
  files,
879
879
  };
880
880
  if (summary) {
881
- printAuditSummary(report);
881
+ printLintSummary(report);
882
882
  }
883
883
  else if (json) {
884
884
  console.log(JSON.stringify(report, null, 2));
885
885
  }
886
886
  return report;
887
887
  }
888
- /** Single-line audit summary for SessionStart hooks — minimal token cost. */
889
- function printAuditSummary(report) {
888
+ /** Single-line lint summary for SessionStart hooks — minimal token cost. */
889
+ function printLintSummary(report) {
890
890
  const parts = [];
891
891
  if (report.hashErrors > 0)
892
892
  parts.push(`${String(report.hashErrors)} stale`);
@@ -1036,7 +1036,14 @@ function init(args) {
1036
1036
  // `.ruler/AGENTS.md.spec.ts` source slot → target "AGENTS.md").
1037
1037
  const targetName = (0, node_path_1.basename)(target);
1038
1038
  const targetLine = targetName !== "CLAUDE.md" ? `\n target: "${targetName}",` : "";
1039
- const template = `import { claude, enforce, guidance } from "vigiles/spec";
1039
+ // Import ONLY what the scaffold uses (`claude`) a strict ESLint with
1040
+ // \`no-unused-vars\` + \`--max-warnings=0\` (common in CI) would otherwise fail
1041
+ // the moment this is committed, because the enforce()/guidance() examples
1042
+ // below are commented out. The commented import shows what to add when you
1043
+ // write a real rule.
1044
+ const template = `import { claude } from "vigiles/spec";
1045
+ // When you add rules below, import the builders you use, e.g.:
1046
+ // import { claude, enforce, guidance } from "vigiles/spec";
1040
1047
 
1041
1048
  export default claude({${targetLine}
1042
1049
  sections: {
@@ -1124,7 +1131,7 @@ ${harness}`;
1124
1131
  /**
1125
1132
  * Detect a workflow that drives vigiles through an OLD API — a bare `npx vigiles`
1126
1133
  * (a no-op help screen in v2+) rather than the `zernie/vigiles@` Action or a real
1127
- * subcommand (`audit`/`test`/…). Upgrading users whose workflow predates the
1134
+ * subcommand (`lint`/`test`/…). Upgrading users whose workflow predates the
1128
1135
  * subcommand split silently lose CI validation, so we flag it loudly.
1129
1136
  */
1130
1137
  function workflowUsesStaleApi(content) {
@@ -1135,24 +1142,69 @@ function workflowUsesStaleApi(content) {
1135
1142
  const hasModernCmd = /vigiles\s+(lint|test|eval|compile|scan|generate-types|generate-schema|init)\b/.test(content);
1136
1143
  return !hasModernCmd;
1137
1144
  }
1145
+ /**
1146
+ * Subcommands removed/renamed across majors → the replacement to suggest. A
1147
+ * workflow that still calls one is a silently-broken CI step (the removed
1148
+ * subcommand exits non-zero / no-ops), and this stays true even when the file
1149
+ * ALSO uses the Action or a modern command — so it is checked independently of
1150
+ * the bare-API heuristic above, which an Action reference short-circuits.
1151
+ */
1152
+ const REMOVED_SUBCOMMANDS = {
1153
+ audit: "lint", // v3 → v4 rename
1154
+ };
1155
+ /** The first removed/renamed `vigiles <sub>` a workflow still calls, if any. */
1156
+ function workflowRemovedSubcommand(content) {
1157
+ for (const [sub, replacement] of Object.entries(REMOVED_SUBCOMMANDS)) {
1158
+ if (new RegExp(`vigiles\\s+${sub}\\b`).test(content))
1159
+ return { sub, replacement };
1160
+ }
1161
+ return null;
1162
+ }
1163
+ /** Rewrite removed/renamed `vigiles <sub>` invocations in place (audit → lint).
1164
+ * Surgical — preserves the rest of the user's workflow. */
1165
+ function rewriteRemovedSubcommands(content) {
1166
+ let out = content;
1167
+ for (const [sub, replacement] of Object.entries(REMOVED_SUBCOMMANDS)) {
1168
+ out = out.replace(new RegExp(`(vigiles\\s+)${sub}\\b`, "g"), `$1${replacement}`);
1169
+ }
1170
+ return out;
1171
+ }
1138
1172
  /** Create `.github/workflows/vigiles.yml`. Returns the files it wrote (for the
1139
- * commit hint). An existing workflow is never clobbered, but a STALE one (old
1140
- * bare-`npx vigiles` API) is reported loudly instead of silently skipped. */
1173
+ * commit hint). An existing workflow is never clobbered unless `--force`, but a
1174
+ * STALE one (old bare-`npx vigiles` API, or a removed subcommand) is reported
1175
+ * loudly instead of silently skipped — and rewritten in place with `--force`. */
1141
1176
  function wireGha(plan) {
1142
1177
  const dir = (0, node_path_1.resolve)(process.cwd(), ".github", "workflows");
1143
1178
  const path = (0, node_path_1.resolve)(dir, "vigiles.yml");
1179
+ const rel = ".github/workflows/vigiles.yml";
1144
1180
  if ((0, node_fs_1.existsSync)(path)) {
1145
1181
  const content = (0, node_fs_1.readFileSync)(path, "utf-8");
1146
- if (workflowUsesStaleApi(content)) {
1147
- console.log("⚠ .github/workflows/vigiles.yml is STALE — it runs a bare `npx vigiles`,\n" +
1148
- " which is a no-op help screen now. Replace its run step with the Action +\n" +
1149
- " the test job (CI is otherwise silently not validating anything):\n" +
1150
- " - uses: zernie/vigiles@v1 # lint pillar — verify references\n" +
1151
- " - run: npx vigiles test # pillar 2 — harness tests\n" +
1152
- " Or delete the file and re-run `vigiles init` to regenerate it.");
1182
+ const removed = workflowRemovedSubcommand(content);
1183
+ if (removed) {
1184
+ if (plan.force) {
1185
+ (0, node_fs_1.writeFileSync)(path, rewriteRemovedSubcommands(content));
1186
+ console.log(`✓ Rewrote ${rel} (vigiles ${removed.sub} ${removed.replacement})`);
1187
+ return [rel];
1188
+ }
1189
+ console.log(`⚠ ${rel} is STALE — it runs \`vigiles ${removed.sub}\`,\n` +
1190
+ ` which was removed/renamed (now \`vigiles ${removed.replacement}\`). That CI\n` +
1191
+ " step is silently broken. Fix it:\n" +
1192
+ ` - re-run \`vigiles init --force\` to rewrite it in place (vigiles ${removed.sub} → ${removed.replacement}), or\n` +
1193
+ " - switch the run step to `uses: zernie/vigiles@v1` (the composite Action).");
1194
+ }
1195
+ else if (workflowUsesStaleApi(content)) {
1196
+ if (plan.force) {
1197
+ (0, node_fs_1.writeFileSync)(path, vigilesWorkflow(plan));
1198
+ console.log(`✓ Regenerated ${rel} (was a stale bare \`npx vigiles\`)`);
1199
+ return [rel];
1200
+ }
1201
+ console.log(`⚠ ${rel} is STALE — it runs a bare \`npx vigiles\`,\n` +
1202
+ " which is a no-op help screen now. CI is silently not validating anything. Fix it:\n" +
1203
+ " - re-run `vigiles init --force` to regenerate the workflow, or\n" +
1204
+ " - replace its run step with `uses: zernie/vigiles@v1` + `run: npx vigiles test`.");
1153
1205
  }
1154
1206
  else {
1155
- console.log("✓ .github/workflows/vigiles.yml already exists (up to date)");
1207
+ console.log(`✓ ${rel} already exists (up to date)`);
1156
1208
  }
1157
1209
  return [];
1158
1210
  }
@@ -1461,6 +1513,51 @@ function harnessBinaryPresent(bin) {
1461
1513
  return false;
1462
1514
  }
1463
1515
  }
1516
+ /** Run a plan's auto-install commands; classify the result. */
1517
+ function runInstall(plan, exec) {
1518
+ if (plan.commands.length === 0)
1519
+ return "no-cli";
1520
+ try {
1521
+ for (const cmd of plan.commands) {
1522
+ exec(cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
1523
+ }
1524
+ return "ok";
1525
+ }
1526
+ catch {
1527
+ return "failed";
1528
+ }
1529
+ }
1530
+ /**
1531
+ * Report a plan's outcome. On anything but success, be LOUD — when an AGENT runs
1532
+ * `init` (no human at the TTY), a quiet "Install vigiles:" hint followed by
1533
+ * `/plugin` slash commands is a trap: the agent can't run a TUI slash command,
1534
+ * so the plugin silently never installs. Surface the failure as a warning AND
1535
+ * lead with the shell-runnable CLI form (which an agent CAN run), keeping the
1536
+ * slash commands clearly labelled as the in-TUI alternative for a human.
1537
+ */
1538
+ function reportInstall(plan, outcome) {
1539
+ if (outcome === "ok") {
1540
+ console.log(plan.successMessage);
1541
+ for (const note of plan.notes)
1542
+ console.log(` ${note}`);
1543
+ return;
1544
+ }
1545
+ console.log(outcome === "failed"
1546
+ ? `⚠ vigiles plugin auto-install for ${plan.harness} FAILED — the plugin (hooks + skills) is NOT installed.`
1547
+ : `⚠ vigiles plugin for ${plan.harness} was NOT installed (the ${plan.harness} CLI isn't on PATH here).`);
1548
+ if (plan.commands.length > 0) {
1549
+ console.log(" Finish from a shell (an agent can run these):");
1550
+ for (const cmd of plan.commands)
1551
+ console.log(` ${cmd}`);
1552
+ }
1553
+ if (plan.manualSteps.length > 0) {
1554
+ console.log(" Or inside the Claude Code TUI (a human, not an agent):");
1555
+ for (const step of plan.manualSteps)
1556
+ console.log(` ${step}`);
1557
+ }
1558
+ for (const note of plan.notes)
1559
+ console.log(` ${note}`);
1560
+ }
1464
1561
  /**
1465
1562
  * Install vigiles's skills/hooks for the chosen harness(es) via the per-harness
1466
1563
  * `planPluginInstall` decision — Claude Code through the GLOBAL plugin
@@ -1474,26 +1571,7 @@ function installPlugins(harnesses) {
1474
1571
  });
1475
1572
  for (const plan of plans) {
1476
1573
  console.log("");
1477
- let installed = false;
1478
- if (plan.commands.length > 0) {
1479
- try {
1480
- for (const cmd of plan.commands) {
1481
- exec(cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
1482
- }
1483
- console.log(plan.successMessage);
1484
- installed = true;
1485
- }
1486
- catch {
1487
- // Fall through to the manual instructions below.
1488
- }
1489
- }
1490
- if (!installed) {
1491
- console.log(`Install vigiles for ${plan.harness}:`);
1492
- for (const step of plan.manualSteps)
1493
- console.log(` ${step}`);
1494
- }
1495
- for (const note of plan.notes)
1496
- console.log(` ${note}`);
1574
+ reportInstall(plan, runInstall(plan, exec));
1497
1575
  }
1498
1576
  }
1499
1577
  /** Add/upgrade `vigiles` in the project's `devDependencies` (and move it out of
@@ -1725,7 +1803,7 @@ function checkUntestedSurfaces(config, silent) {
1725
1803
  }
1726
1804
  /**
1727
1805
  * Apply the configured coverage thresholds. Returns the number of failing
1728
- * thresholds (so the audit can fail CI when severity is "error").
1806
+ * thresholds (so the lint can fail CI when severity is "error").
1729
1807
  *
1730
1808
  * Loads specs directly via loadSpec() when the scripts threshold is set —
1731
1809
  * avoids depending on a pre-built `dist/` tree, which the setup-generated
@@ -1934,7 +2012,7 @@ function printUsage(command) {
1934
2012
  console.log("vigiles — compile typed specs to instruction files");
1935
2013
  console.log("");
1936
2014
  console.log("Commands:");
1937
- console.log(" vigiles init [flags] Setup project (--lint, --test, --harness=, --strict, --no-gha)");
2015
+ console.log(" vigiles init [flags] Setup project (--lint, --test, --harness=, --strict, --no-gha, --force)");
1938
2016
  console.log(" vigiles compile [files...] Compile .spec.ts → .md");
1939
2017
  console.log(" vigiles lint [files...] Verify references, find gaps in instruction files");
1940
2018
  console.log(" vigiles test [files...] Run *.harness.mjs deterministic harness tests");
@@ -1960,11 +2038,11 @@ function printUsage(command) {
1960
2038
  // Main
1961
2039
  // ---------------------------------------------------------------------------
1962
2040
  /**
1963
- * Emit GitHub Actions annotations for an audit report. Skipped when --json or
2041
+ * Emit GitHub Actions annotations for an lint report. Skipped when --json or
1964
2042
  * --summary is active — those modes promise clean machine-readable stdout, and
1965
2043
  * ::error/::warning lines would contaminate output parsed as JSON.
1966
2044
  */
1967
- function annotateAuditForGitHub(report, flags) {
2045
+ function annotateLintForGitHub(report, flags) {
1968
2046
  const structuredOutput = flags.includes("--json") || flags.includes("--summary");
1969
2047
  if (!isGitHubActions() || structuredOutput)
1970
2048
  return;
@@ -1972,7 +2050,7 @@ function annotateAuditForGitHub(report, flags) {
1972
2050
  ghAnnotate("error", `${String(report.hashErrors)} compiled file(s) with stale hash — run vigiles compile`);
1973
2051
  }
1974
2052
  if (report.validationErrors > 0) {
1975
- ghAnnotate("error", `${String(report.validationErrors)} spec validation failure(s) — see audit output`);
2053
+ ghAnnotate("error", `${String(report.validationErrors)} spec validation failure(s) — see lint output`);
1976
2054
  }
1977
2055
  if (report.duplicatePairs > 0) {
1978
2056
  ghAnnotate("warning", `${String(report.duplicatePairs)} near-duplicate rule pair(s) detected — consider merging`);
@@ -2303,9 +2381,9 @@ async function main() {
2303
2381
  case "lint": {
2304
2382
  // lint = verify references + discover + guidance count
2305
2383
  const flags = args.slice(1).filter((a) => a.startsWith("--"));
2306
- const report = await audit(restArgs, flags, config);
2307
- annotateAuditForGitHub(report, flags);
2308
- const exitCode = auditExitCode(report);
2384
+ const report = await runLint(restArgs, flags, config);
2385
+ annotateLintForGitHub(report, flags);
2386
+ const exitCode = lintExitCode(report);
2309
2387
  if (exitCode !== 0) {
2310
2388
  process.exit(exitCode);
2311
2389
  }
@@ -92,7 +92,7 @@ export interface CompileAgentResult {
92
92
  /**
93
93
  * Compile an AgentSpec into a subagent markdown file with YAML frontmatter.
94
94
  * Verifies the tool contract and the body's references; the marks the body
95
- * carries (`vigiles:symbol`, file/cmd refs) are the same ones `audit` re-checks.
95
+ * carries (`vigiles:symbol`, file/cmd refs) are the same ones `lint` re-checks.
96
96
  */
97
97
  export declare function compileAgent(spec: AgentSpec, options: {
98
98
  basePath?: string;
@@ -802,7 +802,7 @@ function renderAgentRules(rules) {
802
802
  /**
803
803
  * Compile an AgentSpec into a subagent markdown file with YAML frontmatter.
804
804
  * Verifies the tool contract and the body's references; the marks the body
805
- * carries (`vigiles:symbol`, file/cmd refs) are the same ones `audit` re-checks.
805
+ * carries (`vigiles:symbol`, file/cmd refs) are the same ones `lint` re-checks.
806
806
  */
807
807
  function compileAgent(spec, options) {
808
808
  const basePath = options.basePath ?? process.cwd();
@@ -13,7 +13,7 @@
13
13
  * This detector is pure filesystem inspection — the same deterministic-detector
14
14
  * shape as `orphans.ts` / `test-coverage.ts`. It reports which tools are present
15
15
  * and any target that collides with a file the tool regenerates, so `vigiles
16
- * audit` can warn before the integrity guarantee is lost.
16
+ * lint` can warn before the integrity guarantee is lost.
17
17
  */
18
18
  /** A rule-sync tool vigiles should compose with rather than reimplement. */
19
19
  export type SyncToolName = "ruler" | "rulesync";
@@ -14,7 +14,7 @@
14
14
  * This detector is pure filesystem inspection — the same deterministic-detector
15
15
  * shape as `orphans.ts` / `test-coverage.ts`. It reports which tools are present
16
16
  * and any target that collides with a file the tool regenerates, so `vigiles
17
- * audit` can warn before the integrity guarantee is lost.
17
+ * lint` can warn before the integrity guarantee is lost.
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.detectSyncTools = detectSyncTools;
@@ -20,7 +20,7 @@ export interface GenerateSchemaOptions {
20
20
  basePath?: string;
21
21
  /**
22
22
  * Custom linters from `.vigilesrc.json` (`rulesDir`-backed). These aren't
23
- * auto-discovered by `generate-types`, but `vigiles audit` resolves their
23
+ * auto-discovered by `generate-types`, but `vigiles lint` resolves their
24
24
  * rules via `checkLinterRule`, so the schema enum must include them too —
25
25
  * otherwise the YAML LSP false-flags a rule that CI accepts.
26
26
  */
@@ -26,7 +26,7 @@ const generate_types_js_1 = require("./generate-types.js");
26
26
  /**
27
27
  * Rule references for config-declared custom linters. Mirrors
28
28
  * `checkLinterRule`'s rulesDir lookup (any file `<rule>.*` is a rule) so the
29
- * enum matches what `vigiles audit` accepts for these linters.
29
+ * enum matches what `vigiles lint` accepts for these linters.
30
30
  */
31
31
  function customRuleRefs(basePath, linters) {
32
32
  const refs = [];
@@ -84,7 +84,7 @@ function generateSchema(options = {}) {
84
84
  properties: {
85
85
  enforce: {
86
86
  type: "array",
87
- description: "Linter rules to enforce, verified by `vigiles audit`.",
87
+ description: "Linter rules to enforce, verified by `vigiles lint`.",
88
88
  items: {
89
89
  type: "object",
90
90
  additionalProperties: false,
@@ -103,12 +103,12 @@ function generateSchema(options = {}) {
103
103
  },
104
104
  files: {
105
105
  type: "array",
106
- description: "File paths referenced by this instruction file, verified to exist by `vigiles audit`.",
106
+ description: "File paths referenced by this instruction file, verified to exist by `vigiles lint`.",
107
107
  items: { type: "string" },
108
108
  },
109
109
  commands: {
110
110
  type: "array",
111
- description: "Commands (npm scripts / script-runner invocations) referenced here, verified by `vigiles audit`.",
111
+ description: "Commands (npm scripts / script-runner invocations) referenced here, verified by `vigiles lint`.",
112
112
  items: { type: "string" },
113
113
  },
114
114
  },
@@ -461,7 +461,7 @@ function isEslintPluginRule(ruleName, basePath) {
461
461
  /**
462
462
  * Enumerate all rules for a CLI-based linter so `tryCliCheck` can emit
463
463
  * closest-match suggestions on typos. Result is cached per (linter,
464
- * basePath) so each linter's discovery CLI runs at most once per audit.
464
+ * basePath) so each linter's discovery CLI runs at most once per lint.
465
465
  */
466
466
  const CLI_RULE_SET_CACHE = new Map();
467
467
  function getCliRuleSet(linterName, basePath) {
@@ -561,7 +561,7 @@ function getCliRuleSet(linterName, basePath) {
561
561
  // The `vigiles/<id>` namespace lets specs declare mechanical checks that
562
562
  // vigiles itself runs (orphan docs, integrity, etc.) without delegating to
563
563
  // an external linter. Existence is verified at compile time against this
564
- // fixed catalog; the actual check runs at audit time.
564
+ // fixed catalog; the actual check runs at lint time.
565
565
  // ---------------------------------------------------------------------------
566
566
  const VIGILES_INTERNAL_RULES = new Set(["orphan-docs"]);
567
567
  /** @internal */ function tryVigilesInternal(ctx) {
@@ -28,6 +28,13 @@ const DEFAULT_IGNORE = [
28
28
  ".vigiles/**",
29
29
  ".git/**",
30
30
  ];
31
+ /**
32
+ * A doc carrying this marker opts out of orphan detection — the inline escape
33
+ * hatch, mirroring `vigiles-disable require-spec` and `vigiles:ignore-test`.
34
+ * Use it for an intentionally-unreferenced doc (a changelog, a top-level index)
35
+ * that nothing else links to but is not rot.
36
+ */
37
+ const DISABLE_RE = /<!--\s*vigiles-disable\s+orphan-docs\s*-->/;
31
38
  // Match markdown links ](path.md) or ](path.md#anchor)
32
39
  const LINK_RE = /\]\(([^)\s]+\.md)(?:#[^)]*)?\)/g;
33
40
  // Match backtick code spans wrapping a path ending in .md
@@ -35,6 +42,27 @@ const BACKTICK_RE = /`([^`\s]+\.md)`/g;
35
42
  function normalizePath(p) {
36
43
  return p.replace(/^\.\//, "").replace(/\\/g, "/");
37
44
  }
45
+ /** True when a doc opts out of orphan detection via the inline disable marker. */
46
+ function isOrphanExempt(absPath) {
47
+ try {
48
+ return DISABLE_RE.test((0, node_fs_1.readFileSync)(absPath, "utf-8"));
49
+ }
50
+ catch {
51
+ return false; // unreadable — treat like any other doc
52
+ }
53
+ }
54
+ /** Discover docs under `include`, dropping any that carry the inline opt-out. */
55
+ function collectDocs(basePath, include, ignore) {
56
+ const docs = new Set();
57
+ for (const pattern of include) {
58
+ for (const p of (0, glob_1.globSync)(pattern, { cwd: basePath, ignore: [...ignore] })) {
59
+ if (isOrphanExempt((0, node_path_1.resolve)(basePath, p)))
60
+ continue;
61
+ docs.add(normalizePath(p));
62
+ }
63
+ }
64
+ return docs;
65
+ }
38
66
  function extractRefs(content) {
39
67
  const refs = [];
40
68
  for (const m of content.matchAll(LINK_RE))
@@ -43,6 +71,23 @@ function extractRefs(content) {
43
71
  refs.push(normalizePath(m[1]));
44
72
  return refs;
45
73
  }
74
+ /**
75
+ * Repo-root-relative targets a reference could mean, from a given source file.
76
+ * Markdown links are conventionally **file-relative** (a `[x](foo.md)` in
77
+ * `research/README.md` points at `research/foo.md`, and `../docs/x.md` walks up),
78
+ * but docs also write **root-relative** paths (`research/foo.md` from anywhere).
79
+ * We credit both so a real link is never miscounted as an orphan.
80
+ */
81
+ function refTargets(sourcePath, ref) {
82
+ const targets = new Set([ref]); // root-relative reading
83
+ const dir = sourcePath.includes("/")
84
+ ? sourcePath.slice(0, sourcePath.lastIndexOf("/"))
85
+ : "";
86
+ // file-relative reading: resolve against the source's directory.
87
+ const resolved = normalizePath(node_path_1.posix.normalize(dir ? `${dir}/${ref}` : ref));
88
+ targets.add(resolved);
89
+ return [...targets];
90
+ }
46
91
  // ---------------------------------------------------------------------------
47
92
  // Public API
48
93
  // ---------------------------------------------------------------------------
@@ -63,12 +108,7 @@ function findOrphanDocs(options = {}) {
63
108
  const include = options.include ?? DEFAULT_INCLUDE;
64
109
  const userExclude = options.exclude ?? [];
65
110
  const ignore = [...DEFAULT_IGNORE, ...userExclude];
66
- const allDocs = new Set();
67
- for (const pattern of include) {
68
- const found = (0, glob_1.globSync)(pattern, { cwd: basePath, ignore });
69
- for (const p of found)
70
- allDocs.add(normalizePath(p));
71
- }
111
+ const allDocs = collectDocs(basePath, include, ignore);
72
112
  const allMarkdown = (0, glob_1.globSync)("**/*.md", {
73
113
  cwd: basePath,
74
114
  ignore: [...DEFAULT_IGNORE],
@@ -83,15 +123,17 @@ function findOrphanDocs(options = {}) {
83
123
  catch {
84
124
  continue;
85
125
  }
86
- for (const target of extractRefs(content)) {
87
- if (target === source)
88
- continue;
89
- let sources = referencedBy.get(target);
90
- if (!sources) {
91
- sources = new Set();
92
- referencedBy.set(target, sources);
126
+ for (const rawRef of extractRefs(content)) {
127
+ for (const target of refTargets(source, rawRef)) {
128
+ if (target === source)
129
+ continue;
130
+ let sources = referencedBy.get(target);
131
+ if (!sources) {
132
+ sources = new Set();
133
+ referencedBy.set(target, sources);
134
+ }
135
+ sources.add(source);
93
136
  }
94
- sources.add(source);
95
137
  }
96
138
  }
97
139
  const orphans = [];
@@ -119,6 +161,7 @@ function formatOrphanReport(report) {
119
161
  ];
120
162
  for (const o of report.orphans)
121
163
  lines.push(` ${o}`);
164
+ lines.push(" Fix: link each from another .md (README, a spec's Key Files, or a doc).", " To silence: add `<!-- vigiles-disable orphan-docs -->` to the doc, or", " exclude it via .vigilesrc.json → `orphans.exclude` (or narrow `orphans.include`).");
122
165
  return lines.join("\n");
123
166
  }
124
167
  //# sourceMappingURL=orphans.js.map
@@ -188,7 +188,7 @@ function findSimilarRules(rules, threshold = 0.5) {
188
188
  *
189
189
  * Throws a structured error on unknown rule kinds so that the caller (e.g.
190
190
  * runProofSuite) can surface a clear proof failure rather than letting an
191
- * `undefined` propagate into compressedSize and crash the audit.
191
+ * `undefined` propagate into compressedSize and crash the computation.
192
192
  */
193
193
  function ruleToText(rule) {
194
194
  switch (rule._kind) {
@@ -29,7 +29,7 @@ export declare function symbolRefs(markdown: string): SymbolRef[];
29
29
  export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[];
30
30
  /**
31
31
  * Whether a span is a **linter-rule reference** that ought to be marked
32
- * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
32
+ * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the lint can verify the
33
33
  * rule exists AND is enabled. High-signal only: a slash-scoped name with no file
34
34
  * extension. A function-call form `` `foo(args)` `` is reduced to its callee.
35
35
  *
package/dist/core/refs.js CHANGED
@@ -109,7 +109,7 @@ const IGNORE_FILE = /<!--\s*vigiles:ignore-file\s*-->/;
109
109
  const IGNORE_LINE = /<!--\s*vigiles:ignore\s*-->/;
110
110
  /**
111
111
  * Whether a span is a **linter-rule reference** that ought to be marked
112
- * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
112
+ * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the lint can verify the
113
113
  * rule exists AND is enabled. High-signal only: a slash-scoped name with no file
114
114
  * extension. A function-call form `` `foo(args)` `` is reduced to its callee.
115
115
  *
@@ -152,7 +152,7 @@ function collectRefIssues(markdown, basePath) {
152
152
  for (const u of unmarkedCodeRefs(markdown)) {
153
153
  out.push(`line ${String(u.line)}: \`${u.text}\` is an unmarked linter-rule ` +
154
154
  `reference — mark it as \`enforce("${u.text}")\` (typed spec) or ` +
155
- `\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so audit can verify ` +
155
+ `\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so lint can verify ` +
156
156
  `it exists and is enabled, or add <!-- vigiles:ignore --> if it is prose`);
157
157
  }
158
158
  return out;
@@ -4,7 +4,7 @@
4
4
  * Used by the post-session audit to know which targets exist and which
5
5
  * spec source / inputs each one tracks. The compile pipeline writes
6
6
  * these whenever a spec is built; readers (currently only session.ts)
7
- * consume them at audit time.
7
+ * consume them at verification time.
8
8
  *
9
9
  * This module is the ONLY place sidecars live now — the freshness rule
10
10
  * doesn't depend on them anymore.
@@ -5,7 +5,7 @@
5
5
  * Used by the post-session audit to know which targets exist and which
6
6
  * spec source / inputs each one tracks. The compile pipeline writes
7
7
  * these whenever a spec is built; readers (currently only session.ts)
8
- * consume them at audit time.
8
+ * consume them at verification time.
9
9
  *
10
10
  * This module is the ONLY place sidecars live now — the freshness rule
11
11
  * doesn't depend on them anymore.
@@ -156,7 +156,7 @@ export declare function cmd(command: NoInfer<StrictCmd>): CmdRef;
156
156
  * Reference a symbol defined in a file — verified at compile time that the
157
157
  * named file exists AND defines the named symbol (via ast-grep, cross-language).
158
158
  * Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
159
- * `audit` / `refs-hook` re-verify the same reference.
159
+ * `lint` / `refs-hook` re-verify the same reference.
160
160
  */
161
161
  export declare function symbol(file: NoInfer<StrictFile>, name: string): SymbolRef;
162
162
  /**
package/dist/core/spec.js CHANGED
@@ -93,7 +93,7 @@ function cmd(command) {
93
93
  * Reference a symbol defined in a file — verified at compile time that the
94
94
  * named file exists AND defines the named symbol (via ast-grep, cross-language).
95
95
  * Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
96
- * `audit` / `refs-hook` re-verify the same reference.
96
+ * `lint` / `refs-hook` re-verify the same reference.
97
97
  */
98
98
  function symbol(file, name) {
99
99
  return { _ref: "symbol", file: file, symbol: name };
@@ -97,7 +97,7 @@ export interface RulesConfig {
97
97
  "untested-surface"?: RuleWithOptions<TestCoverageConfig>;
98
98
  /**
99
99
  * Nudge (or block) when an instruction file has code-shaped references that
100
- * aren't expressed as vigiles marks (so the audit can't verify them), or a
100
+ * aren't expressed as vigiles marks (so the lint can't verify them), or a
101
101
  * `vigiles:symbol` mark that points at a missing symbol. Drives the
102
102
  * PostToolUse refs-hook: "warn" (default) → a non-blocking nudge, "error" →
103
103
  * block the edit, false → off.