vigiles 3.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +35 -22
- package/action.yml +4 -4
- package/dist/adapters/claude-code/adapter.js +1 -1
- package/dist/claude-code.d.ts +1 -1
- package/dist/claude-code.js +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +387 -162
- package/dist/e2e.d.ts +1 -1
- package/dist/e2e.js +1 -1
- package/dist/{adapters/claude-code/eval-cache.d.ts → eval-cache.d.ts} +1 -1
- package/dist/{adapters/claude-code/eval-cache.js → eval-cache.js} +1 -1
- package/dist/{adapters/claude-code/eval.js → eval.js} +2 -2
- package/dist/harness-assert.d.ts +8 -8
- package/dist/harness-assert.js +5 -5
- package/dist/{adapters/claude-code/harness-test.d.ts → harness-test.d.ts} +4 -4
- package/dist/{adapters/claude-code/harness-test.js → harness-test.js} +4 -4
- package/dist/integration.d.ts +3 -3
- package/dist/integration.js +2 -3
- package/dist/{adapters/claude-code/mock-model.d.ts → mock-model.d.ts} +2 -2
- package/dist/{adapters/claude-code/run-hook.d.ts → run-hook.d.ts} +1 -1
- package/dist/{adapters/claude-code/run-hook.js → run-hook.js} +1 -1
- package/dist/{adapters/claude-code/sandbox.js → sandbox.js} +1 -1
- package/dist/setup-plan.d.ts +47 -7
- package/dist/setup-plan.js +104 -29
- package/dist/testing.d.ts +8 -3
- package/dist/testing.js +8 -3
- package/dist/unit.d.ts +2 -2
- package/dist/unit.js +1 -1
- package/hooks/session-start.sh +3 -3
- package/package.json +6 -6
- package/skills/audit-feedback-loop/SKILL.md +0 -76
- package/skills/enforce-rules-format/SKILL.md +0 -71
- package/skills/generate-logo/SKILL.md +0 -103
- package/skills/pr-to-lint-rule/SKILL.md +0 -97
- /package/dist/{adapters/claude-code/egress-entry.d.ts → egress-entry.d.ts} +0 -0
- /package/dist/{adapters/claude-code/egress-entry.js → egress-entry.js} +0 -0
- /package/dist/{adapters/claude-code/egress-proxy.d.ts → egress-proxy.d.ts} +0 -0
- /package/dist/{adapters/claude-code/egress-proxy.js → egress-proxy.js} +0 -0
- /package/dist/{adapters/claude-code/egress.d.ts → egress.d.ts} +0 -0
- /package/dist/{adapters/claude-code/egress.js → egress.js} +0 -0
- /package/dist/{adapters/claude-code/eval-baseline.d.ts → eval-baseline.d.ts} +0 -0
- /package/dist/{adapters/claude-code/eval-baseline.js → eval-baseline.js} +0 -0
- /package/dist/{adapters/claude-code/eval.d.ts → eval.d.ts} +0 -0
- /package/dist/{adapters/claude-code/judge.d.ts → judge.d.ts} +0 -0
- /package/dist/{adapters/claude-code/judge.js → judge.js} +0 -0
- /package/dist/{adapters/claude-code/mock-entry.d.ts → mock-entry.d.ts} +0 -0
- /package/dist/{adapters/claude-code/mock-entry.js → mock-entry.js} +0 -0
- /package/dist/{adapters/claude-code/mock-model.js → mock-model.js} +0 -0
- /package/dist/{adapters/claude-code/sandbox.d.ts → sandbox.d.ts} +0 -0
- /package/dist/{adapters/claude-code/stats.d.ts → stats.d.ts} +0 -0
- /package/dist/{adapters/claude-code/stats.js → stats.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Commands:
|
|
7
7
|
* vigiles init — scaffold a spec from scratch
|
|
8
8
|
* vigiles compile — compile .spec.ts → .md with linter verification
|
|
9
|
-
* vigiles
|
|
9
|
+
* vigiles lint — verify hashes, report coverage, detect duplicates
|
|
10
10
|
* vigiles generate-types — emit .d.ts with types from project state
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -28,6 +28,7 @@ const proofs_js_1 = require("./core/proofs.js");
|
|
|
28
28
|
const inline_js_1 = require("./core/inline.js");
|
|
29
29
|
const frontmatter_js_1 = require("./core/frontmatter.js");
|
|
30
30
|
const generate_schema_js_1 = require("./core/generate-schema.js");
|
|
31
|
+
const compose_js_1 = require("./core/compose.js");
|
|
31
32
|
const compile_generator_js_1 = require("./core/compile-generator.js");
|
|
32
33
|
const action_gate_js_1 = require("./action-gate.js");
|
|
33
34
|
const agent_runtime_js_1 = require("./adapters/claude-code/agent-runtime.js");
|
|
@@ -35,7 +36,7 @@ const refs_js_1 = require("./core/refs.js");
|
|
|
35
36
|
const mcp_js_1 = require("./core/mcp.js");
|
|
36
37
|
const skill_runtime_js_1 = require("./adapters/claude-code/skill-runtime.js");
|
|
37
38
|
const linters_js_1 = require("./core/linters.js");
|
|
38
|
-
const harness_test_js_1 = require("./
|
|
39
|
+
const harness_test_js_1 = require("./harness-test.js");
|
|
39
40
|
const run_scripts_js_1 = require("./adapters/claude-code/run-scripts.js");
|
|
40
41
|
const integrity_js_1 = require("./core/integrity.js");
|
|
41
42
|
const coverage_js_1 = require("./core/coverage.js");
|
|
@@ -51,7 +52,11 @@ const IGNORE_NODE_MODULES = ["node_modules/**"];
|
|
|
51
52
|
function findSpecs(pattern) {
|
|
52
53
|
const glob = pattern ?? "**/*.md.spec.ts";
|
|
53
54
|
return (0, glob_1.globSync)(glob, {
|
|
54
|
-
|
|
55
|
+
// `dot: true` so specs that live in a sync tool's source slot (e.g.
|
|
56
|
+
// `.ruler/AGENTS.md.spec.ts`, the redirect target) are discovered by
|
|
57
|
+
// compile/audit/the recompile hook — not just root-level specs.
|
|
58
|
+
dot: true,
|
|
59
|
+
ignore: [...IGNORE_NODE_MODULES, "dist/**", ".git/**"],
|
|
55
60
|
cwd: process.cwd(),
|
|
56
61
|
});
|
|
57
62
|
}
|
|
@@ -400,7 +405,7 @@ async function findDuplicateRules(threshold = 0.3, silent = false, scopeFiles) {
|
|
|
400
405
|
const allSpecs = findSpecs();
|
|
401
406
|
// If audit was invoked with explicit file arguments, only scan the specs
|
|
402
407
|
// for those files — otherwise an unrelated duplicate elsewhere in the
|
|
403
|
-
// repo would fail a targeted CI check (e.g. `vigiles
|
|
408
|
+
// repo would fail a targeted CI check (e.g. `vigiles lint path/foo.md`).
|
|
404
409
|
//
|
|
405
410
|
// Resolve each requested file to its real source spec by reading the
|
|
406
411
|
// compiled-from header. Multi-target projects compile one spec to
|
|
@@ -989,6 +994,34 @@ function discover(silent = false) {
|
|
|
989
994
|
}
|
|
990
995
|
return { enabled: totalEnabled, documented: totalDocumented };
|
|
991
996
|
}
|
|
997
|
+
/** This package's own version (from the installed package.json). */
|
|
998
|
+
function getVersion() {
|
|
999
|
+
try {
|
|
1000
|
+
// dist/cli.js → ../package.json (the package root).
|
|
1001
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(__dirname, "..", "package.json"), "utf-8"));
|
|
1002
|
+
return pkg.version ?? "unknown";
|
|
1003
|
+
}
|
|
1004
|
+
catch {
|
|
1005
|
+
return "unknown";
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
/** The dependency range to pin `vigiles` to (the running CLI's major, e.g.
|
|
1009
|
+
* `^3`). Falls back to `latest` for the unreleased dev placeholder version. */
|
|
1010
|
+
function vigilesDepSpec() {
|
|
1011
|
+
const major = parseInt(getVersion(), 10);
|
|
1012
|
+
return Number.isFinite(major) && major > 0 ? `^${String(major)}` : "latest";
|
|
1013
|
+
}
|
|
1014
|
+
/** True when the file's first line carries a vigiles integrity hash (i.e. it
|
|
1015
|
+
* is a compiled artifact we own, safe to overwrite — not hand-written prose). */
|
|
1016
|
+
function targetHasHash(absPath) {
|
|
1017
|
+
try {
|
|
1018
|
+
const first = (0, node_fs_1.readFileSync)(absPath, "utf-8").split("\n", 1)[0];
|
|
1019
|
+
return first.includes("vigiles:sha256");
|
|
1020
|
+
}
|
|
1021
|
+
catch {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
992
1025
|
function init(args) {
|
|
993
1026
|
const targetFlag = args.find((a) => a.startsWith("--target="));
|
|
994
1027
|
const target = targetFlag ? targetFlag.split("=")[1] : "CLAUDE.md";
|
|
@@ -997,7 +1030,12 @@ function init(args) {
|
|
|
997
1030
|
console.log(`${specPath} already exists.`);
|
|
998
1031
|
return;
|
|
999
1032
|
}
|
|
1000
|
-
|
|
1033
|
+
// The compiled output is derived from the spec FILE path; the spec's `target`
|
|
1034
|
+
// field is the h1 + the name the compiler validates against, so it must be the
|
|
1035
|
+
// bare filename even when the spec lives in a subdir (e.g. a sync tool's
|
|
1036
|
+
// `.ruler/AGENTS.md.spec.ts` source slot → target "AGENTS.md").
|
|
1037
|
+
const targetName = (0, node_path_1.basename)(target);
|
|
1038
|
+
const targetLine = targetName !== "CLAUDE.md" ? `\n target: "${targetName}",` : "";
|
|
1001
1039
|
const template = `import { claude, enforce, guidance } from "vigiles/spec";
|
|
1002
1040
|
|
|
1003
1041
|
export default claude({${targetLine}
|
|
@@ -1031,19 +1069,22 @@ export default claude({${targetLine}
|
|
|
1031
1069
|
},
|
|
1032
1070
|
});
|
|
1033
1071
|
`;
|
|
1034
|
-
|
|
1072
|
+
const specAbs = (0, node_path_1.resolve)(process.cwd(), specPath);
|
|
1073
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(specAbs), { recursive: true });
|
|
1074
|
+
(0, node_fs_1.writeFileSync)(specAbs, template);
|
|
1035
1075
|
console.log(`Created ${specPath} — edit it and run \`vigiles compile\`.`);
|
|
1036
1076
|
}
|
|
1037
1077
|
// ---------------------------------------------------------------------------
|
|
1038
1078
|
// Setup wizard
|
|
1039
1079
|
// ---------------------------------------------------------------------------
|
|
1040
1080
|
/** Full GitHub Actions workflow that wires the production `zernie/vigiles@v1`
|
|
1041
|
-
* Action (
|
|
1081
|
+
* Action (lint pillar) and, when the test pillar is set up, a deterministic
|
|
1082
|
+
* harness job. */
|
|
1042
1083
|
function vigilesWorkflow(plan) {
|
|
1043
1084
|
const harness = plan.test
|
|
1044
1085
|
? `
|
|
1045
1086
|
harness:
|
|
1046
|
-
#
|
|
1087
|
+
# Test pillar — run your *.harness.{mjs,ts} tests against the real agent CLI and
|
|
1047
1088
|
# a scripted mock model (deterministic, no API key). Drop this job if you only
|
|
1048
1089
|
# author runHook unit tests, or keep it for the deterministic tier.
|
|
1049
1090
|
runs-on: ubuntu-latest
|
|
@@ -1068,9 +1109,9 @@ permissions:
|
|
|
1068
1109
|
pull-requests: write # for the sticky PR comment
|
|
1069
1110
|
|
|
1070
1111
|
jobs:
|
|
1071
|
-
|
|
1072
|
-
#
|
|
1073
|
-
# over the published CLI). Posts a sticky PR comment + a \`valid\` output.
|
|
1112
|
+
lint:
|
|
1113
|
+
# Lint pillar — verify the references in your instruction files (composite
|
|
1114
|
+
# Action over the published CLI). Posts a sticky PR comment + a \`valid\` output.
|
|
1074
1115
|
runs-on: ubuntu-latest
|
|
1075
1116
|
steps:
|
|
1076
1117
|
- uses: actions/checkout@v4
|
|
@@ -1080,18 +1121,46 @@ jobs:
|
|
|
1080
1121
|
- uses: zernie/vigiles@v1
|
|
1081
1122
|
${harness}`;
|
|
1082
1123
|
}
|
|
1083
|
-
/**
|
|
1124
|
+
/**
|
|
1125
|
+
* Detect a workflow that drives vigiles through an OLD API — a bare `npx vigiles`
|
|
1126
|
+
* (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
|
|
1128
|
+
* subcommand split silently lose CI validation, so we flag it loudly.
|
|
1129
|
+
*/
|
|
1130
|
+
function workflowUsesStaleApi(content) {
|
|
1131
|
+
if (content.includes("zernie/vigiles@"))
|
|
1132
|
+
return false; // uses the Action — fine
|
|
1133
|
+
if (!/\bvigiles\b/.test(content))
|
|
1134
|
+
return false; // not a vigiles workflow
|
|
1135
|
+
const hasModernCmd = /vigiles\s+(lint|test|eval|compile|scan|generate-types|generate-schema|init)\b/.test(content);
|
|
1136
|
+
return !hasModernCmd;
|
|
1137
|
+
}
|
|
1138
|
+
/** 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. */
|
|
1084
1141
|
function wireGha(plan) {
|
|
1085
1142
|
const dir = (0, node_path_1.resolve)(process.cwd(), ".github", "workflows");
|
|
1086
1143
|
const path = (0, node_path_1.resolve)(dir, "vigiles.yml");
|
|
1087
1144
|
if ((0, node_fs_1.existsSync)(path)) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1145
|
+
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.");
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
console.log("✓ .github/workflows/vigiles.yml already exists (up to date)");
|
|
1156
|
+
}
|
|
1157
|
+
return [];
|
|
1090
1158
|
}
|
|
1091
1159
|
if (!(0, node_fs_1.existsSync)(dir))
|
|
1092
1160
|
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
1093
1161
|
(0, node_fs_1.writeFileSync)(path, vigilesWorkflow(plan));
|
|
1094
1162
|
console.log("✓ Created .github/workflows/vigiles.yml (uses zernie/vigiles@v1)");
|
|
1163
|
+
return [".github/workflows/vigiles.yml"];
|
|
1095
1164
|
}
|
|
1096
1165
|
const STARTER_HARNESS = `/**
|
|
1097
1166
|
* Starter harness test (Pillar 2) — scaffolded by \`vigiles init\`.
|
|
@@ -1127,15 +1196,17 @@ assert.ok(!allowed.blocked, "guard should allow a safe command");
|
|
|
1127
1196
|
|
|
1128
1197
|
console.log("\\u2713 hook blocks rm -rf / and allows safe commands");
|
|
1129
1198
|
`;
|
|
1130
|
-
/** Pillar 2 — scaffold a starter harness test the user adapts to their hooks.
|
|
1199
|
+
/** Pillar 2 — scaffold a starter harness test the user adapts to their hooks.
|
|
1200
|
+
* Returns the files it wrote (for the commit hint). */
|
|
1131
1201
|
function scaffoldPillar2() {
|
|
1132
1202
|
const path = (0, node_path_1.resolve)(process.cwd(), "vigiles.harness.mjs");
|
|
1133
1203
|
if ((0, node_fs_1.existsSync)(path)) {
|
|
1134
1204
|
console.log("✓ vigiles.harness.mjs already exists");
|
|
1135
|
-
return;
|
|
1205
|
+
return [];
|
|
1136
1206
|
}
|
|
1137
1207
|
(0, node_fs_1.writeFileSync)(path, STARTER_HARNESS);
|
|
1138
|
-
console.log("✓ Scaffolded vigiles.harness.mjs — Pillar 2 starter (
|
|
1208
|
+
console.log("✓ Scaffolded vigiles.harness.mjs — Pillar 2 starter (npx vigiles test)");
|
|
1209
|
+
return ["vigiles.harness.mjs"];
|
|
1139
1210
|
}
|
|
1140
1211
|
/** Interactive prompts (TTY only): which pillars, CI, plugin. */
|
|
1141
1212
|
async function promptSetup() {
|
|
@@ -1151,12 +1222,12 @@ async function promptSetup() {
|
|
|
1151
1222
|
});
|
|
1152
1223
|
const isYes = (s) => /^y(es)?$/i.test(s);
|
|
1153
1224
|
try {
|
|
1154
|
-
const pillars = (await ask("Set up which pillars? [both/
|
|
1225
|
+
const pillars = (await ask("Set up which pillars? [both/lint/test] (both): ", "both")).toLowerCase();
|
|
1155
1226
|
const gha = isYes(await ask("Wire CI (GitHub Action)? [Y/n]: ", "y"));
|
|
1156
1227
|
const plugin = isYes(await ask("Install the Claude Code plugin (hooks + skills)? [Y/n]: ", "y"));
|
|
1157
1228
|
return {
|
|
1158
|
-
|
|
1159
|
-
test: pillars !== "verify",
|
|
1229
|
+
lint: pillars !== "test",
|
|
1230
|
+
test: pillars !== "lint" && pillars !== "verify",
|
|
1160
1231
|
gha,
|
|
1161
1232
|
plugin,
|
|
1162
1233
|
};
|
|
@@ -1240,55 +1311,118 @@ function detectProject() {
|
|
|
1240
1311
|
hasClaude: (0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, ".claude")),
|
|
1241
1312
|
};
|
|
1242
1313
|
}
|
|
1243
|
-
/**
|
|
1244
|
-
*
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
needsSpec.length === detected.instructionFiles.length) {
|
|
1261
|
-
// ALL existing files need migration — don't create new specs.
|
|
1262
|
-
console.log("Use the migration skill: npx skills add zernie/vigiles\n");
|
|
1263
|
-
return [];
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
targets = ["CLAUDE.md"];
|
|
1267
|
-
const hasAgentsMd = detected.instructionFiles.some((f) => f.path === "AGENTS.md");
|
|
1268
|
-
const hasCodex = detected.agents.includes("Codex / GitHub Copilot") || hasAgentsMd;
|
|
1269
|
-
if (hasCodex && !hasAgentsMd)
|
|
1270
|
-
targets.push("AGENTS.md");
|
|
1314
|
+
/** The instruction-file targets Pillar 1 will create specs for — the harness's
|
|
1315
|
+
* native instruction file (CLAUDE.md for Claude Code, AGENTS.md for Codex),
|
|
1316
|
+
* plus any existing instruction file that lacks a spec. */
|
|
1317
|
+
function determineTargets(detected, targetValue, harnesses) {
|
|
1318
|
+
if (targetValue)
|
|
1319
|
+
return [targetValue];
|
|
1320
|
+
const targets = [];
|
|
1321
|
+
if (harnesses.includes("claude"))
|
|
1322
|
+
targets.push("CLAUDE.md");
|
|
1323
|
+
if (harnesses.includes("codex"))
|
|
1324
|
+
targets.push("AGENTS.md");
|
|
1325
|
+
if (targets.length === 0)
|
|
1326
|
+
targets.push("CLAUDE.md");
|
|
1327
|
+
// Any existing instruction file without a spec also gets one.
|
|
1328
|
+
for (const f of detected.instructionFiles) {
|
|
1329
|
+
if (!f.hasSpec && !targets.includes(f.path))
|
|
1330
|
+
targets.push(f.path);
|
|
1271
1331
|
}
|
|
1272
|
-
|
|
1332
|
+
return targets;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* When CLAUDE.md and AGENTS.md are ONE artifact — a symlink, or kept
|
|
1336
|
+
* byte-identical by rulesync/Ruler — collapse them to a single canonical spec
|
|
1337
|
+
* target. Two specs would fight over one file and collide on the integrity hash
|
|
1338
|
+
* (see the "Compose With Sync Tools" rule). The mirror is distributed from the
|
|
1339
|
+
* canonical, not compiled separately.
|
|
1340
|
+
*/
|
|
1341
|
+
function collapseMirroredTargets(targets, mirror) {
|
|
1342
|
+
if (!mirror)
|
|
1343
|
+
return targets;
|
|
1344
|
+
// The compile source slot: the real file for a symlink, else CLAUDE.md (the
|
|
1345
|
+
// one Claude Code reads natively; the sync tool fans out to AGENTS.md).
|
|
1346
|
+
const canonical = mirror.kind === "symlink"
|
|
1347
|
+
? (mirror.realTarget ?? "CLAUDE.md")
|
|
1348
|
+
: "CLAUDE.md";
|
|
1349
|
+
const mirrored = mirror.files.find((f) => f !== canonical);
|
|
1350
|
+
if (!mirrored)
|
|
1351
|
+
return targets;
|
|
1352
|
+
if (!targets.includes(canonical) && !targets.includes(mirrored)) {
|
|
1353
|
+
return targets; // neither file is a target — nothing to collapse
|
|
1354
|
+
}
|
|
1355
|
+
const collapsed = targets.filter((t) => t !== mirrored);
|
|
1356
|
+
if (!collapsed.includes(canonical))
|
|
1357
|
+
collapsed.push(canonical);
|
|
1358
|
+
console.log(`Note: CLAUDE.md and AGENTS.md are one artifact (${mirror.kind}). ` +
|
|
1359
|
+
`Scaffolding a single spec for ${canonical}; ${mirrored} is its mirror ` +
|
|
1360
|
+
`(don't add a second spec — it would collide on the integrity hash).`);
|
|
1361
|
+
return collapsed;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* When a detected rule-sync tool (rulesync / Ruler) regenerates a file vigiles
|
|
1365
|
+
* would compile to, REDIRECT the compile target to the tool's source slot
|
|
1366
|
+
* (`.ruler/AGENTS.md`, `.rulesync/rules/vigiles.md`). vigiles compiles upstream;
|
|
1367
|
+
* the tool distributes to CLAUDE.md/AGENTS.md/Cursor/… — so the integrity hash
|
|
1368
|
+
* never collides with the tool's output (the "Compose With Sync Tools" rule).
|
|
1369
|
+
*/
|
|
1370
|
+
function redirectSyncToolTargets(cwd, targets) {
|
|
1371
|
+
const collisions = (0, compose_js_1.composeCollisions)(cwd, targets);
|
|
1372
|
+
if (collisions.length === 0)
|
|
1373
|
+
return targets;
|
|
1374
|
+
const slotFor = new Map(collisions.map((c) => [c.target, c.redirectTo]));
|
|
1375
|
+
const out = [];
|
|
1376
|
+
for (const t of targets) {
|
|
1377
|
+
const redirected = slotFor.get(t) ?? t;
|
|
1378
|
+
if (!out.includes(redirected))
|
|
1379
|
+
out.push(redirected);
|
|
1380
|
+
}
|
|
1381
|
+
const tool = collisions[0].tool;
|
|
1382
|
+
const slots = [...new Set(collisions.map((c) => c.redirectTo))].join(", ");
|
|
1383
|
+
const from = [...slotFor.keys()].join(", ");
|
|
1384
|
+
console.log(`Note: ${tool} detected — scaffolding the spec to compile into its source ` +
|
|
1385
|
+
`slot (${slots}) instead of ${from}, so ${tool} distributes it without ` +
|
|
1386
|
+
`staling the integrity hash.`);
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
/** Pillar 1 — specs + types + schema + compile. Scaffolds a spec for every
|
|
1390
|
+
* instruction file (so `--lint` always delivers a spec), but never compiles
|
|
1391
|
+
* OVER a hand-written file — that is left to the migrate-to-spec skill. */
|
|
1392
|
+
async function setupPillar1(detected, targetValue, harnesses) {
|
|
1393
|
+
const cwd = process.cwd();
|
|
1394
|
+
const written = [];
|
|
1395
|
+
const needsMigration = [];
|
|
1396
|
+
// An explicit --target is honoured as-is; otherwise collapse a CLAUDE.md⇄
|
|
1397
|
+
// AGENTS.md mirror (symlink or synced) to one canonical spec, then redirect
|
|
1398
|
+
// into a sync tool's source slot when one would own the output.
|
|
1399
|
+
const targets = targetValue
|
|
1400
|
+
? determineTargets(detected, targetValue, harnesses)
|
|
1401
|
+
: redirectSyncToolTargets(cwd, collapseMirroredTargets(determineTargets(detected, targetValue, harnesses), (0, compose_js_1.detectInstructionMirror)(cwd)));
|
|
1402
|
+
// Create specs (blank). An existing hand-written target keeps its content —
|
|
1403
|
+
// we scaffold the spec but flag it for migration rather than clobbering it.
|
|
1273
1404
|
for (const target of targets) {
|
|
1274
1405
|
const specPath = `${target}.spec.ts`;
|
|
1275
|
-
|
|
1406
|
+
const targetExists = (0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, target));
|
|
1407
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, specPath))) {
|
|
1276
1408
|
console.log(`✓ ${specPath} already exists`);
|
|
1277
1409
|
}
|
|
1278
|
-
else if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), target))) {
|
|
1279
|
-
console.log(`⚠ ${target} exists without spec — migrate with migrate-to-spec skill`);
|
|
1280
|
-
}
|
|
1281
1410
|
else {
|
|
1282
|
-
init(["--target=" + target]);
|
|
1411
|
+
init(["--target=" + target]); // prints "Created …"
|
|
1412
|
+
written.push(specPath);
|
|
1413
|
+
}
|
|
1414
|
+
if (targetExists && !targetHasHash((0, node_path_1.resolve)(cwd, target))) {
|
|
1415
|
+
needsMigration.push(target);
|
|
1416
|
+
console.log(` ${target} already has content — port it into the spec with the migrate-to-spec skill, then \`vigiles compile\`.`);
|
|
1283
1417
|
}
|
|
1284
1418
|
}
|
|
1285
|
-
// Generate types + schema
|
|
1419
|
+
// Generate types + schema.
|
|
1286
1420
|
console.log("\nScanning linters and project files...");
|
|
1287
|
-
const typesResult = (0, generate_types_js_1.generateTypes)({ basePath:
|
|
1288
|
-
const outDir = (0, node_path_1.resolve)(
|
|
1421
|
+
const typesResult = (0, generate_types_js_1.generateTypes)({ basePath: cwd });
|
|
1422
|
+
const outDir = (0, node_path_1.resolve)(cwd, ".vigiles");
|
|
1289
1423
|
if (!(0, node_fs_1.existsSync)(outDir))
|
|
1290
1424
|
(0, node_fs_1.mkdirSync)(outDir, { recursive: true });
|
|
1291
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(
|
|
1425
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(cwd, ".vigiles/generated.d.ts"), typesResult.dts);
|
|
1292
1426
|
for (const l of typesResult.linters) {
|
|
1293
1427
|
console.log(` ${l.linter}: ${String(l.rules.length)} rules`);
|
|
1294
1428
|
}
|
|
@@ -1296,83 +1430,132 @@ async function setupPillar1(detected, targetValue) {
|
|
|
1296
1430
|
console.log(` npm scripts: ${String(typesResult.scripts.length)}`);
|
|
1297
1431
|
}
|
|
1298
1432
|
console.log("✓ Generated .vigiles/generated.d.ts");
|
|
1433
|
+
written.push(".vigiles/generated.d.ts");
|
|
1299
1434
|
const schemaResult = (0, generate_schema_js_1.generateSchema)({
|
|
1300
|
-
basePath:
|
|
1435
|
+
basePath: cwd,
|
|
1301
1436
|
linters: (0, validate_js_1.loadConfig)().linters,
|
|
1302
1437
|
});
|
|
1303
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(
|
|
1438
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(cwd, ".vigiles/schema.json"), schemaResult.json);
|
|
1304
1439
|
console.log("✓ Generated .vigiles/schema.json (YAML-LSP frontmatter schema)");
|
|
1305
|
-
|
|
1440
|
+
written.push(".vigiles/schema.json");
|
|
1441
|
+
// Compile — but only specs whose target is greenfield or already ours. A
|
|
1442
|
+
// freshly-scaffolded blank spec over an existing hand-written file is skipped
|
|
1443
|
+
// so we never overwrite the user's instructions with an empty compile.
|
|
1306
1444
|
console.log("\nCompiling specs...");
|
|
1307
|
-
const specs = findSpecs()
|
|
1445
|
+
const specs = findSpecs().filter((s) => {
|
|
1446
|
+
const tf = (0, node_path_1.resolve)(cwd, s.replace(/\.spec\.ts$/, ""));
|
|
1447
|
+
return !(0, node_fs_1.existsSync)(tf) || targetHasHash(tf);
|
|
1448
|
+
});
|
|
1308
1449
|
if (specs.length > 0)
|
|
1309
1450
|
await compile(specs, (0, validate_js_1.loadConfig)());
|
|
1310
|
-
return targets;
|
|
1451
|
+
return { specTargets: targets, written, needsMigration };
|
|
1311
1452
|
}
|
|
1312
|
-
/**
|
|
1313
|
-
|
|
1314
|
-
function installPlugin() {
|
|
1453
|
+
/** Whether a harness binary (`claude`, `codex`) is on PATH. */
|
|
1454
|
+
function harnessBinaryPresent(bin) {
|
|
1315
1455
|
try {
|
|
1316
1456
|
const { execSync: exec } = require("node:child_process");
|
|
1317
|
-
exec(
|
|
1318
|
-
|
|
1319
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1320
|
-
timeout: 30000,
|
|
1321
|
-
});
|
|
1322
|
-
console.log("✓ Installed vigiles plugin (hooks + skills) via skills CLI");
|
|
1323
|
-
return false;
|
|
1457
|
+
exec(`${bin} --version`, { stdio: "ignore", timeout: 10000 });
|
|
1458
|
+
return true;
|
|
1324
1459
|
}
|
|
1325
1460
|
catch {
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Install vigiles's skills/hooks for the chosen harness(es) via the per-harness
|
|
1466
|
+
* `planPluginInstall` decision — Claude Code through the GLOBAL plugin
|
|
1467
|
+
* marketplace (nothing vendored into the repo), Codex via AGENTS.md-direct (no
|
|
1468
|
+
* global store). The decision is pure and unit-tested; this is the thin IO.
|
|
1469
|
+
*/
|
|
1470
|
+
function installPlugins(harnesses) {
|
|
1471
|
+
const { execSync: exec } = require("node:child_process");
|
|
1472
|
+
const plans = (0, setup_plan_js_1.planPluginInstall)(harnesses, {
|
|
1473
|
+
hasClaude: harnesses.includes("claude") && harnessBinaryPresent("claude"),
|
|
1474
|
+
});
|
|
1475
|
+
for (const plan of plans) {
|
|
1476
|
+
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
|
+
}
|
|
1336
1489
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
const postCmd = `FILE=$(cat | jq -r '.tool_input.file_path // empty') && case "$(basename "$FILE")" in eslint.config.*|.eslintrc*|package.json|pyproject.toml|Cargo.toml) npx vigiles generate-types 2>&1 || true ;; esac && case "$FILE" in *.spec.ts) npx vigiles compile 2>&1 || true ;; esac`;
|
|
1346
|
-
const existingStr = JSON.stringify(settings);
|
|
1347
|
-
if (!existingStr.includes("vigiles:sha256")) {
|
|
1348
|
-
const pre = (hooks["PreToolUse"] ?? []);
|
|
1349
|
-
pre.push({ matcher: "Edit|Write", command: preCmd });
|
|
1350
|
-
hooks["PreToolUse"] = pre;
|
|
1351
|
-
}
|
|
1352
|
-
if (!existingStr.includes("vigiles compile")) {
|
|
1353
|
-
const post = (hooks["PostToolUse"] ?? []);
|
|
1354
|
-
post.push({ matcher: "Edit|Write", command: postCmd });
|
|
1355
|
-
hooks["PostToolUse"] = post;
|
|
1356
|
-
}
|
|
1357
|
-
(0, node_fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1358
|
-
console.log("✓ Installed hooks in .claude/settings.json");
|
|
1359
|
-
console.log(" (For skills like edit-spec and migrate-to-spec, also run: npx skills add zernie/vigiles)");
|
|
1360
|
-
return true;
|
|
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}`);
|
|
1497
|
+
}
|
|
1361
1498
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
if ((0,
|
|
1368
|
-
|
|
1499
|
+
/** Add/upgrade `vigiles` in the project's `devDependencies` (and move it out of
|
|
1500
|
+
* `dependencies` if it's there). Returns the files it wrote (for the commit
|
|
1501
|
+
* hint). No-op in the vigiles repo itself and when there is no package.json. */
|
|
1502
|
+
function ensureVigilesDevDep() {
|
|
1503
|
+
const pkgPath = (0, node_path_1.resolve)(process.cwd(), "package.json");
|
|
1504
|
+
if (!(0, node_fs_1.existsSync)(pkgPath))
|
|
1505
|
+
return [];
|
|
1506
|
+
let pkg;
|
|
1507
|
+
try {
|
|
1508
|
+
pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
1369
1509
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
const
|
|
1510
|
+
catch {
|
|
1511
|
+
return [];
|
|
1512
|
+
}
|
|
1513
|
+
if (pkg.name === "vigiles")
|
|
1514
|
+
return []; // don't self-depend in this repo
|
|
1515
|
+
const spec = vigilesDepSpec();
|
|
1516
|
+
let changed = false;
|
|
1517
|
+
// Move a stale/misplaced runtime dependency (e.g. a `github:zernie/vigiles`
|
|
1518
|
+
// git pin, or vigiles sitting in `dependencies`) into devDependencies.
|
|
1519
|
+
if (pkg.dependencies && "vigiles" in pkg.dependencies) {
|
|
1520
|
+
delete pkg.dependencies.vigiles;
|
|
1521
|
+
changed = true;
|
|
1522
|
+
}
|
|
1523
|
+
const dev = (pkg.devDependencies ??= {});
|
|
1524
|
+
if (dev.vigiles !== spec) {
|
|
1525
|
+
dev.vigiles = spec;
|
|
1526
|
+
changed = true;
|
|
1527
|
+
}
|
|
1528
|
+
if (!changed)
|
|
1529
|
+
return [];
|
|
1530
|
+
(0, node_fs_1.writeFileSync)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1531
|
+
console.log(`✓ Set vigiles@${spec} in devDependencies — run \`npm install\` to fetch it`);
|
|
1532
|
+
return ["package.json"];
|
|
1533
|
+
}
|
|
1534
|
+
/** Which harnesses to set up: an explicit `--harness=` list, else auto-detected
|
|
1535
|
+
* from the repo (Claude Code / Codex), defaulting to Claude Code. */
|
|
1536
|
+
function resolveHarnesses(parsed, detected) {
|
|
1537
|
+
if (parsed.harness) {
|
|
1538
|
+
return parsed.harness
|
|
1539
|
+
.split(",")
|
|
1540
|
+
.map((s) => s.trim().toLowerCase())
|
|
1541
|
+
.filter(Boolean);
|
|
1542
|
+
}
|
|
1543
|
+
const set = new Set();
|
|
1544
|
+
if (detected.hasClaude ||
|
|
1545
|
+
detected.agents.includes("Claude Code") ||
|
|
1546
|
+
detected.instructionFiles.some((f) => f.path === "CLAUDE.md")) {
|
|
1547
|
+
set.add("claude");
|
|
1548
|
+
}
|
|
1549
|
+
if (detected.agents.includes("Codex / GitHub Copilot") ||
|
|
1550
|
+
detected.instructionFiles.some((f) => f.path === "AGENTS.md")) {
|
|
1551
|
+
set.add("codex");
|
|
1552
|
+
}
|
|
1553
|
+
if (set.size === 0)
|
|
1554
|
+
set.add("claude");
|
|
1555
|
+
return [...set];
|
|
1556
|
+
}
|
|
1557
|
+
/** Print the project-detection summary line(s). */
|
|
1558
|
+
function printDetection(detected, harnesses) {
|
|
1376
1559
|
if (detected.agents.length > 0) {
|
|
1377
1560
|
console.log(`Detected: ${detected.agents.join(", ")}`);
|
|
1378
1561
|
}
|
|
@@ -1386,24 +1569,85 @@ async function setup(args) {
|
|
|
1386
1569
|
if (f.isSymlink)
|
|
1387
1570
|
console.log(`Note: ${f.path} is a symlink`);
|
|
1388
1571
|
}
|
|
1389
|
-
|
|
1572
|
+
console.log(`Harness: ${harnesses.join(", ")}`);
|
|
1573
|
+
}
|
|
1574
|
+
/** Print the closing next-steps list + an honest commit hint (only files
|
|
1575
|
+
* actually written this run). */
|
|
1576
|
+
function printSetupSummary(opts) {
|
|
1577
|
+
const { plan, strict, targets, needsMigration, written } = opts;
|
|
1578
|
+
const specPathsList = targets.map((t) => `${t}.spec.ts`);
|
|
1579
|
+
console.log("\n---\nSetup complete.\n");
|
|
1580
|
+
const nextSteps = [];
|
|
1581
|
+
if (needsMigration.length > 0) {
|
|
1582
|
+
nextSteps.push(`Port ${needsMigration.join(", ")} into its spec with the migrate-to-spec skill, then \`npx vigiles compile\``);
|
|
1583
|
+
}
|
|
1584
|
+
else if (specPathsList.length > 0) {
|
|
1585
|
+
nextSteps.push(`Edit ${specPathsList.join(", ")} — add your conventions, then \`/strengthen\``);
|
|
1586
|
+
}
|
|
1587
|
+
if (plan.test) {
|
|
1588
|
+
nextSteps.push("Edit vigiles.harness.mjs to test a real hook, then `npx vigiles test`");
|
|
1589
|
+
}
|
|
1590
|
+
if (written.includes("package.json")) {
|
|
1591
|
+
nextSteps.push("Run `npm install` to fetch the vigiles dev dependency");
|
|
1592
|
+
}
|
|
1593
|
+
if (!strict) {
|
|
1594
|
+
nextSteps.push("When ready, enforce in CI: npx vigiles init --strict");
|
|
1595
|
+
}
|
|
1596
|
+
nextSteps.forEach((s, i) => {
|
|
1597
|
+
console.log(` ${String(i + 1)}. ${s}`);
|
|
1598
|
+
});
|
|
1599
|
+
// Only list files actually written this run (deduped, in a stable order).
|
|
1600
|
+
const files = [...new Set(written)];
|
|
1601
|
+
if (files.length > 0) {
|
|
1602
|
+
console.log(`\n Commit:\n git add ${files.join(" ")} && git commit -m "Add vigiles"`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
async function setup(args) {
|
|
1606
|
+
const parsed = (0, setup_plan_js_1.parseSetupArgs)(args);
|
|
1607
|
+
const strict = parsed.strict;
|
|
1608
|
+
// Plan: defaults → flags → interactive prompts (only a human at a TTY).
|
|
1609
|
+
let plan = (0, setup_plan_js_1.resolvePlan)(parsed);
|
|
1610
|
+
if ((0, setup_plan_js_1.shouldPrompt)(parsed, process.stdin.isTTY ?? false)) {
|
|
1611
|
+
plan = (0, setup_plan_js_1.resolvePlan)(parsed, await promptSetup());
|
|
1612
|
+
}
|
|
1613
|
+
const pillars = [plan.lint && "lint", plan.test && "test"]
|
|
1614
|
+
.filter(Boolean)
|
|
1615
|
+
.join(" + ");
|
|
1616
|
+
console.log(`vigiles setup${strict ? " (strict)" : ""} — pillars: ${pillars}\n`);
|
|
1617
|
+
// Detect project.
|
|
1618
|
+
const detected = detectProject();
|
|
1619
|
+
const harnesses = resolveHarnesses(parsed, detected);
|
|
1620
|
+
printDetection(detected, harnesses);
|
|
1621
|
+
// Files actually written, accumulated for an honest commit hint.
|
|
1622
|
+
const written = [];
|
|
1623
|
+
// Lint pillar — verify instruction-file references.
|
|
1390
1624
|
let targets = [];
|
|
1391
|
-
|
|
1625
|
+
let needsMigration = [];
|
|
1626
|
+
if (plan.lint) {
|
|
1392
1627
|
console.log("");
|
|
1393
|
-
|
|
1628
|
+
const p1 = await setupPillar1(detected, parsed.target, harnesses);
|
|
1629
|
+
targets = p1.specTargets;
|
|
1630
|
+
needsMigration = p1.needsMigration;
|
|
1631
|
+
written.push(...p1.written);
|
|
1394
1632
|
}
|
|
1395
|
-
//
|
|
1633
|
+
// Test pillar — test the harness.
|
|
1396
1634
|
if (plan.test) {
|
|
1397
1635
|
console.log("");
|
|
1398
|
-
scaffoldPillar2();
|
|
1636
|
+
written.push(...scaffoldPillar2());
|
|
1637
|
+
}
|
|
1638
|
+
// Add/upgrade the vigiles dev dependency (both pillars import from it).
|
|
1639
|
+
if (plan.lint || plan.test) {
|
|
1640
|
+
written.push(...ensureVigilesDevDep());
|
|
1399
1641
|
}
|
|
1400
1642
|
// CI — the production Action (+ a harness job when Pillar 2 is on).
|
|
1401
1643
|
if (plan.gha) {
|
|
1402
1644
|
console.log("");
|
|
1403
|
-
wireGha(plan);
|
|
1645
|
+
written.push(...wireGha(plan));
|
|
1646
|
+
}
|
|
1647
|
+
// Plugin/skill install — per-harness (Claude marketplace / Codex direct).
|
|
1648
|
+
if (plan.plugin) {
|
|
1649
|
+
installPlugins(harnesses);
|
|
1404
1650
|
}
|
|
1405
|
-
// Claude Code plugin (hooks + skills).
|
|
1406
|
-
const wroteSettings = plan.plugin ? installPlugin() : false;
|
|
1407
1651
|
// Agent-specific guidance.
|
|
1408
1652
|
if (targets.includes("AGENTS.md")) {
|
|
1409
1653
|
console.log("\n Codex / Copilot reads AGENTS.md directly — no hooks needed.");
|
|
@@ -1419,36 +1663,10 @@ async function setup(args) {
|
|
|
1419
1663
|
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
1420
1664
|
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify({ rules: { "require-spec": "error", "require-skill-spec": "error" } }, null, 2) + "\n");
|
|
1421
1665
|
console.log("✓ Created .vigilesrc.json with strict rules");
|
|
1666
|
+
written.push(".vigilesrc.json");
|
|
1422
1667
|
}
|
|
1423
1668
|
}
|
|
1424
|
-
|
|
1425
|
-
const specPathsList = targets.map((t) => `${t}.spec.ts`);
|
|
1426
|
-
console.log("\n---\nSetup complete.\n");
|
|
1427
|
-
const nextSteps = [];
|
|
1428
|
-
if (specPathsList.length > 0) {
|
|
1429
|
-
nextSteps.push(`Edit ${specPathsList.join(", ")} — add your conventions, then \`/strengthen\``);
|
|
1430
|
-
}
|
|
1431
|
-
if (plan.test) {
|
|
1432
|
-
nextSteps.push("Edit vigiles.harness.mjs to test a real hook, then `npx vigiles test`");
|
|
1433
|
-
}
|
|
1434
|
-
if (!strict) {
|
|
1435
|
-
nextSteps.push("When ready, enforce in CI: npx vigiles init --strict");
|
|
1436
|
-
}
|
|
1437
|
-
nextSteps.forEach((s, i) => {
|
|
1438
|
-
console.log(` ${String(i + 1)}. ${s}`);
|
|
1439
|
-
});
|
|
1440
|
-
const files = [
|
|
1441
|
-
...targets,
|
|
1442
|
-
...specPathsList,
|
|
1443
|
-
...(plan.verify ? [".vigiles/generated.d.ts", ".vigiles/schema.json"] : []),
|
|
1444
|
-
...(plan.test ? ["vigiles.harness.mjs"] : []),
|
|
1445
|
-
...(plan.gha ? [".github/workflows/vigiles.yml"] : []),
|
|
1446
|
-
...(wroteSettings ? [".claude/settings.json"] : []),
|
|
1447
|
-
...(strict ? [".vigilesrc.json"] : []),
|
|
1448
|
-
];
|
|
1449
|
-
if (files.length > 0) {
|
|
1450
|
-
console.log(`\n Commit:\n git add ${files.join(" ")} && git commit -m "Add vigiles"`);
|
|
1451
|
-
}
|
|
1669
|
+
printSetupSummary({ plan, strict, targets, needsMigration, written });
|
|
1452
1670
|
}
|
|
1453
1671
|
// ---------------------------------------------------------------------------
|
|
1454
1672
|
// Strengthen: guidance() → enforce() suggestions
|
|
@@ -1716,22 +1934,23 @@ function printUsage(command) {
|
|
|
1716
1934
|
console.log("vigiles — compile typed specs to instruction files");
|
|
1717
1935
|
console.log("");
|
|
1718
1936
|
console.log("Commands:");
|
|
1719
|
-
console.log(" vigiles init [flags] Setup project (--
|
|
1937
|
+
console.log(" vigiles init [flags] Setup project (--lint, --test, --harness=, --strict, --no-gha)");
|
|
1720
1938
|
console.log(" vigiles compile [files...] Compile .spec.ts → .md");
|
|
1721
|
-
console.log(" vigiles
|
|
1939
|
+
console.log(" vigiles lint [files...] Verify references, find gaps in instruction files");
|
|
1722
1940
|
console.log(" vigiles test [files...] Run *.harness.mjs deterministic harness tests");
|
|
1723
1941
|
console.log(" vigiles eval [files...] Run *.eval.mjs real-model harness evals (--trials=N)");
|
|
1724
1942
|
console.log("");
|
|
1725
1943
|
console.log("Examples:");
|
|
1726
1944
|
console.log(" vigiles init Auto-detect project, create specs, wire CI");
|
|
1727
1945
|
console.log(" vigiles compile Compile all .spec.ts files");
|
|
1728
|
-
console.log(" vigiles
|
|
1946
|
+
console.log(" vigiles lint Verify references, hashes, coverage + suggestions");
|
|
1729
1947
|
console.log("");
|
|
1730
1948
|
console.log("Plumbing:");
|
|
1731
1949
|
console.log(" vigiles generate-types [out] Emit .d.ts from project state");
|
|
1732
1950
|
console.log(" vigiles generate-types --check Verify .d.ts is up to date");
|
|
1733
1951
|
console.log(" vigiles generate-schema [out] Emit JSON Schema for vigiles: frontmatter");
|
|
1734
1952
|
console.log(" vigiles generate-schema --check Verify schema.json is up to date");
|
|
1953
|
+
console.log(" vigiles --version Print the version number");
|
|
1735
1954
|
if (command && command !== "--help") {
|
|
1736
1955
|
console.log(`\nUnknown command: "${command}"`);
|
|
1737
1956
|
process.exit(1);
|
|
@@ -2025,7 +2244,7 @@ function refsHookCommand() {
|
|
|
2025
2244
|
issues.map((m) => ` - ${m}`).join("\n") +
|
|
2026
2245
|
`\nExpress references as marks (\`enforce()\` / \`file()\` / \`cmd()\` / a ` +
|
|
2027
2246
|
`\`vigiles:symbol\` span / an inline \`<!-- vigiles:enforce -->\` comment) so ` +
|
|
2028
|
-
`\`vigiles
|
|
2247
|
+
`\`vigiles lint\` can check them — or add \`<!-- vigiles:ignore -->\` if it ` +
|
|
2029
2248
|
`is prose, not a reference.`;
|
|
2030
2249
|
process.stdout.write(JSON.stringify({
|
|
2031
2250
|
hookSpecificOutput: {
|
|
@@ -2037,6 +2256,12 @@ function refsHookCommand() {
|
|
|
2037
2256
|
async function main() {
|
|
2038
2257
|
const args = process.argv.slice(2);
|
|
2039
2258
|
const command = args[0];
|
|
2259
|
+
// `--version` / `-v` / `version` prints the version number, not the help
|
|
2260
|
+
// banner — so `npx vigiles --version` reports e.g. `3.0.0`.
|
|
2261
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
2262
|
+
console.log(getVersion());
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2040
2265
|
const restArgs = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
2041
2266
|
// Shared flags (--max-rules, --catalog-only) override the loaded config so
|
|
2042
2267
|
// every GitHub Action input maps to a real CLI flag. See src/cli-flags.ts.
|
|
@@ -2075,8 +2300,8 @@ async function main() {
|
|
|
2075
2300
|
}
|
|
2076
2301
|
break;
|
|
2077
2302
|
}
|
|
2078
|
-
case "
|
|
2079
|
-
//
|
|
2303
|
+
case "lint": {
|
|
2304
|
+
// lint = verify references + discover + guidance count
|
|
2080
2305
|
const flags = args.slice(1).filter((a) => a.startsWith("--"));
|
|
2081
2306
|
const report = await audit(restArgs, flags, config);
|
|
2082
2307
|
annotateAuditForGitHub(report, flags);
|