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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/adapter-conformance.js +1 -1
- package/dist/check.d.ts +132 -0
- package/dist/check.js +318 -0
- package/dist/cli.js +130 -52
- package/dist/core/compile.d.ts +1 -1
- package/dist/core/compile.js +1 -1
- package/dist/core/compose.d.ts +1 -1
- package/dist/core/compose.js +1 -1
- package/dist/core/generate-schema.d.ts +1 -1
- package/dist/core/generate-schema.js +4 -4
- package/dist/core/linters.js +2 -2
- package/dist/core/orphans.js +57 -14
- package/dist/core/proofs.js +1 -1
- package/dist/core/refs.d.ts +1 -1
- package/dist/core/refs.js +2 -2
- package/dist/core/sidecar.d.ts +1 -1
- package/dist/core/sidecar.js +1 -1
- package/dist/core/spec.d.ts +1 -1
- package/dist/core/spec.js +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/validate.js +2 -2
- package/dist/e2e.d.ts +10 -13
- package/dist/e2e.js +10 -17
- package/dist/eval.d.ts +217 -2
- package/dist/eval.js +428 -18
- package/dist/harness-assert.d.ts +3 -0
- package/dist/harness-assert.js +16 -0
- package/dist/harness-test.d.ts +46 -0
- package/dist/harness-test.js +102 -0
- package/dist/integration.d.ts +8 -0
- package/dist/integration.js +10 -0
- package/dist/jest.d.ts +3 -1
- package/dist/jest.js +3 -2
- package/dist/run-hook.d.ts +22 -0
- package/dist/run-hook.js +28 -0
- package/dist/scan.d.ts +1 -1
- package/dist/scan.js +1 -1
- package/dist/setup-plan.d.ts +5 -1
- package/dist/setup-plan.js +11 -1
- package/dist/test-coverage.js +8 -1
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +7 -0
- package/dist/unit.d.ts +4 -2
- package/dist/unit.js +7 -1
- package/dist/vitest.d.mts +3 -1
- package/hooks/refs-nudge.sh +1 -1
- package/package.json +3 -2
- package/skills/edit-spec/SKILL.md +21 -10
- package/skills/linter-docs/SKILL.md +23 -0
- package/skills/migrate-to-spec/SKILL.md +1 -1
- package/skills/strengthen/SKILL.md +1 -2
- 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/
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
889
|
-
function
|
|
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
|
-
|
|
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 (`
|
|
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
|
|
1140
|
-
* bare-`npx vigiles` API
|
|
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
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2307
|
-
|
|
2308
|
-
const exitCode =
|
|
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
|
}
|
package/dist/core/compile.d.ts
CHANGED
|
@@ -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 `
|
|
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;
|
package/dist/core/compile.js
CHANGED
|
@@ -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 `
|
|
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();
|
package/dist/core/compose.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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";
|
package/dist/core/compose.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
111
|
+
description: "Commands (npm scripts / script-runner invocations) referenced here, verified by `vigiles lint`.",
|
|
112
112
|
items: { type: "string" },
|
|
113
113
|
},
|
|
114
114
|
},
|
package/dist/core/linters.js
CHANGED
|
@@ -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
|
|
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
|
|
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) {
|
package/dist/core/orphans.js
CHANGED
|
@@ -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 =
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
package/dist/core/proofs.js
CHANGED
|
@@ -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
|
|
191
|
+
* `undefined` propagate into compressedSize and crash the computation.
|
|
192
192
|
*/
|
|
193
193
|
function ruleToText(rule) {
|
|
194
194
|
switch (rule._kind) {
|
package/dist/core/refs.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/dist/core/sidecar.d.ts
CHANGED
|
@@ -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
|
|
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.
|
package/dist/core/sidecar.js
CHANGED
|
@@ -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
|
|
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.
|
package/dist/core/spec.d.ts
CHANGED
|
@@ -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
|
-
* `
|
|
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
|
-
* `
|
|
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 };
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
|
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.
|