vigiles 7.0.0 → 8.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.
@@ -27,6 +27,10 @@ export interface SetupPlan {
27
27
  export interface ParsedSetupArgs {
28
28
  target?: string;
29
29
  strict: boolean;
30
+ /** `--report-only` — write the gating rules at "warn" (nothing fails CI). The
31
+ * orthogonal severity dial; composes with `--strict` (which rules) by setting
32
+ * their severity. */
33
+ reportOnly: boolean;
30
34
  yes: boolean;
31
35
  /** `--force` — rewrite a stale CI workflow in place. */
32
36
  force: boolean;
@@ -51,9 +55,52 @@ export declare function defaultPlan(strict?: boolean): SetupPlan;
51
55
  * changed (so the IO layer skips the write). The IO (read/parse/write + the
52
56
  * malformed-file guard) stays in cli.ts.
53
57
  */
58
+ /**
59
+ * The structural rules `init` gates BY DEFAULT (severity `error`, so a broken
60
+ * surface fails `vigiles lint`). Every one is HIGH-PRECISION / FP-safe — it fires
61
+ * only on a genuine defect (a never-available/typo'd tool, a subagent missing
62
+ * `name`/`description`, a typo'd hook event, a dead hook script, a broken MCP
63
+ * ref, two skills that collide in the selector) — so a well-formed plugin stays
64
+ * green and catching real breakage out of the box never cries wolf.
65
+ *
66
+ * Deliberately EXCLUDES `require-instructions-spec` and the workflow-forcing rules:
67
+ * those make a CLEAN repo fail (you simply haven't written the spec/test yet), so
68
+ * they stay opt-in under `--strict` (progressive adoption — see
69
+ * `STRICT_EXTRA_RULES`).
70
+ *
71
+ * This is the **`structural`** rule group (see research/install-enforcement-dx.md).
72
+ */
73
+ export declare const STRUCTURAL_RULES: readonly ["subagent-tool-contract", "subagent-frontmatter", "hook-events", "hook-script-exists", "mcp-config", "mcp-tool-resolves", "mcp-hook-target-resolves", "disallowed-tools-contract", "description-overlap"];
74
+ /**
75
+ * The **`workflow`** group — the WORKFLOW-FORCING / opinionated tier `--strict`
76
+ * gates, which a clean repo can still fail because you haven't done the work yet:
77
+ * a spec per instruction file (`require-instructions-spec`), a test/eval per
78
+ * surface (`untested-*`). Opt-in by design (the smooth-adoption on-ramp). The
79
+ * Clippy-`pedantic` / TS-`strict` analog — ONE opinionated opt-in.
80
+ *
81
+ * NB `frontmatter-valid` / `skill-frontmatter` live in the `nudge` group, not
82
+ * here: they're acknowledged-noisy recommendations we never gate on (see
83
+ * research/install-enforcement-dx.md).
84
+ */
85
+ export declare const WORKFLOW_RULES: readonly ["require-instructions-spec", "untested-skill", "untested-subagent", "untested-hook"];
86
+ /**
87
+ * The **`nudge`** group — recommendations / acknowledged-noisy checks that NEVER
88
+ * gate (not even under `--strict`): `frontmatter-valid` (js-yaml is stricter than
89
+ * CC's loader), `skill-frontmatter` (skills load without it) and `unmarked-refs`
90
+ * (the undecidable-plaintext nudge) sit at `warn`; `prefer-compiled-hooks` defaults
91
+ * OFF (a recommendation that shouldn't fire unasked — the shell lane stays
92
+ * first-class). `init` does not write these — they keep their own default
93
+ * severities. Named for the group taxonomy (research/install-enforcement-dx.md).
94
+ */
95
+ export declare const NUDGE_RULES: readonly ["frontmatter-valid", "skill-frontmatter", "prefer-compiled-hooks", "unmarked-refs"];
54
96
  export declare function mergeProjectConfig(existing: Record<string, unknown>, opts: {
55
97
  harness: string | string[];
56
98
  strict: boolean;
99
+ reportOnly?: boolean;
100
+ /** Whether the LINT pillar is on (default true). The rule gate is a lint-layer
101
+ * concern, so a test-only setup (`init --test` / `--no-lint`) records the
102
+ * harness but writes NO lint rules. */
103
+ lint?: boolean;
57
104
  }): Record<string, unknown> | null;
58
105
  /**
59
106
  * Whether to drop into interactive prompts: a human at a TTY who passed neither
@@ -62,7 +109,18 @@ export declare function mergeProjectConfig(existing: Record<string, unknown>, op
62
109
  */
63
110
  export declare function shouldPrompt(parsed: ParsedSetupArgs, isTTY: boolean): boolean;
64
111
  /** Interactive answers (only the fields the prompts cover). */
65
- export type SetupAnswers = Partial<Pick<SetupPlan, "lint" | "test" | "gha" | "plugin">>;
112
+ export type SetupAnswers = Partial<Pick<SetupPlan, "lint" | "test" | "gha" | "plugin" | "strict">>;
113
+ /** Ask one question with a default — injected so the interactive Q&A is pure +
114
+ * unit-testable (a fake `ask` scripts answers; no TTY, no readline). */
115
+ export type AskFn = (question: string, def: string) => Promise<string>;
116
+ /**
117
+ * The interactive setup Q&A as PURE logic over an injected `ask` — the prompts,
118
+ * their defaults, and the answer→`SetupAnswers` mapping. The IO shell (readline)
119
+ * lives in `cli.ts`'s `promptSetup`, which just supplies a real `ask`. Keeping
120
+ * this here means the fragile interactive path is unit-tested deterministically
121
+ * (the questions can't silently break) without a terminal.
122
+ */
123
+ export declare function collectSetupAnswers(ask: AskFn): Promise<SetupAnswers>;
66
124
  /**
67
125
  * How to install vigiles's skills/hooks for ONE harness — the deterministic
68
126
  * decision behind the IO in cli.ts, so a CI test asserts WHICH commands an
@@ -10,10 +10,12 @@
10
10
  * on a prompt. See docs/agent-setup.md.
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.NUDGE_RULES = exports.WORKFLOW_RULES = exports.STRUCTURAL_RULES = void 0;
13
14
  exports.parseSetupArgs = parseSetupArgs;
14
15
  exports.defaultPlan = defaultPlan;
15
16
  exports.mergeProjectConfig = mergeProjectConfig;
16
17
  exports.shouldPrompt = shouldPrompt;
18
+ exports.collectSetupAnswers = collectSetupAnswers;
17
19
  exports.planPluginInstall = planPluginInstall;
18
20
  exports.resolvePlan = resolvePlan;
19
21
  function flagValue(args, prefix) {
@@ -33,6 +35,7 @@ function parseSetupArgs(args) {
33
35
  return {
34
36
  target: flagValue(args, "--target="),
35
37
  strict: args.includes("--strict"),
38
+ reportOnly: args.includes("--report-only"),
36
39
  yes: args.includes("--yes") || args.includes("-y"),
37
40
  force: args.includes("--force"),
38
41
  lint: boolFlag(args, "lint"),
@@ -60,6 +63,64 @@ function defaultPlan(strict = false) {
60
63
  * changed (so the IO layer skips the write). The IO (read/parse/write + the
61
64
  * malformed-file guard) stays in cli.ts.
62
65
  */
66
+ /**
67
+ * The structural rules `init` gates BY DEFAULT (severity `error`, so a broken
68
+ * surface fails `vigiles lint`). Every one is HIGH-PRECISION / FP-safe — it fires
69
+ * only on a genuine defect (a never-available/typo'd tool, a subagent missing
70
+ * `name`/`description`, a typo'd hook event, a dead hook script, a broken MCP
71
+ * ref, two skills that collide in the selector) — so a well-formed plugin stays
72
+ * green and catching real breakage out of the box never cries wolf.
73
+ *
74
+ * Deliberately EXCLUDES `require-instructions-spec` and the workflow-forcing rules:
75
+ * those make a CLEAN repo fail (you simply haven't written the spec/test yet), so
76
+ * they stay opt-in under `--strict` (progressive adoption — see
77
+ * `STRICT_EXTRA_RULES`).
78
+ *
79
+ * This is the **`structural`** rule group (see research/install-enforcement-dx.md).
80
+ */
81
+ exports.STRUCTURAL_RULES = [
82
+ "subagent-tool-contract",
83
+ "subagent-frontmatter",
84
+ "hook-events",
85
+ "hook-script-exists",
86
+ "mcp-config",
87
+ "mcp-tool-resolves",
88
+ "mcp-hook-target-resolves",
89
+ "disallowed-tools-contract",
90
+ "description-overlap",
91
+ ];
92
+ /**
93
+ * The **`workflow`** group — the WORKFLOW-FORCING / opinionated tier `--strict`
94
+ * gates, which a clean repo can still fail because you haven't done the work yet:
95
+ * a spec per instruction file (`require-instructions-spec`), a test/eval per
96
+ * surface (`untested-*`). Opt-in by design (the smooth-adoption on-ramp). The
97
+ * Clippy-`pedantic` / TS-`strict` analog — ONE opinionated opt-in.
98
+ *
99
+ * NB `frontmatter-valid` / `skill-frontmatter` live in the `nudge` group, not
100
+ * here: they're acknowledged-noisy recommendations we never gate on (see
101
+ * research/install-enforcement-dx.md).
102
+ */
103
+ exports.WORKFLOW_RULES = [
104
+ "require-instructions-spec",
105
+ "untested-skill",
106
+ "untested-subagent",
107
+ "untested-hook",
108
+ ];
109
+ /**
110
+ * The **`nudge`** group — recommendations / acknowledged-noisy checks that NEVER
111
+ * gate (not even under `--strict`): `frontmatter-valid` (js-yaml is stricter than
112
+ * CC's loader), `skill-frontmatter` (skills load without it) and `unmarked-refs`
113
+ * (the undecidable-plaintext nudge) sit at `warn`; `prefer-compiled-hooks` defaults
114
+ * OFF (a recommendation that shouldn't fire unasked — the shell lane stays
115
+ * first-class). `init` does not write these — they keep their own default
116
+ * severities. Named for the group taxonomy (research/install-enforcement-dx.md).
117
+ */
118
+ exports.NUDGE_RULES = [
119
+ "frontmatter-valid",
120
+ "skill-frontmatter",
121
+ "prefer-compiled-hooks",
122
+ "unmarked-refs",
123
+ ];
63
124
  function mergeProjectConfig(existing, opts) {
64
125
  const config = { ...existing };
65
126
  let changed = false;
@@ -67,13 +128,23 @@ function mergeProjectConfig(existing, opts) {
67
128
  config.harness = opts.harness;
68
129
  changed = true;
69
130
  }
70
- if (opts.strict) {
131
+ // The rule gate belongs to the LINT layer — a test-only setup records the
132
+ // harness but writes no rules (honoring the positive-flag contract that
133
+ // `--test` selects only the test pillar).
134
+ if (opts.lint !== false) {
135
+ // Gate the FP-safe `structural` group by default; `--strict` adds the
136
+ // `workflow` group on top. `--report-only` is the orthogonal severity dial —
137
+ // it writes the SAME rule set at "warn" (nothing fails CI; the
138
+ // migration/observe mode). Never clobber a severity the user already set —
139
+ // only fill the undefined ones.
140
+ const severity = opts.reportOnly ? "warn" : "error";
141
+ const gate = opts.strict
142
+ ? [...exports.STRUCTURAL_RULES, ...exports.WORKFLOW_RULES]
143
+ : [...exports.STRUCTURAL_RULES];
71
144
  const rules = { ...config.rules };
72
- // `require-skill-spec` is deprecated (skills can be hand-written), so --strict
73
- // no longer promotes it; it tightens only `require-spec` (instruction files).
74
- for (const r of ["require-spec"]) {
145
+ for (const r of gate) {
75
146
  if (rules[r] === undefined) {
76
- rules[r] = "error";
147
+ rules[r] = severity;
77
148
  changed = true;
78
149
  }
79
150
  }
@@ -93,6 +164,31 @@ function shouldPrompt(parsed, isTTY) {
93
164
  const allPinned = pillarsPinned && parsed.gha !== undefined && parsed.plugin !== undefined;
94
165
  return !allPinned;
95
166
  }
167
+ const isYesAnswer = (s) => /^y(es)?$/i.test(s);
168
+ /**
169
+ * The interactive setup Q&A as PURE logic over an injected `ask` — the prompts,
170
+ * their defaults, and the answer→`SetupAnswers` mapping. The IO shell (readline)
171
+ * lives in `cli.ts`'s `promptSetup`, which just supplies a real `ask`. Keeping
172
+ * this here means the fragile interactive path is unit-tested deterministically
173
+ * (the questions can't silently break) without a terminal.
174
+ */
175
+ async function collectSetupAnswers(ask) {
176
+ const pillars = (await ask("Set up which pillars? [both/lint/test] (both): ", "both")).toLowerCase();
177
+ const gha = isYesAnswer(await ask("Wire CI (GitHub Action)? [Y/n]: ", "y"));
178
+ const plugin = isYesAnswer(await ask("Install the Claude Code plugin (hooks + skills)? [Y/n]: ", "y"));
179
+ // Structural gating (broken tools/hooks/MCP/collisions) is always on. This asks
180
+ // about the WORKFLOW tier — a spec per file + a test per surface — which a clean
181
+ // repo can fail just for not having done the work yet, so it's the recommended
182
+ // default a human opts OUT of (never forced on a silent run).
183
+ const strict = isYesAnswer(await ask("Also enforce specs + a test per surface (recommended)? [Y/n]: ", "y"));
184
+ return {
185
+ lint: pillars !== "test",
186
+ test: pillars !== "lint" && pillars !== "verify",
187
+ gha,
188
+ plugin,
189
+ strict,
190
+ };
191
+ }
96
192
  /**
97
193
  * Apply the pillar flags. A positive flag (`--lint` and/or `--test`) is an
98
194
  * explicit SELECTION — enable exactly the named pillars. Otherwise default to
@@ -118,6 +214,8 @@ function applyAnswers(plan, answers) {
118
214
  plan.gha = answers.gha;
119
215
  if (answers.plugin !== undefined)
120
216
  plan.plugin = answers.plugin;
217
+ if (answers.strict !== undefined)
218
+ plan.strict = answers.strict;
121
219
  }
122
220
  /** Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
123
221
  * (else the same two steps are printed as `/plugin` slash commands).
@@ -19,7 +19,7 @@ BASENAME=$(basename "$FILE")
19
19
  case "$BASENAME" in
20
20
  eslint.config.*|.eslintrc*|.stylelintrc*|.rubocop.yml|pyproject.toml|Cargo.toml|package.json)
21
21
  if command -v npx &>/dev/null && [ -f "package.json" ]; then
22
- npx vigiles generate-types 2>&1 || true
22
+ npx vigiles generate types 2>&1 || true
23
23
  fi
24
24
  ;;
25
25
  esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "7.0.0",
3
+ "version": "8.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",
@@ -7,7 +7,7 @@ argument-hint: <path to CLAUDE.md, defaults to CLAUDE.md>
7
7
 
8
8
  Start a typed `CLAUDE.md.spec.ts` from an existing hand-written CLAUDE.md (or AGENTS.md). This is the non-destructive adoption path — you keep your existing instruction file as the starting point and get type safety going forward.
9
9
 
10
- > **Don't need full TypeScript?** A typed spec is the deepest commitment level. If the user only wants verified rules without a build step, point them at markdown mode first: inline `<!-- vigiles:enforce ... -->` comments (Level 0) or a `vigiles:` YAML frontmatter block with `vigiles generate-schema` for editor autocomplete (Level 1). Both are verified by `vigiles lint` with the same engine as a spec. See `docs/markdown-mode.md`. Adopt a spec only when they want compiler-grade guarantees.
10
+ > **Faithful by default, and reversible.** Adoption is non-destructive: the goal is a spec that compiles back to the user's existing file as closely as possible preserve every rule, command, key file, and prose section. Don't upgrade `guidance()` to `enforce()` here; that's a separate, opt-in step (the `strengthen` skill). And it's never a one-way door — `vigiles eject <file>` hands the file back as plain hand-owned markdown anytime. For the lightest touch with no spec at all, inline `<!-- vigiles:enforce ... -->` comments are verified by `vigiles lint` with the same engine.
11
11
 
12
12
  ## Instructions
13
13
 
@@ -100,9 +100,9 @@ Show the user:
100
100
  2. How many rules were converted (enforce vs guidance vs TODO)
101
101
  3. How many file/cmd refs were added for stale reference detection
102
102
  4. The command to compile: `npx vigiles compile`
103
- 5. The command to verify: `npx vigiles check`
103
+ 5. The command to verify: `npx vigiles lint`
104
104
 
105
- Ask if they want you to write the file. If yes, also suggest adding to `.gitignore` or updating CI to run `vigiles compile` and `vigiles check`.
105
+ Ask if they want you to write the file. If yes, also suggest adding to `.gitignore` or updating CI to run `vigiles compile` and `vigiles lint`.
106
106
 
107
107
  ### Step 6: Optional — Set Up CI
108
108
 
@@ -111,14 +111,14 @@ If the user wants CI integration, suggest adding to their GitHub Actions workflo
111
111
  ```yaml
112
112
  - name: Compile specs
113
113
  run: npx vigiles compile
114
- - name: Verify integrity
115
- run: npx vigiles check
114
+ - name: Verify references + integrity
115
+ run: npx vigiles lint
116
116
  ```
117
117
 
118
118
  Or using the vigiles GitHub Action:
119
119
 
120
120
  ```yaml
121
- - uses: zernie/vigiles@main
121
+ - uses: zernie/vigiles@v1
122
122
  with:
123
- command: check
123
+ command: lint
124
124
  ```
@@ -365,7 +365,7 @@ If two rules try to fix the same range of code, ESLint drops both fixes. Avoid t
365
365
  ### Monorepo considerations
366
366
 
367
367
  - ESLint flat config is resolved from `cwd`, not from the file being linted. In a monorepo, set `cwd` to the package root, not the workspace root.
368
- - `vigiles generate-types` discovers ESLint rules using `calculateConfigForFile("dummy.js")` from the project `basePath`. If your monorepo has different configs per package, run `generate-types` from each package root.
368
+ - `vigiles generate types` discovers ESLint rules using `calculateConfigForFile("dummy.js")` from the project `basePath`. If your monorepo has different configs per package, run `generate types` from each package root.
369
369
  - Plugin rules must be installed in the `node_modules` visible from the config file's location. Hoisted deps in a monorepo can cause "plugin not found" errors — install them in the package's own `devDependencies`.
370
370
 
371
371
  ## Mapping PR Feedback to Rule Strategy
@@ -20,7 +20,7 @@ Default to interactive if the user doesn't specify.
20
20
 
21
21
  ### Step 1: Discover What's Installed
22
22
 
23
- Run `npx vigiles generate-types` to get the full list of enabled linter rules in the project. Read `.vigiles/generated.d.ts` to see every rule available across all detected linters.
23
+ Run `npx vigiles generate types` to get the full list of enabled linter rules in the project. Read `.vigiles/generated.d.ts` to see every rule available across all detected linters.
24
24
 
25
25
  Note which linter prefixes appear in the generated types (e.g., `EslintRule`, `RuffRule`). You'll only need reference docs for detected linters.
26
26