vigiles 22.0.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -163,7 +163,7 @@ Every one of these is **valid markdown** — parses fine, does the wrong thing.
163
163
  | `test` | Does the harness behave? | No — a scripted stand-in | Every commit |
164
164
  | `eval` | Does a skill actually help? | Yes — your subscription | On demand |
165
165
 
166
- **One engine, two doors.** `audit` is the local report; **`lint` is the CI gate** that fails the build on the same deterministic checks — broken refs, bad tool contracts, dead hooks, skill collisions (Proofs 1–2). `test` and `eval` go further: past _does it exist_ to _does it work_. (`init` / `compile` / `eject` manage the optional typed-spec layer for the structural rules no linter can express — a graduation step you rarely run by hand.) [How the verbs relate →](docs/commands-and-how-they-relate.md)
166
+ **One engine, two doors.** `audit` is the local report; **`lint` is the CI gate** that fails the build on the same deterministic checks — broken refs, bad tool contracts, dead hooks, skill collisions (Proofs 1–2). `test` and `eval` go further: past _does it exist_ to _does it work_. (`init` / `compile` / `eject` manage the optional typed-spec layer for the structural rules no linter can express — a graduation step you rarely run by hand. If you use a spec, run `compile` in CI too: it is what re-derives that spec's refs, while `lint` verifies the compiled file is intact.) [How the verbs relate →](docs/commands-and-how-they-relate.md)
167
167
 
168
168
  ### 🔎 Lint — your instructions stop lying
169
169
 
@@ -6,6 +6,9 @@ exports.claudeCodeHookProtocol = {
6
6
  blockExitCode: 2,
7
7
  denyDecisionValues: ["block", "deny"],
8
8
  eventEnvVars: [],
9
+ // `{"continue": false}` stops the turn outright and returns `stopReason` to
10
+ // the agent — a stronger stop than a per-call deny, and a documented one.
11
+ haltsTurnField: "continue",
9
12
  // Events that honor `hookSpecificOutput.additionalContext` (developer-context
10
13
  // injection). Covers vigiles's shipped inject hooks: the SessionStart lint
11
14
  // summary and the PostToolUse refs / eval-lock nudges.
@@ -16,6 +16,8 @@ exports.claudeCodeLayout = {
16
16
  skillDir: "skills",
17
17
  agentDir: "agents",
18
18
  commandDir: "commands",
19
+ // `.claude/rules/*.md` — path-scoped project instructions (see PluginLayout).
20
+ rulesDir: "rules",
19
21
  materializeRoot: ".claude",
20
22
  pluginRootToken: "${CLAUDE_PLUGIN_ROOT}",
21
23
  // Both names Claude Code uses for the project root (mirrors the
@@ -24,6 +24,7 @@ exports.formatScriptSummary = formatScriptSummary;
24
24
  const node_child_process_1 = require("node:child_process");
25
25
  const node_os_1 = require("node:os");
26
26
  const node_path_1 = require("node:path");
27
+ const node_url_1 = require("node:url");
27
28
  const node_fs_1 = require("node:fs");
28
29
  const node_os_2 = require("node:os");
29
30
  const glob_1 = require("glob");
@@ -250,10 +251,23 @@ async function runScripts(files, cwd, env = {}, opts = {}) {
250
251
  return;
251
252
  }
252
253
  const countFile = (0, node_path_1.join)(countDir, `${String(i)}.count`);
253
- const child = (0, node_child_process_1.spawn)("node", argv, {
254
+ // Resolve a harness's bare `vigiles` import from the CLI's OWN install, so
255
+ // running the gate does not require installing the package into the
256
+ // project — which, in a repo that already has a package.json, drags in the
257
+ // entire dependency tree (measured at 840 packages against vigiles' 42,
258
+ // #184). Only rescues that specifier, and only after normal resolution
259
+ // fails, so a locally installed copy still wins.
260
+ const selfRoot = (0, node_path_1.resolve)(__dirname, "..", "..", "..");
261
+ const hook = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(selfRoot, "dist", "harness-resolve-hooks.mjs")).href;
262
+ const child = (0, node_child_process_1.spawn)("node", ["--import", hookImport(hook), ...argv], {
254
263
  cwd,
255
264
  stdio: ["ignore", "pipe", "pipe"],
256
- env: { ...process.env, ...env, [check_count_js_1.CHECK_COUNT_ENV]: countFile },
265
+ env: {
266
+ ...process.env,
267
+ ...env,
268
+ VIGILES_SELF_ROOT: selfRoot,
269
+ [check_count_js_1.CHECK_COUNT_ENV]: countFile,
270
+ },
257
271
  });
258
272
  const chunks = [];
259
273
  child.stdout.on("data", (c) => chunks.push(c));
@@ -376,4 +390,14 @@ function formatScriptSummary(results) {
376
390
  }
377
391
  return lines.join("\n");
378
392
  }
393
+ /**
394
+ * A `--import` argument that registers the resolver hook without a temp file:
395
+ * a data: URL calling `module.register`. Inline because writing a shim into the
396
+ * user's tree to run their tests would be a side effect the runner has no
397
+ * business having.
398
+ */
399
+ function hookImport(hookHref) {
400
+ const src = `import {register} from "node:module";register(${JSON.stringify(hookHref)});`;
401
+ return `data:text/javascript,${encodeURIComponent(src)}`;
402
+ }
379
403
  //# sourceMappingURL=run-scripts.js.map
@@ -78,7 +78,7 @@ exports.COMMAND_FLAGS = {
78
78
  ],
79
79
  compile: [],
80
80
  eject: ["--keep-spec"],
81
- lint: ["--summary", "--json"],
81
+ lint: ["--bundles=", "--summary", "--json", "--json-out="],
82
82
  // handleRunScripts (free tier — no lock flags).
83
83
  test: ["--min=", "--all", "--yes", "--no-interactive", "--no-skip"],
84
84
  // handleRunScripts + resolveEvalLockEnv + the trials knob.
@@ -95,6 +95,7 @@ exports.COMMAND_FLAGS = {
95
95
  audit: [
96
96
  "--json",
97
97
  "--md",
98
+ "--single",
98
99
  "--out=",
99
100
  "--no-html",
100
101
  "--no-json",
package/dist/cli.d.ts CHANGED
@@ -9,6 +9,127 @@
9
9
  * `self-command-refs.test.ts` did not catch it because it guards against refs to
10
10
  * REMOVED commands, not against a list that merely stops growing.
11
11
  */
12
+ import type { RuleSeverity } from "./core/types.js";
12
13
  /** Reason the most recent `loadSpec()` returned null, or null if it succeeded. */
13
14
  export declare function specLoadFailureReason(): string | null;
15
+ /**
16
+ * Structured lint report used by --json, --summary, and exit-code logic.
17
+ */
18
+ interface LintReport {
19
+ hashErrors: number;
20
+ validationErrors: number;
21
+ inlineErrors: number;
22
+ inlineRules: number;
23
+ frontmatterErrors: number;
24
+ frontmatterRules: number;
25
+ specRefIssues: number;
26
+ specRefErrors: number;
27
+ duplicatePairs: number;
28
+ /** Severity of `duplicate-instructions` — carried so the exit code can tier it. */
29
+ duplicateSeverity: RuleSeverity;
30
+ coverageEnabled: number;
31
+ coverageDocumented: number;
32
+ strengthenSuggestions: number;
33
+ integrityErrors: number;
34
+ coverageErrors: number;
35
+ orphanCount: number;
36
+ /** Severity of `orphan-docs` — carried so the exit code can tier it (#181). */
37
+ orphanSeverity: RuleSeverity;
38
+ untestedSurfaces: number;
39
+ untestedErrors: number;
40
+ toolContractIssues: number;
41
+ toolContractErrors: number;
42
+ hookEventIssues: number;
43
+ hookEventErrors: number;
44
+ frontmatterSchemaIssues: number;
45
+ frontmatterSchemaErrors: number;
46
+ mcpConfigIssues: number;
47
+ mcpConfigErrors: number;
48
+ skillFrontmatterIssues: number;
49
+ skillFrontmatterErrors: number;
50
+ mcpToolIssues: number;
51
+ mcpToolErrors: number;
52
+ hookScriptIssues: number;
53
+ hookScriptErrors: number;
54
+ disallowedToolIssues: number;
55
+ disallowedToolErrors: number;
56
+ descriptionOverlapIssues: number;
57
+ descriptionOverlapErrors: number;
58
+ descriptionBudgetIssues: number;
59
+ descriptionBudgetErrors: number;
60
+ frontmatterValidIssues: number;
61
+ frontmatterValidErrors: number;
62
+ mcpHookIssues: number;
63
+ mcpHookErrors: number;
64
+ preferCompiledHookIssues: number;
65
+ preferCompiledHookErrors: number;
66
+ lethalTrifectaIssues: number;
67
+ lethalTrifectaErrors: number;
68
+ skillResourceIssues: number;
69
+ skillResourceErrors: number;
70
+ skillFenceIssues: number;
71
+ skillFenceErrors: number;
72
+ pluginLayoutIssues: number;
73
+ pluginLayoutErrors: number;
74
+ delegationTrifectaIssues: number;
75
+ delegationTrifectaErrors: number;
76
+ hookBlockIssues: number;
77
+ hookBlockErrors: number;
78
+ hookMatcherIssues: number;
79
+ hookMatcherErrors: number;
80
+ docRefErrors: number;
81
+ symbolRefErrors: number;
82
+ mcpRefErrors: number;
83
+ files: string[];
84
+ /**
85
+ * Findings / errors / warnings for the whole run — the same numbers the
86
+ * human-readable summary line prints, so a consumer never has to reconstruct
87
+ * them by counting output lines (#183, and the generic-consumer half of #181).
88
+ */
89
+ totals?: {
90
+ findings: number;
91
+ errors: number;
92
+ warnings: number;
93
+ };
94
+ }
95
+ /** Exit codes: 0 clean, 1 warnings only, 2 hard errors. */
96
+ /**
97
+ * The run's totals, derived from the report itself.
98
+ *
99
+ * 🔴 ONE SOURCE, because the two numbers disagreeing IS the bug (#183). The
100
+ * human-readable log had no total, and counting its `⚠` lines gave a different
101
+ * number from the JSON — 21 against 88 on a real repo — because some checks print
102
+ * one line per finding and others one line carrying a count. Both numbers were
103
+ * right and nothing said why they differed, so "vigiles reports 21 warnings" and
104
+ * "88 warnings" were equally defensible readings of one run.
105
+ *
106
+ * Counted GENERICALLY off the `*Issues` / `*Errors` / count keys rather than a
107
+ * hand-maintained list, so a rule added later is included by existing, not by
108
+ * somebody remembering. `orphanCount` and `duplicatePairs` are named explicitly
109
+ * only because they predate the `*Issues` convention (#181).
110
+ */
111
+ export declare function lintTotals(report: LintReport): {
112
+ findings: number;
113
+ errors: number;
114
+ warnings: number;
115
+ };
116
+ /**
117
+ * Nested plugin bundles under a lint root — a directory that is itself a harness
118
+ * (its own `.claude-plugin/plugin.json`, or its own skills dir) and is NOT the
119
+ * root being linted.
120
+ *
121
+ * 🔴 WHY THIS EXISTS. Every per-surface check reads ONE root, so in a monorepo
122
+ * holding `skills/` plus `plugins/ * /skills/` the nested skills were never scored
123
+ * and nothing said so. Measured on a fixture: 4 skills over the description
124
+ * budget, `lint .` reported 2, exit 0 — a repo reads that as green-with-2 while
125
+ * the other 2 carry the same defect (#185). The failure is silent, which is the
126
+ * shape this repo treats as worse than a loud one.
127
+ *
128
+ * Deliberately shallow (one level under a container dir): deep recursion would
129
+ * sweep vendored corpora — this repo's own `test/dogfood/` holds real pinned
130
+ * third-party plugins — and scoring someone else's vendored plugin as if it were
131
+ * yours is the false-positive that gets a gate switched off.
132
+ */
133
+ export declare function discoverNestedBundles(root: string, exclude?: readonly string[]): string[];
134
+ export {};
14
135
  //# sourceMappingURL=cli.d.ts.map