stylelint-plugin-rhythmguard 3.1.0 → 3.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ The format follows Keep a Changelog principles and semantic versioning.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.2.0] - 2026-09-06
10
+
11
+ ### Added
12
+
13
+ - `rhythmguard audit --format badge` writes a shields.io endpoint document for a README badge: `spacing drift` as a percentage by default, or the off-scale count with `--badge-metric findings`. Workflow in `docs/CI_ADOPTION.md`. ([#68](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/issues/68))
14
+ - `rhythmguard init --agents <claude|cursor|copilot|all>` installs the agents block from `docs/FOR_AGENTS.md` as a Claude Code skill, a Cursor rule, or a block in `.github/copilot-instructions.md`. The packs ship in the package under `agents/`, are generated by `npm run build:agents`, and a test keeps them in sync with the docs. ([#69](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/issues/69))
15
+ - `npm run bench:state-of-spacing` generates a dated State of Spacing edition from the quiet benchmark results: one row per repository with scale source, off-scale count, drift per 100 CSS files, cleanliness, top values, top properties, and the change since the previous edition. Benchmark results now record the property of each finding. First edition at 20 repositories in `docs/STATE_OF_SPACING.md`. ([#70](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/issues/70))
16
+
17
+ ### Changed
18
+
19
+ - Dev dependencies: eslint 9.39.5; the unused `geist` package and its `next` and `sharp` chain are gone, and `npm audit` reports no findings. No runtime dependency changed.
20
+
9
21
  ## [3.1.0] - 2026-09-06
10
22
 
11
23
  ### Added
package/CONTRIBUTING.md CHANGED
@@ -6,7 +6,7 @@ Thank you for being here. Rhythmguard is a small, opinionated tool with one job:
6
6
 
7
7
  **Report a false positive.** A finding your team considers wrong is the most valuable report we get. It is how percentages and hairlines stopped being findings. Open a [false-positive report](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/issues/new?template=false-positive.yml) with the CSS or class string and your config. No fix required.
8
8
 
9
- **Add a repository to the quiet benchmark.** The benchmark audits public design systems and fails CI when the findings change. Adding a repo is one JSON entry in `benchmarks/quiet/repos.json` plus `npm run bench:quiet -- --only <name>` to create its snapshot. Systems with tokens in unusual places are the most useful additions.
9
+ **Add a repository to the quiet benchmark.** The benchmark audits public design systems and fails CI when the findings change. Adding a repo is one JSON entry in `benchmarks/quiet/repos.json` plus `npm run bench:quiet -- --only <name>` to create its snapshot. Systems with tokens in unusual places are the most useful additions. The same results feed the dated [State of Spacing](./docs/STATE_OF_SPACING.md) editions, so every repository you add widens that report.
10
10
 
11
11
  **Review a drift list.** Each benchmark repo has a list of findings that heuristics call real drift. Labelling them as real or as an allowance in `benchmarks/quiet/labels/<repo>.json` with a one-line reason turns a heuristic number into a reviewed one, and any systematic pattern you find becomes a candidate default.
12
12
 
package/README.md CHANGED
@@ -85,6 +85,8 @@ The audit scans CSS declarations, Tailwind class strings and your token contract
85
85
 
86
86
  `npx rhythmguard init` writes a starter config for your stack. `npx rhythmguard doctor` checks the setup.
87
87
 
88
+ A README badge comes from the same audit: `--format badge` writes a shields.io endpoint document, see [`docs/CI_ADOPTION.md`](./docs/CI_ADOPTION.md#5-show-a-badge).
89
+
88
90
  ## Guides
89
91
 
90
92
  - [Tailwind integration](docs/TAILWIND.md), including v4 `@theme` tokens and what each layer covers
@@ -92,8 +94,9 @@ The audit scans CSS declarations, Tailwind class strings and your token contract
92
94
  - [Comparison with adjacent plugins](docs/COMPARISON.md) and migration recipes
93
95
  - [Real before/after excerpts](docs/ADOPTION_DIFFS.md) from public codebases
94
96
  - [For shared-config authors](docs/FOR_CONFIG_AUTHORS.md): the `embed` entry point and how inference works per consumer
95
- - [For coding agents](docs/FOR_AGENTS.md): a paste-ready `AGENTS.md` block and the commands to run
97
+ - [For coding agents](docs/FOR_AGENTS.md): a paste-ready `AGENTS.md` block, installable with `npx rhythmguard init --agents all` for Claude Code, Cursor and Copilot
96
98
  - [Quiet benchmark](docs/QUIET_BENCHMARK.md): findings on public design systems, checked on every change
99
+ - [State of Spacing](docs/STATE_OF_SPACING.md): dated editions of the same data, ranked by drift density, with the values and properties that drifted
97
100
  - [Product direction](docs/STRATEGY_2026-09.md)
98
101
  - Browser playground: [petrilahdelma.github.io/stylelint-plugin-rhythmguard](https://petrilahdelma.github.io/stylelint-plugin-rhythmguard/)
99
102
 
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: rhythmguard
3
+ description: Keep CSS and Tailwind spacing on the project scale. Use when writing or changing CSS, SCSS, CSS Modules or Tailwind class strings, and before finishing any such change.
4
+ ---
5
+
6
+ ## Spacing scale
7
+
8
+ This project enforces its spacing scale with stylelint-plugin-rhythmguard.
9
+
10
+ - Before finishing any change to CSS or to Tailwind class strings, run `npx rhythmguard audit . --format json` (or `npx stylelint "**/*.css"` if a config exists) and fix every `off-scale` finding.
11
+ - A finding names the value and the two nearest on-scale values. Prefer the design token for that value when one exists; otherwise use the nearest on-scale value. Never invent a new spacing value.
12
+ - Do not use `--fix` on spacing findings unless the task says so. Snapping a value can change layout; choose the value deliberately.
13
+ - Values of one pixel or less (hairlines) and percentages are allowed and are not findings.
14
+ - Tailwind arbitrary spacing values such as `p-[13px]` are findings too; use the scale utility (`p-3`) or an on-scale arbitrary value.
15
+ - When the audit's `contracts.scale.offScaleProperties` table is dominated by margins on sibling elements, put a `gap` on the parent instead of fixing each margin. The parent owns the spacing between its children.
16
+ - If the audit reports the scale source as `fallback`, the project has no discoverable spacing tokens. Ask before adding any; do not guess a scale.
17
+
18
+ Source: docs/FOR_AGENTS.md in the stylelint-plugin-rhythmguard repository (https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/FOR_AGENTS.md). Regenerate with `npx rhythmguard init --agents`.
@@ -0,0 +1,13 @@
1
+ ## Spacing scale
2
+
3
+ This project enforces its spacing scale with stylelint-plugin-rhythmguard.
4
+
5
+ - Before finishing any change to CSS or to Tailwind class strings, run `npx rhythmguard audit . --format json` (or `npx stylelint "**/*.css"` if a config exists) and fix every `off-scale` finding.
6
+ - A finding names the value and the two nearest on-scale values. Prefer the design token for that value when one exists; otherwise use the nearest on-scale value. Never invent a new spacing value.
7
+ - Do not use `--fix` on spacing findings unless the task says so. Snapping a value can change layout; choose the value deliberately.
8
+ - Values of one pixel or less (hairlines) and percentages are allowed and are not findings.
9
+ - Tailwind arbitrary spacing values such as `p-[13px]` are findings too; use the scale utility (`p-3`) or an on-scale arbitrary value.
10
+ - When the audit's `contracts.scale.offScaleProperties` table is dominated by margins on sibling elements, put a `gap` on the parent instead of fixing each margin. The parent owns the spacing between its children.
11
+ - If the audit reports the scale source as `fallback`, the project has no discoverable spacing tokens. Ask before adding any; do not guess a scale.
12
+
13
+ Source: docs/FOR_AGENTS.md in the stylelint-plugin-rhythmguard repository (https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/FOR_AGENTS.md). Regenerate with `npx rhythmguard init --agents`.
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Spacing scale rules enforced by stylelint-plugin-rhythmguard
3
+ globs: ["**/*.css", "**/*.scss", "**/*.tsx", "**/*.jsx", "**/*.vue", "**/*.svelte", "**/*.astro", "**/*.html"]
4
+ alwaysApply: true
5
+ ---
6
+
7
+ ## Spacing scale
8
+
9
+ This project enforces its spacing scale with stylelint-plugin-rhythmguard.
10
+
11
+ - Before finishing any change to CSS or to Tailwind class strings, run `npx rhythmguard audit . --format json` (or `npx stylelint "**/*.css"` if a config exists) and fix every `off-scale` finding.
12
+ - A finding names the value and the two nearest on-scale values. Prefer the design token for that value when one exists; otherwise use the nearest on-scale value. Never invent a new spacing value.
13
+ - Do not use `--fix` on spacing findings unless the task says so. Snapping a value can change layout; choose the value deliberately.
14
+ - Values of one pixel or less (hairlines) and percentages are allowed and are not findings.
15
+ - Tailwind arbitrary spacing values such as `p-[13px]` are findings too; use the scale utility (`p-3`) or an on-scale arbitrary value.
16
+ - When the audit's `contracts.scale.offScaleProperties` table is dominated by margins on sibling elements, put a `gap` on the parent instead of fixing each margin. The parent owns the spacing between its children.
17
+ - If the audit reports the scale source as `fallback`, the project has no discoverable spacing tokens. Ask before adding any; do not guess a scale.
18
+
19
+ Source: docs/FOR_AGENTS.md in the stylelint-plugin-rhythmguard repository (https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/FOR_AGENTS.md). Regenerate with `npx rhythmguard init --agents`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylelint-plugin-rhythmguard",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Nobody chose 13px. Catches off-scale spacing in CSS and Tailwind class strings and snaps it to your scale or tokens. Stylelint rules, an ESLint companion, and an audit CLI.",
5
5
  "bin": {
6
6
  "rhythmguard": "src/cli/index.js"
@@ -89,6 +89,7 @@
89
89
  }
90
90
  },
91
91
  "files": [
92
+ "agents",
92
93
  "examples",
93
94
  "scales",
94
95
  "schemas",
@@ -113,7 +114,9 @@
113
114
  "test:npm-smoke": "node scripts/ci/npm-registry-smoke.mjs --package stylelint-plugin-rhythmguard --version latest",
114
115
  "test:pack-smoke": "node scripts/ci/pack-smoke.mjs",
115
116
  "test:watch": "node --test --watch test/*.test.js",
116
- "typecheck": "tsc -p tsconfig.typecheck.json"
117
+ "typecheck": "tsc -p tsconfig.typecheck.json",
118
+ "build:agents": "node scripts/build-agents.mjs",
119
+ "bench:state-of-spacing": "node scripts/bench/state-of-spacing.mjs"
117
120
  },
118
121
  "repository": {
119
122
  "type": "git",
@@ -125,8 +128,8 @@
125
128
  "email": "hello@petrilahdelma.com"
126
129
  },
127
130
  "peerDependencies": {
128
- "stylelint": "^16.0.0 || ^17.0.0",
129
131
  "postcss-scss": "^4.0.0",
132
+ "stylelint": "^16.0.0 || ^17.0.0",
130
133
  "stylelint-config-tailwindcss": "^1.0.1",
131
134
  "stylelint-plugin-logical-css": "^2.0.2"
132
135
  },
@@ -145,11 +148,10 @@
145
148
  "known-css-properties": "^0.37.0"
146
149
  },
147
150
  "devDependencies": {
148
- "@eslint/js": "^9.22.0",
151
+ "@eslint/js": "^9.39.5",
149
152
  "@types/node": "^22.20.1",
150
153
  "c8": "^10.1.3",
151
- "eslint": "^9.22.0",
152
- "geist": "^1.7.0",
154
+ "eslint": "^9.39.5",
153
155
  "postcss-scss": "^4.0.9",
154
156
  "postcss-value-parser": "^4.2.0",
155
157
  "stylelint": "^16.15.0",
package/src/audit/args.js CHANGED
@@ -4,6 +4,7 @@ const {
4
4
  normalizeTokenKind,
5
5
  normalizeTokenSourceFormat,
6
6
  } = require('../utils/token-sources');
7
+ const { BADGE_METRICS } = require('./render-badge');
7
8
  const {
8
9
  VALID_FORMATS,
9
10
  createDefaultAuditOptions,
@@ -13,12 +14,14 @@ const {
13
14
  const HELP = `Usage: rhythmguard audit <dir> [options]
14
15
 
15
16
  Options:
16
- --format <text|json|json-v1|markdown|html|github> Output format (default: text)
17
+ --format <text|json|json-v1|markdown|html|github|badge> Output format (default: text)
17
18
  github = GitHub Actions workflow-command annotations
19
+ badge = shields.io endpoint JSON for a README badge
20
+ --badge-metric <drift|findings> Badge value: drift percent or off-scale count (default: drift)
18
21
  --json Alias for --format json
19
22
  --markdown Alias for --format markdown
20
23
  --schema Print the audit JSON schema and exit
21
- --output <file> Write json, markdown, or html output to a file
24
+ --output <file> Write json, markdown, html or badge output to a file
22
25
  --config <file> Load audit config (default: .rhythmguardrc.json when present)
23
26
  --no-config Ignore .rhythmguardrc.json discovery
24
27
  --ignore <pattern> Exclude root-relative path/glob (repeatable, comma-separated)
@@ -283,6 +286,16 @@ function parseArgs(argv) {
283
286
  continue;
284
287
  }
285
288
 
289
+ if (arg === '--badge-metric') {
290
+ parsed.badgeMetric = String(argv[++index] || '').toLowerCase();
291
+ continue;
292
+ }
293
+
294
+ if (arg.startsWith('--badge-metric=')) {
295
+ parsed.badgeMetric = arg.slice('--badge-metric='.length).toLowerCase();
296
+ continue;
297
+ }
298
+
286
299
  if (arg === '--format') {
287
300
  parsed.format = String(argv[++index] || '').toLowerCase();
288
301
  continue;
@@ -329,6 +342,10 @@ function parseArgs(argv) {
329
342
  throw new Error(`Invalid format "${parsed.format}". Expected text, json, json-v1, markdown, html, or github.`);
330
343
  }
331
344
 
345
+ if (!BADGE_METRICS.has(parsed.badgeMetric)) {
346
+ throw new Error(`Invalid badge metric "${parsed.badgeMetric}". Expected drift or findings.`);
347
+ }
348
+
332
349
  if (parsed.since && parsed.staged) {
333
350
  throw new Error('Use either --since or --staged, not both.');
334
351
  }
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * shields.io endpoint badge: https://shields.io/badges/endpoint
5
+ *
6
+ * `drift` reports 100 minus scale cleanliness as a percentage (the share of
7
+ * scanned files with at least one finding). `findings` reports the number of
8
+ * off-scale CSS values plus Tailwind class-string findings.
9
+ */
10
+ const BADGE_METRICS = new Set(['drift', 'findings']);
11
+
12
+ const THRESHOLDS = {
13
+ drift: [[2, 'brightgreen'], [5, 'green'], [15, 'yellow']],
14
+ findings: [[0, 'brightgreen'], [10, 'green'], [50, 'yellow']],
15
+ };
16
+
17
+ function badgeColor(metric, value) {
18
+ for (const [limit, color] of THRESHOLDS[metric]) {
19
+ if (value <= limit) {
20
+ return color;
21
+ }
22
+ }
23
+ return 'orange';
24
+ }
25
+
26
+ function badgeValue(report, metric) {
27
+ if (metric === 'drift') {
28
+ const cleanliness = Number.isFinite(report.scaleCleanliness) ? report.scaleCleanliness : 100;
29
+ return Math.max(0, Math.min(100, 100 - cleanliness));
30
+ }
31
+ const css = (report.findings && report.findings.css) || [];
32
+ const tailwind = (report.findings && report.findings.tailwind) || [];
33
+ return css.filter((finding) => finding.type === 'off-scale').length + tailwind.length;
34
+ }
35
+
36
+ function renderBadge(report, { metric = 'drift' } = {}) {
37
+ if (!BADGE_METRICS.has(metric)) {
38
+ throw new Error(`Unknown badge metric "${metric}". Use one of: ${Array.from(BADGE_METRICS).join(', ')}.`);
39
+ }
40
+ const value = badgeValue(report, metric);
41
+ const badge = {
42
+ schemaVersion: 1,
43
+ label: metric === 'drift' ? 'spacing drift' : 'off-scale values',
44
+ message: metric === 'drift' ? `${value}%` : String(value),
45
+ color: badgeColor(metric, value),
46
+ };
47
+ return `${JSON.stringify(badge, null, 2)}\n`;
48
+ }
49
+
50
+ module.exports = {
51
+ BADGE_METRICS,
52
+ badgeColor,
53
+ renderBadge,
54
+ };
@@ -18,7 +18,7 @@ const DEFAULT_AUDIT_TOKEN_PATTERN = '^--(space|spacing)-';
18
18
 
19
19
  const DEFAULT_TOKEN_CANDIDATE_MIN_COUNT = 2;
20
20
 
21
- const VALID_FORMATS = new Set(['text', 'json', 'json-v1', 'markdown', 'html', 'github']);
21
+ const VALID_FORMATS = new Set(['text', 'json', 'json-v1', 'markdown', 'html', 'github', 'badge']);
22
22
 
23
23
  const SKIP_DIRS = new Set([
24
24
  '.git',
@@ -69,6 +69,7 @@ function createDefaultAuditOptions() {
69
69
  configPath: DEFAULT_CONFIG_PATH,
70
70
  dir: null,
71
71
  failOnNewDrift: false,
72
+ badgeMetric: 'drift',
72
73
  format: 'text',
73
74
  ignorePath: DEFAULT_IGNORE_PATH,
74
75
  ignorePatterns: [],
package/src/cli/audit.js CHANGED
@@ -13,6 +13,7 @@ const {
13
13
  toAuditContractReport,
14
14
  } = require('../audit/contract');
15
15
  const { renderGithub } = require('../audit/render-github');
16
+ const { renderBadge } = require('../audit/render-badge');
16
17
  const { renderHtml } = require('../audit/render-html');
17
18
  const { renderMarkdown } = require('../audit/render-markdown');
18
19
  const { renderText } = require('../audit/render-text');
@@ -73,6 +74,12 @@ async function run() {
73
74
  return;
74
75
  }
75
76
 
77
+ if (parsed.format === 'badge') {
78
+ writeOutput(renderBadge(report, { metric: parsed.badgeMetric }), parsed.outputPath);
79
+ finish(auditFailures);
80
+ return;
81
+ }
82
+
76
83
  if (parsed.format === 'github') {
77
84
  writeOutput(renderGithub(report), parsed.outputPath);
78
85
  finish(auditFailures);
@@ -108,6 +115,7 @@ module.exports = {
108
115
  createAuditReport,
109
116
  loadAuditConfig,
110
117
  parseArgs,
118
+ renderBadge,
111
119
  renderHtml,
112
120
  run,
113
121
  toAuditContractReport,
package/src/cli/index.js CHANGED
@@ -12,6 +12,7 @@ Commands:
12
12
  quickstart Same as running with no command
13
13
  audit <dir> Report design-system drift across CSS and Tailwind class strings
14
14
  init Scaffold a Rhythmguard config for your project
15
+ --agents <claude|cursor|copilot|all> installs the agent instruction packs instead
15
16
  doctor Validate your Rhythmguard setup
16
17
 
17
18
  Options:
@@ -22,6 +23,7 @@ Examples:
22
23
  npx rhythmguard audit ./src
23
24
  npx rhythmguard audit ./src --format markdown
24
25
  npx rhythmguard init
26
+ npx rhythmguard init --agents all
25
27
  npx rhythmguard doctor
26
28
  `;
27
29
 
package/src/cli/init.js CHANGED
@@ -98,7 +98,84 @@ function selectProfile(stack) {
98
98
  return 'recommended';
99
99
  }
100
100
 
101
+ const AGENT_TARGETS = {
102
+ claude: { source: 'claude-code/SKILL.md', target: path.join('.claude', 'skills', 'rhythmguard', 'SKILL.md'), mode: 'write' },
103
+ cursor: { source: 'cursor/rhythmguard.mdc', target: path.join('.cursor', 'rules', 'rhythmguard.mdc'), mode: 'write' },
104
+ copilot: { source: 'copilot/copilot-instructions.md', target: path.join('.github', 'copilot-instructions.md'), mode: 'append' },
105
+ };
106
+ const AGENT_BLOCK_START = '<!-- rhythmguard:agents:start -->';
107
+ const AGENT_BLOCK_END = '<!-- rhythmguard:agents:end -->';
108
+
109
+ function parseInitArgs(argv) {
110
+ const parsed = { agents: null };
111
+ for (let index = 0; index < argv.length; index += 1) {
112
+ const arg = argv[index];
113
+ if (arg === '--agents') {
114
+ parsed.agents = String(argv[++index] || 'all').toLowerCase();
115
+ } else if (arg.startsWith('--agents=')) {
116
+ parsed.agents = arg.slice('--agents='.length).toLowerCase();
117
+ }
118
+ }
119
+ return parsed;
120
+ }
121
+
122
+ /**
123
+ * Copies the generated packs under <package>/agents into the locations each
124
+ * agent reads. Claude Code and Cursor files are owned by Rhythmguard and
125
+ * overwritten; copilot-instructions.md is shared, so the block is appended
126
+ * between markers and replaced in place on later runs.
127
+ */
128
+ function installAgents(selection, cwd = process.cwd()) {
129
+ const names = selection === 'all' ? Object.keys(AGENT_TARGETS) : [selection];
130
+ const unknown = names.filter((name) => !AGENT_TARGETS[name]);
131
+ if (unknown.length > 0) {
132
+ throw new Error(`Unknown agents target "${unknown[0]}". Use claude, cursor, copilot or all.`);
133
+ }
134
+
135
+ const packsDir = path.join(__dirname, '..', '..', 'agents');
136
+ const written = [];
137
+ for (const name of names) {
138
+ const { mode, source, target } = AGENT_TARGETS[name];
139
+ const content = fs.readFileSync(path.join(packsDir, source), 'utf8');
140
+ const targetPath = path.join(cwd, target);
141
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
142
+
143
+ if (mode === 'append' && fs.existsSync(targetPath)) {
144
+ const existing = fs.readFileSync(targetPath, 'utf8');
145
+ const block = `${AGENT_BLOCK_START}\n${content.trimEnd()}\n${AGENT_BLOCK_END}\n`;
146
+ const start = existing.indexOf(AGENT_BLOCK_START);
147
+ const end = existing.indexOf(AGENT_BLOCK_END);
148
+ const next = start !== -1 && end !== -1
149
+ ? `${existing.slice(0, start)}${block}${existing.slice(end + AGENT_BLOCK_END.length).replace(/^\n/, '')}`
150
+ : `${existing.trimEnd()}\n\n${block}`;
151
+ fs.writeFileSync(targetPath, next);
152
+ } else {
153
+ fs.writeFileSync(targetPath, content);
154
+ }
155
+ written.push(target);
156
+ }
157
+ return written;
158
+ }
159
+
101
160
  async function run() {
161
+ const args = parseInitArgs(process.argv.slice(3));
162
+
163
+ if (args.agents) {
164
+ let written;
165
+ try {
166
+ written = installAgents(args.agents);
167
+ } catch (error) {
168
+ process.stderr.write(`${error.message}\n`);
169
+ process.exit(1);
170
+ }
171
+ process.stdout.write('\nRhythmguard agent packs\n\n');
172
+ for (const file of written) {
173
+ process.stdout.write(`✓ Wrote ${file}\n`);
174
+ }
175
+ process.stdout.write('\nThe block is the one in docs/FOR_AGENTS.md. Trim it to the lines that apply.\n\n');
176
+ return;
177
+ }
178
+
102
179
  const prompter = createPrompter();
103
180
 
104
181
  try {
@@ -152,7 +229,7 @@ async function run() {
152
229
  }
153
230
  }
154
231
 
155
- module.exports = { detect, run, selectProfile };
232
+ module.exports = { AGENT_TARGETS, detect, installAgents, run, selectProfile };
156
233
 
157
234
  if (require.main === module) {
158
235
  run();
package/types/audit.d.ts CHANGED
@@ -27,7 +27,9 @@ export interface AuditOptions {
27
27
  configPath?: string;
28
28
  dir?: string;
29
29
  failOnNewDrift?: boolean;
30
- format?: "json" | "json-v1" | "markdown" | "text" | "html" | "github";
30
+ /** Badge value when `format` is "badge": drift percent or off-scale count. */
31
+ badgeMetric?: "drift" | "findings";
32
+ format?: "json" | "json-v1" | "markdown" | "text" | "html" | "github" | "badge";
31
33
  ignorePath?: string;
32
34
  ignorePatterns?: string[];
33
35
  includeMotion?: boolean;