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 +32 -13
- package/dist/core/orphans.d.ts +16 -6
- package/dist/core/orphans.js +45 -19
- package/dist/core/rule-meta.js +2 -2
- package/dist/core/types.d.ts +10 -4
- package/package.json +1 -1
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 —
|
|
1015
|
-
//
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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/.
|
package/dist/core/orphans.d.ts
CHANGED
|
@@ -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
|
-
* `
|
|
30
|
-
* Set to `[]` to disable scanning
|
|
31
|
-
* doc
|
|
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
|
|
47
|
-
*
|
|
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. */
|
package/dist/core/orphans.js
CHANGED
|
@@ -21,7 +21,7 @@ const glob_1 = require("glob");
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// Internals
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
|
-
const DEFAULT_INCLUDE = ["docs/**/*.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 —
|
|
40
|
-
* `
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* the
|
|
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 ===
|
|
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
|
-
|
|
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; //
|
|
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
|
|
125
|
-
*
|
|
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
|
|
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],
|
package/dist/core/rule-meta.js
CHANGED
|
@@ -256,10 +256,10 @@ exports.RULE_META = {
|
|
|
256
256
|
// --- Docs hygiene ---------------------------------------------------------
|
|
257
257
|
"orphan-docs": {
|
|
258
258
|
id: "orphan-docs",
|
|
259
|
-
bucket: "
|
|
259
|
+
bucket: "heuristic-behavioral",
|
|
260
260
|
surface: ["docs"],
|
|
261
261
|
defaultSeverity: "warn",
|
|
262
|
-
summary: "
|
|
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
|
};
|
package/dist/core/types.d.ts
CHANGED
|
@@ -54,13 +54,19 @@ export interface CoverageThresholds {
|
|
|
54
54
|
/** Min % of npm scripts documented in spec commands. */
|
|
55
55
|
scripts?: number;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
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.
|
|
62
|
-
* convention
|
|
63
|
-
*
|
|
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": "
|
|
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",
|