vigiles 33.2.0 → 34.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.
@@ -17,12 +17,12 @@
17
17
  * boundary is deliberate: plugins "can execute arbitrary code on your machine
18
18
  * with your user privileges", so a repo is not allowed to install one for you.
19
19
  *
20
- * The declaration buys exactly one thing, and it is worth having: a collaborator
21
- * who clones the repo and never runs `vigiles init` currently gets **silence** —
22
- * the package is in `node_modules`, its six skills are unreachable, and nothing
23
- * says so. With the declaration, Claude Code tells them the project wants this
24
- * plugin and prints the command that installs it. **Silent absence becomes a
25
- * prompt.** It does not become a working install.
20
+ * The declaration buys exactly one thing: a collaborator who clones the repo is
21
+ * told the project wants this plugin, with the command that installs it.
22
+ * **Silent absence becomes a prompt.** It does not become a working install. What
23
+ * the plugin adds on their machine is the HOOKS; the SKILLS do not depend on it
24
+ * any more — `init` links them into `.claude/skills/` (`./skill-links.ts`), and
25
+ * the links travel with the clone.
26
26
  *
27
27
  * Nothing is vendored: what lands in the repo is a *reference* (a marketplace
28
28
  * source plus an enabled flag). The plugin content still lives in the global
@@ -18,12 +18,12 @@
18
18
  * boundary is deliberate: plugins "can execute arbitrary code on your machine
19
19
  * with your user privileges", so a repo is not allowed to install one for you.
20
20
  *
21
- * The declaration buys exactly one thing, and it is worth having: a collaborator
22
- * who clones the repo and never runs `vigiles init` currently gets **silence** —
23
- * the package is in `node_modules`, its six skills are unreachable, and nothing
24
- * says so. With the declaration, Claude Code tells them the project wants this
25
- * plugin and prints the command that installs it. **Silent absence becomes a
26
- * prompt.** It does not become a working install.
21
+ * The declaration buys exactly one thing: a collaborator who clones the repo is
22
+ * told the project wants this plugin, with the command that installs it.
23
+ * **Silent absence becomes a prompt.** It does not become a working install. What
24
+ * the plugin adds on their machine is the HOOKS; the SKILLS do not depend on it
25
+ * any more — `init` links them into `.claude/skills/` (`./skill-links.ts`), and
26
+ * the links travel with the clone.
27
27
  *
28
28
  * Nothing is vendored: what lands in the repo is a *reference* (a marketplace
29
29
  * source plus an enabled flag). The plugin content still lives in the global
@@ -16,7 +16,10 @@ export interface SetupPlan {
16
16
  test: boolean;
17
17
  /** Wire CI (the `zernie/vigiles@v1` Action; creates a workflow if none). */
18
18
  gha: boolean;
19
- /** Install the Claude Code plugin (hooks + skills). */
19
+ /**
20
+ * Install vigiles's skills + hooks for the agent: the skills LINKED into the
21
+ * repo (every clone), the Claude Code hooks via the per-machine plugin.
22
+ */
20
23
  plugin: boolean;
21
24
  /**
22
25
  * Adopt/scaffold the instruction file(s) into typed `.spec.ts` (the compiled
@@ -74,6 +77,11 @@ export interface ParsedSetupArgs {
74
77
  * DISCOVERY row of `cohesive-feature-delivery`.
75
78
  */
76
79
  ciOnly: boolean;
80
+ /**
81
+ * `--full` — run the whole setup even on a repo that already uses vigiles.
82
+ * Without it, a re-run there is the minimal one (see {@link rerunScope}).
83
+ */
84
+ full: boolean;
77
85
  }
78
86
  /** Parse `init` args into the choices the user pinned. The two pillars are
79
87
  * selected with `--lint` / `--test`. */
@@ -84,29 +92,6 @@ export declare function parseSetupArgs(args: readonly string[]): ParsedSetupArgs
84
92
  * raw files WITHOUT scaffolding a spec (gate-first-adoption). Non-interactive
85
93
  * behaviour is unchanged: bare `init` (lint on) scaffolds as before. */
86
94
  export declare function defaultPlan(strict?: boolean): SetupPlan;
87
- /**
88
- * Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
89
- * the `harness` if absent, add strict rule severities if `--strict`, NEVER
90
- * clobber an existing key. Returns the merged config, or `null` when nothing
91
- * changed (so the IO layer skips the write). The IO (read/parse/write + the
92
- * malformed-file guard) stays in cli.ts.
93
- */
94
- /**
95
- * The structural rules `init` gates BY DEFAULT (severity `error`, so a broken
96
- * surface fails `vigiles lint`). Every one is HIGH-PRECISION / FP-safe — it fires
97
- * only on a genuine defect (a never-available/typo'd tool, a subagent missing
98
- * `name`/`description`, a typo'd hook event, a dead hook script, a broken MCP
99
- * ref, two skills that collide in the selector) — so a well-formed plugin stays
100
- * green and catching real breakage out of the box never cries wolf.
101
- *
102
- * Deliberately EXCLUDES `require-instructions-spec` and the workflow-forcing rules:
103
- * those make a CLEAN repo fail (you simply haven't written the spec/test yet), so
104
- * they stay opt-in under `--strict` (progressive adoption — see
105
- * `STRICT_EXTRA_RULES`).
106
- *
107
- * This is the **`structural`** rule group (see research/install-enforcement-dx.md).
108
- */
109
- 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"];
110
95
  /**
111
96
  * The **`workflow`** group — the WORKFLOW-FORCING / opinionated tier `--strict`
112
97
  * gates, which a clean repo can still fail because you haven't done the work yet:
@@ -132,7 +117,24 @@ export declare const WORKFLOW_RULES: readonly ["require-instructions-spec", "unt
132
117
  * (research/install-enforcement-dx.md).
133
118
  */
134
119
  export declare const NUDGE_RULES: readonly ["skill-description-budget", "frontmatter-valid", "skill-frontmatter", "prefer-compiled-hooks", "unmarked-refs", "lethal-trifecta", "skill-resource-resolves", "skill-missing-fence", "plugin-dir-layout", "delegation-trifecta", "hook-block-ineffective", "hook-matcher", "doc-refs"];
135
- export declare function mergeProjectConfig(existing: Record<string, unknown>, opts: {
120
+ /**
121
+ * Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
122
+ * the harnesses if absent, gate the structural rules by EXTENDING the
123
+ * `vigiles:recommended` preset (never by writing each rule), add the workflow
124
+ * rules on `--strict`, and NEVER clobber an existing key or severity. Returns
125
+ * the merged config, or `null` when nothing changed (so the IO layer skips the
126
+ * write). The IO (read/parse/write + the malformed-file guard) stays in cli.ts.
127
+ *
128
+ * 🔴 THE PRESET, NOT NINE LINES (#338). Written lines are a snapshot of the
129
+ * structural group on the day `init` ran, so a repo adopted earlier never got a
130
+ * rule added to the group later; the preset is resolved by the loader on every
131
+ * run (`src/core/presets.ts`).
132
+ *
133
+ * `--report-only` is the exception and stays EXPLICIT: it promises that nothing
134
+ * fails CI, and extending a preset that later gains a rule at `error` would
135
+ * break that promise on an upgrade. So it writes the same rules at `warn`.
136
+ */
137
+ export declare function mergeProjectConfig(existing: Readonly<Record<string, unknown>>, opts: Readonly<{
136
138
  /** Canonical harness names this repo targets, in the order to declare them. */
137
139
  harnesses: readonly string[];
138
140
  strict: boolean;
@@ -141,7 +143,7 @@ export declare function mergeProjectConfig(existing: Record<string, unknown>, op
141
143
  * concern, so a test-only setup (`init --test` / `--no-lint`) records the
142
144
  * harness but writes NO lint rules. */
143
145
  lint?: boolean;
144
- }): Record<string, unknown> | null;
146
+ }>): Record<string, unknown> | null;
145
147
  /**
146
148
  * Whether to drop into interactive prompts: a human at a TTY who passed neither
147
149
  * `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
@@ -193,15 +195,18 @@ export interface InstallPlan {
193
195
  notes: string[];
194
196
  /** Whether this method writes files into the consumer's repo (vendoring). */
195
197
  vendors: boolean;
198
+ /**
199
+ * Link the package's shipped skills into the repo's skills home
200
+ * (`src/skill-links.ts`). True only where the vendor documents following a
201
+ * symlinked skill folder — a link the harness skips is a silent absence.
202
+ */
203
+ linkSkills: boolean;
204
+ /**
205
+ * The global install carries HOOKS as well as skills, so linked skills do not
206
+ * replace it (see {@link shouldRunGlobalInstall}).
207
+ */
208
+ globalInstallCarriesHooks: boolean;
196
209
  }
197
- /** Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
198
- * (else the same two steps are printed as `/plugin` slash commands).
199
- *
200
- * Both methods install GLOBALLY, never into the repo: Claude through its plugin
201
- * marketplace (~/.claude/plugins/), Codex through the cross-agent `skills` CLI
202
- * with `-g -y` (the global store ~/.agents/skills/, which Codex reads). Codex
203
- * gets the skills but NOT hooks — Codex hook wiring (.codex/config.toml [hooks])
204
- * is not automated yet. */
205
210
  /**
206
211
  * The SHIPPED consumer skills (the `skills/` dir, published via `plugin.json`) —
207
212
  * the ONLY ones a user's install should get. The cross-agent `skills` CLI would
@@ -212,9 +217,43 @@ export interface InstallPlan {
212
217
  * dogfood test in `src/adapters/claude-code/skills-dogfood.test.ts`.
213
218
  */
214
219
  export declare const SHIPPED_SKILLS: readonly ["adopt-spec", "debug-my-harness", "edit-spec", "linter-docs", "strengthen", "test-harness"];
220
+ /**
221
+ * Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
222
+ * (else the same two steps are printed as `/plugin` slash commands).
223
+ *
224
+ * TWO CARRIERS, by what each one reaches. The SKILLS are linked into the repo
225
+ * (`linkSkills`), so every clone and container has them after `npm install`. The
226
+ * GLOBAL installs reach one machine: Claude Code's plugin (~/.claude/plugins/)
227
+ * is still the only carrier of vigiles's Claude Code HOOKS, so it always runs;
228
+ * Codex's `skills` CLI install (~/.agents/skills/) carries skills only, so it
229
+ * runs only when the links could not be made (no `package.json` to install
230
+ * into). Codex's nudge hooks are written into `.codex/config.toml` directly.
231
+ *
232
+ * THE CODEX GLOBAL COMMAND. The cross-agent `skills` CLI with `-g -y` installs
233
+ * to the global store ~/.agents/skills/ (NOT the repo, and NOT ~/.codex/ —
234
+ * verified against the real CLI). It is scoped to the SHIPPED skills with `-s`
235
+ * — without it the `skills` CLI installs EVERY SKILL.md in the repo, leaking
236
+ * the contributor-only .claude/skills/ into the user's global store
237
+ * (#dogfood-I2; confirmed — the CLI's `-l` list shows 17 skills for this repo,
238
+ * incl. .claude/skills). The `-s` parser is SPACE-separated (it consumes
239
+ * consecutive non-dash args), NOT comma-separated — a comma list is read as one
240
+ * literal skill name, matches nothing, and the install exits 1 (verified against
241
+ * the real `skills@1.5.20` arg parser).
242
+ */
215
243
  export declare function planPluginInstall(harnesses: readonly string[], opts: {
216
244
  hasClaude: boolean;
217
245
  }): InstallPlan[];
246
+ /**
247
+ * Run a plan's GLOBAL install, given whether the skills were linked into the
248
+ * repo? Skip it only when linked skills make it redundant — when it carries
249
+ * nothing but skills. Running it anyway would list every skill twice (Codex:
250
+ * "If two skills share the same `name`, Codex doesn't merge them; both can
251
+ * appear in skill selectors"). Claude Code's plugin carries the hooks, so it
252
+ * always runs; its skills are namespaced `vigiles:<name>` and load beside the
253
+ * linked `<name>` (a known, documented duplicate until the hooks move into the
254
+ * repo too).
255
+ */
256
+ export declare function shouldRunGlobalInstall(plan: Readonly<Pick<InstallPlan, "globalInstallCarriesHooks">>, skillsLinked: boolean): boolean;
218
257
  /** One vigiles-managed Codex hook: a `[[hooks.<event>]]` entry. */
219
258
  export interface CodexPluginHook {
220
259
  readonly event: string;
@@ -252,6 +291,75 @@ export declare function codexPluginHooks(): CodexPluginHook[];
252
291
  * (and every other config key) untouched. Returns a new object.
253
292
  */
254
293
  export declare function applyCodexPluginHooks(existing: Record<string, unknown>): Record<string, unknown>;
294
+ /**
295
+ * Whether this repo already uses vigiles, decided from what is ON DISK.
296
+ *
297
+ * 🔴 THE SIGNAL IS `.vigilesrc.json` OR A SPEC NEXT TO AN INSTRUCTION FILE, and
298
+ * deliberately NOT a `vigiles` devDependency: the documented first step on a new
299
+ * repo can be `npm i -D vigiles` followed by `npx vigiles init`, and that run
300
+ * must still be the full setup. Every `init` writes `.vigilesrc.json` (it records
301
+ * the harnesses even on a test-only run), and a hand-written one means the owner
302
+ * has already configured vigiles. A spec beside `CLAUDE.md`/`AGENTS.md` covers a
303
+ * repo adopted before the config file existed.
304
+ */
305
+ export type AdoptionState = {
306
+ readonly kind: "fresh";
307
+ } | {
308
+ readonly kind: "adopted";
309
+ readonly evidence: string;
310
+ };
311
+ /** {@link AdoptionState} from the two disk facts that decide it. */
312
+ export declare function adoptionState(facts: Readonly<{
313
+ hasConfig: boolean;
314
+ specs: readonly string[];
315
+ }>): AdoptionState;
316
+ /**
317
+ * Which setup a run performs: the full one, or the minimal re-run (#338).
318
+ *
319
+ * MINIMAL only when the repo is adopted AND the run named nothing that asks for
320
+ * more. A flag that selects or widens the setup (`--full`, `--strict`,
321
+ * `--ci-only`, `--report-only`, `--force`, `--target=`, a positive `--lint` /
322
+ * `--test`) is an explicit request and keeps today's behaviour; the narrowing
323
+ * flags (`--no-*`, `--harness=`, `--yes`) do not, because they can only make a
324
+ * run do less.
325
+ */
326
+ export declare function rerunScope(parsed: Readonly<ParsedSetupArgs>, state: AdoptionState): "full" | "minimal";
327
+ /**
328
+ * The `devDependencies` a minimal re-run writes, or null to leave package.json
329
+ * alone. It ADDS vigiles when no dependency field declares it and never edits an
330
+ * existing declaration: rewriting `^33.3.0` to `^33` loosens a pin the owner
331
+ * chose, and moving it between fields is a decision, not a missing piece.
332
+ */
333
+ export declare function missingVigilesDevDep(pkg: Readonly<{
334
+ dependencies?: Readonly<Record<string, string>>;
335
+ devDependencies?: Readonly<Record<string, string>>;
336
+ }>, spec: string): Readonly<Record<string, string>> | null;
337
+ /** What a full setup would add on an adopted repo — the facts that decide it. */
338
+ export interface FullSetupGap {
339
+ /** `.github/workflows/vigiles.yml` is absent (and CI wiring is not opted out). */
340
+ readonly workflow: boolean;
341
+ /** `.vigilesrc.json`: absent, present without the preset, or already extending it. */
342
+ readonly config: "missing" | "no-preset" | "has-preset";
343
+ /** `vigiles.harness.mjs` is absent (and the test layer is not opted out). */
344
+ readonly harnessTest: boolean;
345
+ /** Instruction files and surfaces a full setup would adopt into a spec. */
346
+ readonly unspecced: readonly string[];
347
+ /** `.vigiles/generated.d.ts` / `.vigiles/schema.json` would be created. */
348
+ readonly generated: boolean;
349
+ /** The global plugin install / hook wiring a full setup would run, one line each. */
350
+ readonly installs: readonly string[];
351
+ /** The devDependency rewrite a full setup would make, if any. */
352
+ readonly devDep: Readonly<{
353
+ from: string;
354
+ to: string;
355
+ }> | null;
356
+ }
357
+ /**
358
+ * The lines a minimal re-run prints about what it did NOT do: each thing a full
359
+ * setup would add here, then the flag that does it. Empty when nothing is
360
+ * missing, so a fully set-up repo is not told to run anything.
361
+ */
362
+ export declare function fullSetupFollowUps(gap: FullSetupGap): readonly string[];
255
363
  /**
256
364
  * Resolve the final plan: defaults, then flags, then interactive answers (each
257
365
  * layer overrides the previous only where it has an opinion). `--target` pins a