vigiles 12.8.0 → 13.0.0

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
@@ -1011,23 +1011,42 @@ async function runLint(restArgs, flags, config) {
1011
1011
  }
1012
1012
  // 6. Coverage thresholds (gates CI when severity is "error")
1013
1013
  const coverageErrors = await checkCoverageThresholds(coverage, config, silent);
1014
- // 7. Orphan docs check — find .md files no other markdown references.
1015
- // Enforces the `vigiles/orphan-docs` built-in rule when declared in a
1016
- // spec. Include/exclude come from .vigilesrc.json#orphans (tsconfig-
1017
- // style globs); default include is docs/ + research/ for the
1018
- // vigiles-repo convention.
1014
+ // 7. Orphan docs check — OPT-IN (the `orphans` block in .vigilesrc.json is
1015
+ // the on-switch). "Unreferenced" only means "rot" for a hand-cross-linked
1016
+ // corpus; on a nav-managed doc site (Docusaurus/MkDocs) the page graph lives
1017
+ // in config, not inline links, so an unconditional scan is ~all false
1018
+ // positives there (an OSS sweep confirmed it). So we scan only when the repo
1019
+ // declares the block; its `include` defaults to docs/ (research/ etc. are
1020
+ // opted into explicitly). `enforce("vigiles/orphan-docs")` in a spec only
1021
+ // validates the rule NAME — the block is what drives the scan.
1022
+ const orphansCfg = config?.orphans;
1019
1023
  if (!silent)
1020
1024
  console.log("\nOrphan docs check:\n");
1021
- const orphanReport = (0, orphans_js_1.findOrphanDocs)({
1022
- basePath: process.cwd(),
1023
- include: config?.orphans?.include,
1024
- exclude: config?.orphans?.exclude,
1025
- });
1026
- if (!silent) {
1027
- for (const line of (0, orphans_js_1.formatOrphanReport)(orphanReport).split("\n")) {
1028
- console.log(` ${line}`);
1025
+ let orphanReport = {
1026
+ include: [],
1027
+ totalDocs: 0,
1028
+ referencedDocs: [],
1029
+ orphans: [],
1030
+ };
1031
+ if (orphansCfg) {
1032
+ orphanReport = (0, orphans_js_1.findOrphanDocs)({
1033
+ basePath: process.cwd(),
1034
+ include: orphansCfg.include,
1035
+ exclude: orphansCfg.exclude,
1036
+ // Exempt every registered harness's surface files (instruction file,
1037
+ // SKILL.md, subagents, commands) as orphan candidates — layout-driven so
1038
+ // core carries no harness literal (see src/core/orphans.ts).
1039
+ layouts: adapter_registry_js_1.ADAPTERS.map((a) => a.layout),
1040
+ });
1041
+ if (!silent) {
1042
+ for (const line of (0, orphans_js_1.formatOrphanReport)(orphanReport).split("\n")) {
1043
+ console.log(` ${line}`);
1044
+ }
1029
1045
  }
1030
1046
  }
1047
+ else if (!silent) {
1048
+ console.log(" ⊘ not enabled — add an `orphans` block to .vigilesrc.json to opt in (scans docs/ by default)");
1049
+ }
1031
1050
  // 7b. Untested-surface check — skills/agents/hooks shipping without a test or
1032
1051
  // eval. Warning by default (a nudge, exit 0); set rules.untested-{skill,agent,
1033
1052
  // hook} to "error" to gate CI. See src/test-coverage.ts and docs/rules/.
@@ -11,6 +11,7 @@
11
11
  * references (markdown links and backtick paths). Works against source
12
12
  * README plus compiled CLAUDE.md — no spec loading required.
13
13
  */
14
+ import type { PluginLayout } from "./layout.js";
14
15
  export interface OrphanReport {
15
16
  /** Include globs that were scanned. */
16
17
  readonly include: readonly string[];
@@ -25,14 +26,22 @@ export interface FindOrphansOptions {
25
26
  /** Repository root. Defaults to `process.cwd()`. */
26
27
  readonly basePath?: string;
27
28
  /**
28
- * Glob patterns of `.md` files to scan. Defaults to
29
- * `["docs/**\/*.md", "research/**\/*.md"]` vigiles-repo convention.
30
- * Set to `[]` to disable scanning entirely. Set to your project's
31
- * doc directory globs (e.g. `["wiki/**\/*.md"]`) to override.
29
+ * Glob patterns of `.md` files to scan. Defaults to `["docs/**\/*.md"]`
30
+ * (`docs/` is the near-universal convention; a vigiles-specific dir like
31
+ * `research/` is opted into explicitly). Set to `[]` to disable scanning,
32
+ * or to your project's doc globs (e.g. `["wiki/**\/*.md"]`) to override.
32
33
  */
33
34
  readonly include?: readonly string[];
34
35
  /** Glob patterns to exclude within the include scope. */
35
36
  readonly exclude?: readonly string[];
37
+ /**
38
+ * Harnesses whose surface files (instruction file, `SKILL.md`, subagents,
39
+ * commands) are load-bearing by location and thus never orphan CANDIDATES
40
+ * (still counted as referencers). Injected by the CLI from the registered
41
+ * adapters so core stays harness-agnostic; a direct caller passes its own.
42
+ * Omitted ⇒ only the universal `SKILL.md` convention is exempt.
43
+ */
44
+ readonly layouts?: readonly PluginLayout[];
36
45
  }
37
46
  /**
38
47
  * Find docs under `include` globs that no other markdown file references.
@@ -43,8 +52,9 @@ export interface FindOrphansOptions {
43
52
  * links to itself is still an orphan.
44
53
  *
45
54
  * `include` and `exclude` are tsconfig-style glob arrays. Default include
46
- * is the vigiles-repo convention `["docs/**\/*.md", "research/**\/*.md"]`;
47
- * override per-project via `.vigilesrc.json` → `orphans.include`.
55
+ * is `["docs/**\/*.md"]` (the common convention); override per-project via
56
+ * `.vigilesrc.json` → `orphans.include` (whose presence also opts the repo
57
+ * into the scan — see the CLI gate in `vigiles lint`).
48
58
  */
49
59
  export declare function findOrphanDocs(options?: FindOrphansOptions): OrphanReport;
50
60
  /** Format an orphan report as human-readable text. */
@@ -21,7 +21,7 @@ const glob_1 = require("glob");
21
21
  // ---------------------------------------------------------------------------
22
22
  // Internals
23
23
  // ---------------------------------------------------------------------------
24
- const DEFAULT_INCLUDE = ["docs/**/*.md", "research/**/*.md"];
24
+ const DEFAULT_INCLUDE = ["docs/**/*.md"];
25
25
  const DEFAULT_IGNORE = [
26
26
  "node_modules/**",
27
27
  "dist/**",
@@ -35,24 +35,48 @@ const DEFAULT_IGNORE = [
35
35
  * that nothing else links to but is not rot.
36
36
  */
37
37
  const DISABLE_RE = /<!--\s*vigiles-disable\s+orphan-docs\s*-->/;
38
+ /** The one universal cross-harness skill-entry filename (CC + Codex). */
39
+ const SKILL_FILE = "SKILL.md";
38
40
  /**
39
- * Files the HARNESS loads directly — an instruction file (`CLAUDE.md` /
40
- * `AGENTS.md`), a skill (`SKILL.md`), a subagent (`agents/*.md`), or a slash
41
- * command (`commands/*.md`) are load-bearing by their NAME/LOCATION, not
42
- * because another `.md` links to them. They are categorically NOT docs, so they
43
- * are never orphans, even if a project broadens `orphans.include` to scan the
44
- * whole repo. (They are still scanned as REFERENCERS, so a real doc that only
45
- * a CLAUDE.md links to is still credited — this exemption only removes them from
46
- * the orphan-CANDIDATE set.)
41
+ * Files the HARNESS loads directly — its instruction file
42
+ * (`layout.instructionFile`, e.g. `CLAUDE.md` / `AGENTS.md`), a skill
43
+ * (`SKILL.md`), a subagent (`<agentDir>/*.md`), or a slash command
44
+ * (`<commandDir>/*.md`) are load-bearing by their NAME/LOCATION, not because
45
+ * another `.md` links to them. They are categorically NOT docs, so they are
46
+ * never orphans even when `orphans.include` broadens to the whole repo.
47
+ *
48
+ * The surface names come from the INJECTED layouts, so core stays
49
+ * harness-agnostic — no Claude Code literal here; the CLI passes every
50
+ * registered adapter's layout, and `SKILL.md` is the one universal convention.
51
+ * (They are still scanned as REFERENCERS, so a real doc that only a `CLAUDE.md`
52
+ * links to is still credited — this exemption only removes them from the
53
+ * orphan-CANDIDATE set.)
47
54
  */
48
- function isHarnessLoadedFile(path) {
55
+ function isHarnessLoadedFile(path, layouts) {
49
56
  const norm = normalizePath(path);
50
57
  const base = norm.slice(norm.lastIndexOf("/") + 1);
51
- if (base === "CLAUDE.md" || base === "AGENTS.md" || base === "SKILL.md") {
58
+ if (base === SKILL_FILE)
52
59
  return true;
60
+ for (const layout of layouts) {
61
+ if (base === layout.instructionFile)
62
+ return true;
63
+ // Subagent / slash-command surfaces live at a REAL surface root — the repo
64
+ // root, the user-surface root (e.g. `.claude/`), or the materialize root —
65
+ // NOT any nested dir that merely shares the name. A doc under `docs/prompts/`
66
+ // is documentation, not Codex's `prompts` command surface.
67
+ const roots = [
68
+ "",
69
+ ...[layout.userSurfaceRoot, layout.materializeRoot]
70
+ .filter((r) => !!r)
71
+ .map((r) => `${r}/`),
72
+ ];
73
+ for (const dir of [layout.agentDir, layout.commandDir]) {
74
+ if (dir && roots.some((r) => norm.startsWith(`${r}${dir}/`))) {
75
+ return true;
76
+ }
77
+ }
53
78
  }
54
- // Subagent / slash-command surfaces the harness enumerates by directory.
55
- return /(^|\/)(agents|commands)\//.test(norm);
79
+ return false;
56
80
  }
57
81
  // Match markdown links ](path.md) or ](path.md#anchor)
58
82
  const LINK_RE = /\]\(([^)\s]+\.md)(?:#[^)]*)?\)/g;
@@ -71,12 +95,12 @@ function isOrphanExempt(absPath) {
71
95
  }
72
96
  }
73
97
  /** Discover docs under `include`, dropping any that carry the inline opt-out. */
74
- function collectDocs(basePath, include, ignore) {
98
+ function collectDocs(basePath, include, ignore, layouts) {
75
99
  const docs = new Set();
76
100
  for (const pattern of include) {
77
101
  for (const p of (0, glob_1.globSync)(pattern, { cwd: basePath, ignore: [...ignore] })) {
78
- if (isHarnessLoadedFile(p))
79
- continue; // instruction files are never orphans
102
+ if (isHarnessLoadedFile(p, layouts))
103
+ continue; // harness files are never orphans
80
104
  if (isOrphanExempt((0, node_path_1.resolve)(basePath, p)))
81
105
  continue;
82
106
  docs.add(normalizePath(p));
@@ -121,15 +145,17 @@ function refTargets(sourcePath, ref) {
121
145
  * links to itself is still an orphan.
122
146
  *
123
147
  * `include` and `exclude` are tsconfig-style glob arrays. Default include
124
- * is the vigiles-repo convention `["docs/**\/*.md", "research/**\/*.md"]`;
125
- * override per-project via `.vigilesrc.json` → `orphans.include`.
148
+ * is `["docs/**\/*.md"]` (the common convention); override per-project via
149
+ * `.vigilesrc.json` → `orphans.include` (whose presence also opts the repo
150
+ * into the scan — see the CLI gate in `vigiles lint`).
126
151
  */
127
152
  function findOrphanDocs(options = {}) {
128
153
  const basePath = options.basePath ?? process.cwd();
129
154
  const include = options.include ?? DEFAULT_INCLUDE;
130
155
  const userExclude = options.exclude ?? [];
131
156
  const ignore = [...DEFAULT_IGNORE, ...userExclude];
132
- const allDocs = collectDocs(basePath, include, ignore);
157
+ const layouts = options.layouts ?? [];
158
+ const allDocs = collectDocs(basePath, include, ignore, layouts);
133
159
  const allMarkdown = (0, glob_1.globSync)("**/*.md", {
134
160
  cwd: basePath,
135
161
  ignore: [...DEFAULT_IGNORE],
@@ -256,10 +256,10 @@ exports.RULE_META = {
256
256
  // --- Docs hygiene ---------------------------------------------------------
257
257
  "orphan-docs": {
258
258
  id: "orphan-docs",
259
- bucket: "external-decidable",
259
+ bucket: "heuristic-behavioral",
260
260
  surface: ["docs"],
261
261
  defaultSeverity: "warn",
262
- summary: "Every docs/ + research/ .md is referenced by another .md.",
262
+ summary: "Opt-in: a doc in a configured dir (default docs/) that no other .md references.",
263
263
  detector: "findOrphanDocs",
264
264
  },
265
265
  };
@@ -54,13 +54,19 @@ export interface CoverageThresholds {
54
54
  /** Min % of npm scripts documented in spec commands. */
55
55
  scripts?: number;
56
56
  }
57
- /** Options for the orphan-docs check. */
57
+ /**
58
+ * Options for the orphan-docs check. The PRESENCE of this block in
59
+ * `.vigilesrc.json` OPTS THE REPO IN — the scan is off unless declared,
60
+ * because "unreferenced" only means "rot" for a hand-cross-linked corpus,
61
+ * not for a nav-managed doc site (Docusaurus/MkDocs) where the page graph
62
+ * lives in config. `include` is the optional dir override.
63
+ */
58
64
  export interface OrphansConfig {
59
65
  /**
60
66
  * Glob patterns of `.md` files to scan for orphans. A doc is "orphaned"
61
- * when no other markdown file references it. Defaults to vigiles-repo
62
- * convention: `["docs/**\/*.md", "research/**\/*.md"]`. Set to `[]` to
63
- * disable orphan detection entirely.
67
+ * when no other markdown file references it. Omitted `["docs/**\/*.md"]`
68
+ * (the common convention); add your own dirs (e.g. a `research/` notes
69
+ * tree) explicitly. Set to `[]` to opt in but scan nothing.
64
70
  */
65
71
  include?: readonly string[];
66
72
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "12.8.0",
3
+ "version": "13.0.0",
4
4
  "description": "Lint & test the harness your AI agent runs on — verify the references in your CLAUDE.md / AGENTS.md and test that your hooks and skills actually work.",
5
5
  "keywords": [
6
6
  "claude-code",