@fantastic.dev/repo-gates 0.2.1-bootstrap.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kelly Kampen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,499 @@
1
+ # repo-gates
2
+
3
+ Config-driven quality gates for **turborepo** (and any) monorepos — one quiet
4
+ `check-all` command that runs your whole battery (lint, format, typecheck, tests)
5
+ alongside ratchet guards for file size, tech-debt markers, circular imports,
6
+ secret-shaped strings, shadcn UI quality, coverage, and bundle size, plus a
7
+ CI-parity drift detector and a PR docs-coverage gate. The engine is repo-agnostic; your policy
8
+ lives in a single `repo-gates.config.json`.
9
+
10
+ ## What it does
11
+
12
+ `check-all` runs an ordered manifest of gates and reports them **quietly** — one
13
+ aligned line per gate, a tally, and (on failure) the parsed failure signature
14
+ instead of a wall of logs:
15
+
16
+ ```text
17
+ ✓ lint (2.5s)
18
+ ✓ format:check (5.9s)
19
+ ✓ typecheck (0.5s)
20
+ ✓ check:size (0.3s)
21
+ ✓ check:debt (0.3s)
22
+ ✓ test (8.1s)
23
+ ✓ check:coverage (9.4s)
24
+ ✓ check:ci-parity (0.3s)
25
+
26
+ 8/8 gates passed (24.3s)
27
+
28
+ Scores:
29
+ coverage lowest packages/api 81.2% lines (12 pkgs ≥ floor)
30
+ file-size tightest src/app.ts 512/512 (0 to spare)
31
+ ```
32
+
33
+ The gates:
34
+
35
+ | Command | What it does |
36
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `check-all` | Runs the whole manifest quietly: aligned `✓/✗ gate (N.Ns)`, a tally, and parsed failure signatures (never the full log). On success prints a compact `Scores:` block. `--bail` stops at the first failure; `CHECK_ALL_VERBOSE=1` streams everything. |
38
+ | `check-ci-parity` | Fails when a `pnpm run <gate>` in `.github/workflows/ci*.yml` isn't reachable from `check-all` — kills CI/local drift. |
39
+ | `check-size` | Per-file line ceiling; large files are grandfathered and may only shrink. `--init` seeds baselines. |
40
+ | `check-debt` | `TODO/FIXME/HACK/XXX` must carry a tracker ref (`ABC-123` / `#123` / URL) or be allowlisted. `--init` seeds the allowlist. |
41
+ | `check-circular` | Flags new circular-import groups (relative imports within `scanRoots`, resolved into a graph, reduced to strongly-connected components). Existing cycles are grandfathered. `--init` seeds the allowlist. |
42
+ | `check-secrets` | Static scan of every **git-tracked** file for credential-shaped strings (AWS/GitHub/Slack/Stripe/npm/Google keys, PEM headers, userinfo-in-URL). Findings are reported as a redacted fingerprint — never the matched text. Not a substitute for a dedicated secret scanner (gitleaks/trufflehog): no entropy analysis, no git-history scan. `--init` seeds the allowlist — review every entry, it silences whatever it captures. |
43
+ | `check-agents` | Fails if a `pnpm run <x>` or a backticked path in `AGENTS.md` no longer resolves. |
44
+ | `check:design-system` | Optional consumer script using [@shadcn/lint](https://github.com/shadcn-ui/lint) to enforce Tailwind v4 design-system rules through ESLint or Oxlint. Defining the script includes it in `check-all`. |
45
+ | `check:shadscan` | Optional consumer script for React repositories using shadcn/ui. Runs a pinned [Shadscan](https://www.shadscan.com/docs) audit and fails below the repository's ratcheted score floor. Defining the script automatically adds it to `check-all`; non-shadcn repositories omit it. |
46
+ | `check-docs-coverage` | PR gate: a changed "surface" (config-defined glob) must come with a docs change, or a `docs: n/a - <reason>` opt-out in the PR body. Reads the changed-file list from the GitHub API (`GITHUB_REPOSITORY`/`PR_NUMBER`/`GITHUB_TOKEN`/`PR_BODY`); a no-op outside a PR context (safe to include in `check:all`). See [CI](#ci) for wiring it as its own `pull_request`-triggered job. |
47
+ | `check-coverage` | Holds **each package** to its own floor (no repo-wide aggregate — a high package can't mask a low one); unlisted packages must meet a `default`. Floors ratchet up. `--init` seeds; `--skip-run` reuses existing summaries. |
48
+ | `check-bundle-size` | Builds each configured target (turbo, cached), then ratchets raw+gzip totals AND the largest single chunk per bucket. `--init` re-baselines. |
49
+ | `report-test-timing` / `report-quality-metrics` | Non-gating dashboards → `$GITHUB_STEP_SUMMARY`. |
50
+
51
+ ## Why
52
+
53
+ - **One quiet command.** `check-all` is the single entry point — aligned pass/fail, a tally, and parsed failure signatures instead of a wall of logs. `--bail` stops early; `CHECK_ALL_VERBOSE=1` streams everything.
54
+ - **Ratchets, not fixed limits.** File size, tech debt, circular imports, secret-shaped strings, shadcn UI quality, coverage, and bundle size only move in the right direction. Seed each baseline from your current tree, so day one is green — no big cleanup up front.
55
+ - **Per-package coverage floors.** Each package is held to its own floor, so a well-covered package can't mask a thin one.
56
+ - **Docs don't drift behind the product.** `check-docs-coverage` blocks a PR that changes a user-facing surface without touching docs — unless the author opts out on the record.
57
+ - **CI ↔ local parity.** `check-ci-parity` fails if your CI workflow drifts from the `check-all` manifest, so "green locally" means "green in CI."
58
+ - **Config-driven & reusable.** The engine ships no repo-specific assumptions; drop it into any repo and describe policy in one JSON file.
59
+
60
+ ## How to use it
61
+
62
+ ### Install
63
+
64
+ ```bash
65
+ pnpm add -D @fantastic.dev/repo-gates
66
+ # or: npm i -D @fantastic.dev/repo-gates / yarn add -D @fantastic.dev/repo-gates
67
+ ```
68
+
69
+ Ships compiled JS + types — no build step or Node type-stripping required in your
70
+ repo (Node ≥ 18).
71
+
72
+ ### Migrating from @kellykampen/repo-gates
73
+
74
+ Maintenance has moved to [FantasticDevHQ/repo-gates](https://github.com/FantasticDevHQ/repo-gates)
75
+ and the npm package is now [@fantastic.dev/repo-gates](https://www.npmjs.com/package/@fantastic.dev/repo-gates).
76
+ Replace the dependency using your package manager, for example:
77
+
78
+ ```bash
79
+ pnpm remove @kellykampen/repo-gates
80
+ pnpm add -D @fantastic.dev/repo-gates
81
+ ```
82
+
83
+ Update imports from `@kellykampen/repo-gates` to `@fantastic.dev/repo-gates`,
84
+ including the `/config`, `/design-system`, and `/eslint-boundaries` exports.
85
+ Check existing `eslint.design-system.config.mjs` files too: `init` preserves
86
+ existing configuration files, so it will not rewrite their imports for you.
87
+ The `repo-gates` executable, package scripts, `repo-gates.config.json`, and
88
+ existing baselines continue to work without changes. The old npm package remains
89
+ available for existing installs; new releases use the `@fantastic.dev` scope.
90
+
91
+ ### Quickstart
92
+
93
+ ```bash
94
+ pnpm exec repo-gates init
95
+ pnpm run check:all
96
+ ```
97
+
98
+ `init` configures the repository, rather than only printing instructions. Run
99
+ it at the root containing `package.json`. It detects npm, pnpm, Yarn, or Bun
100
+ from `packageManager` or lockfiles, adds missing scripts, writes the gate
101
+ manifest, and installs applicable UI tools with that package manager.
102
+ Installation of repo-gates itself has no `postinstall` hook that modifies your
103
+ repository; initialization is an explicit command.
104
+
105
+ | Gate | What `init` does |
106
+ | --- | --- |
107
+ | File size, debt markers, circular imports | Adds scripts and seeds missing baselines from the current source. Existing baselines are never reseeded. |
108
+ | Secrets | Adds `check:secrets`, without automatically allowlisting findings. It scans Git-tracked files. |
109
+ | CI parity | Adds `check:ci-parity`. Run `check:all` from your CI workflow to enforce the generated manifest. |
110
+ | Design system | Detects Tailwind v4, installs pinned `@shadcn/lint` and a compatible linter/parser when absent, and configures all six rules as errors. |
111
+ | Shadscan | Detects `components.json` in root/workspace packages, installs `@shadscan/cli@0.7.0`, and checks each detected project with an initial floor of 80. |
112
+ | Agent docs | Enables `check:agents` when `AGENTS.md` files or configured targets exist. |
113
+ | Coverage | Enables `check:coverage` when `test:coverage` exists. Configure that script to emit `json-summary` reports, then run `repo-gates check-coverage --init` to measure the initial floors. |
114
+ | Docs coverage, bundle size | Enables their scripts when `docsCoverage.surfaces` or `bundleSize.targets` are configured. See the respective policy sections below. |
115
+ | Existing lint, format, typecheck, test, dependency and duplication checks | Includes existing scripts whose names match the default manifest. It does not invent framework-specific commands. Missing core scripts are listed at completion. |
116
+
117
+ A fresh config scans the repository root, excludes dependency/build directories,
118
+ and includes JS and TS source extensions. Workspace discovery reads
119
+ `package.json` workspaces or `pnpm-workspace.yaml`, supporting literal paths,
120
+ `*`, `**`, and `!` exclusions. It detects installed dependency versions or
121
+ ordinary declared semver ranges, including pnpm catalogs. Existing lint configs
122
+ remain untouched; the design-system pass gets a separate generated config.
123
+ ESLint is preferred when declared at the root; an existing root Oxlint setup
124
+ is used otherwise. If neither is present, initialization adds ESLint.
125
+
126
+ Re-running `init` preserves existing scripts, lint configs, explicit policy
127
+ values, and baselines, while adding missing applicable gates. Existing custom
128
+ manifests keep deliberate omissions of scripts that were already present;
129
+ newly added gate scripts are inserted into the manifest. Review the generated
130
+ files and commit them with your lockfile. An install failure exits nonzero and
131
+ leaves the generated files available for repair; run your package manager's
132
+ install command or rerun `init` to retry.
133
+
134
+ ```bash
135
+ repo-gates init --skip-install # write dependencies/configs; install later
136
+ repo-gates init --no-design-system # skip automatic Tailwind rule setup
137
+ repo-gates init --no-shadscan # skip automatic Shadscan setup
138
+ repo-gates init --shadscan-floor 70 # choose an initial integer floor, 0–100
139
+ ```
140
+
141
+ Opt-out flags skip setup; they do not remove existing scripts or dependencies.
142
+ To disable an existing gate, remove its entry from `repo-gates.config.json`.
143
+ The initial Shadscan floor is a policy choice, not a measured baseline. It may
144
+ fail on the first run. Raise it as findings are fixed, and never lower an
145
+ established floor to pass a regression. UI tooling needs Node 20.19+ for the
146
+ ESLint setup; Oxlint requires Node 20.19.x or 22.12+.
147
+
148
+ ### Wire it up
149
+
150
+ ```jsonc
151
+ {
152
+ "scripts": {
153
+ "check:all": "repo-gates check-all",
154
+ "check:size": "repo-gates check-size",
155
+ "check:debt": "repo-gates check-debt",
156
+ "check:shadscan": "pnpm dlx @shadscan/cli@0.7.0 ./apps/web --json --fail-under 40 --no-roast --no-interactive",
157
+ "check:coverage": "repo-gates check-coverage",
158
+ "check:ci-parity": "repo-gates check-ci-parity"
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### Add the Shadscan ratchet for shadcn repositories
164
+
165
+ `init` adds this gate automatically for detected shadcn/ui projects. For manual
166
+ setup, only define `check:shadscan` when the repository uses shadcn/ui. Run Shadscan
167
+ once against the React application package, choose a conservative floor below
168
+ or equal to the assessed score, and commit that floor as the starting ratchet:
169
+
170
+ ```bash
171
+ pnpm dlx @shadscan/cli@0.7.0 ./apps/web --json --fail-under 40 --no-roast --no-interactive
172
+ ```
173
+
174
+ Keep the CLI version exact so the same source is evaluated by the same ruleset
175
+ locally and in CI. The default gate manifest treats `check:shadscan` as
176
+ conditional: defining the package script includes it in `check-all` (and any
177
+ pre-commit hook that runs `check-all`); omitting it leaves non-shadcn
178
+ repositories unaffected. Raise `--fail-under` as findings are remediated, and
179
+ never lower it to make a regression pass. See the [Shadscan pre-commit
180
+ documentation](https://www.shadscan.com/docs#pre-commit) for hook-manager-specific
181
+ wiring.
182
+
183
+ ### Add design-system checks with @shadcn/lint
184
+
185
+ `repo-gates init` creates this setup automatically for detected Tailwind v4
186
+ repositories. For manual setup, define `check:design-system` to run a separate
187
+ design-system gate immediately after `lint`. The default manifest skips it
188
+ when the script is absent. This is a pass/fail check, with no score or ratchet.
189
+ Shadcn/ui is optional; custom Tailwind design systems are supported too.
190
+
191
+ Install the plugin in the consumer package that owns the lint configuration.
192
+ It requires Node **20.19+** and ESLint **9.30+**, or Oxlint **1.80+**.
193
+ The optional gate does not change repo-gates' own Node 18 requirement.
194
+
195
+ ```bash
196
+ pnpm add -D -E @shadcn/lint@0.1.5
197
+ # If ESLint and a TSX parser are not already installed:
198
+ pnpm add -D -E eslint@^9.30.0 @typescript-eslint/parser@^8.40.0
199
+ ```
200
+
201
+ Our default setup enables all six rules as errors. For a separate ESLint pass,
202
+ create `eslint.design-system.config.mjs`:
203
+
204
+ ```js
205
+ import { plugin as shadcn } from "@shadcn/lint";
206
+ import tsParser from "@typescript-eslint/parser";
207
+ import { designSystemRules } from "@fantastic.dev/repo-gates/design-system";
208
+
209
+ export default [
210
+ { ignores: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/coverage/**"] },
211
+ {
212
+ files: ["**/*.{js,jsx,ts,tsx}"],
213
+ languageOptions: {
214
+ parser: tsParser,
215
+ parserOptions: { ecmaFeatures: { jsx: true } },
216
+ },
217
+ plugins: { shadcn },
218
+ rules: {
219
+ ...designSystemRules,
220
+ // Add consumer overrides here, after the preset.
221
+ },
222
+ },
223
+ {
224
+ // Adjust to the directories where your design-system components are defined.
225
+ files: ["src/components/ui/**", "components/ui/**", "packages/ui/src/components/**"],
226
+ rules: { "shadcn/no-restyle": "off" },
227
+ },
228
+ ];
229
+ ```
230
+
231
+ Add the consumer script, adjusting the source path to your application:
232
+
233
+ ```json
234
+ {
235
+ "scripts": {
236
+ "check:design-system": "eslint --config eslint.design-system.config.mjs src --max-warnings 0"
237
+ }
238
+ }
239
+ ```
240
+
241
+ Run `pnpm run check:design-system`, then `pnpm exec repo-gates check-all`.
242
+ If your repository overrides the `gates` array, add
243
+ `{ "name": "check:design-system", "conditional": true }` to that array;
244
+ arrays replace the defaults in full.
245
+
246
+ #### Update the rules
247
+
248
+ The shipped `@fantastic.dev/repo-gates/design-system` preset is the source of
249
+ ESLint defaults. Edit your consumer's `eslint.design-system.config.mjs` and add
250
+ rule overrides **after** `...designSystemRules`. Generated Oxlint setups use
251
+ `.oxlintrc.design-system.json`; edit the rules in its first `overrides` entry,
252
+ which contains a copy of the same defaults scoped to the detected UI packages.
253
+ The defaults are:
254
+
255
+ | Rule | What it checks |
256
+ | --- | --- |
257
+ | `shadcn/no-restyle` | Styling overrides on design-system components; layout classes are allowed. |
258
+ | `shadcn/no-raw-colors` | Colors that bypass the design-system theme. |
259
+ | `shadcn/no-arbitrary-values` | Arbitrary Tailwind values such as `p-[13px]`. |
260
+ | `shadcn/no-inline-styles` | Inline styles and `<style>` elements. |
261
+ | `shadcn/no-unknown-classes` | Classes that Tailwind cannot generate. |
262
+ | `shadcn/require-static-classes` | Component class values the linter cannot statically read. |
263
+
264
+ All six use `error`. The existing component-directory override turns off only
265
+ `no-restyle`, so components can define their own styles. The other five remain
266
+ active there too. Upstream recommends also exempting component definitions from
267
+ `no-arbitrary-values` and `require-static-classes` when needed; our default
268
+ keeps those checks enabled until you explicitly add an exception.
269
+
270
+ To disable a rule, set its value to `"off"`. To change its options, use an array
271
+ such as `["error", { allow: ["layout", "spacing"] }]` for `no-restyle`.
272
+ For a scoped exception, append a config entry after the defaults, for example:
273
+
274
+ ```js
275
+ {
276
+ files: ["src/components/ui/**"],
277
+ rules: {
278
+ "shadcn/no-arbitrary-values": "off",
279
+ "shadcn/require-static-classes": "off",
280
+ },
281
+ }
282
+ ```
283
+
284
+ Adjust the glob to your component directory. A later matching ESLint entry
285
+ replaces the earlier setting for that rule. `"warn"` is also supported, but
286
+ our script uses `--max-warnings 0`, so warnings still fail the gate. Remove
287
+ that flag or raise its limit if warnings should be non-blocking.
288
+
289
+ Component and theme discovery uses `components.json`; custom imports and shared
290
+ UI packages may need `settings.shadcn` configuration. See the
291
+ [shadcn/lint repository](https://github.com/shadcn-ui/lint),
292
+ [rule options](https://github.com/shadcn-ui/lint/blob/main/docs/rules.md), and
293
+ [settings](https://github.com/shadcn-ui/lint#settings).
294
+ After editing the configuration, run `pnpm run check:design-system`.
295
+ Existing consumers can run `repo-gates init` to add missing gate setup. Existing
296
+ design-system scripts and configurations are preserved; adopt the preset
297
+ explicitly if your config still lists rules individually. Upgrading repo-gates
298
+ can update the imported ESLint preset; Oxlint's copied rules remain unchanged
299
+ until you edit them. New upstream rules are not enabled merely by upgrading
300
+ `@shadcn/lint`. Review rule changes when upgrading repo-gates.
301
+
302
+ You can instead register the plugin in your existing ESLint or Oxlint config
303
+ and run it through the existing `lint` gate. In that case, omit the separate
304
+ script to avoid checking the same rules twice. Oxlint uses
305
+ `"jsPlugins": ["@shadcn/lint"]`; its JS plugin API is currently alpha.
306
+ See the upstream [setup instructions](https://github.com/shadcn-ui/lint#get-started).
307
+ Keep versions pinned and commit the consumer lockfile for consistent CI results.
308
+
309
+ ## How it finds your repo
310
+
311
+ Every command resolves the repo root from `process.cwd()` and loads
312
+ `repo-gates.config.json` from there (falling back to the built-in `DEFAULT_CONFIG`).
313
+ The config is a **partial overlay** on the defaults — set only what differs.
314
+
315
+ ## Configuration
316
+
317
+ `repo-gates.config.json` is a **partial overlay** on the built-in defaults — set only
318
+ what differs from your repo. JSON is parsed strictly (no `//` comments); use a
319
+ `"$comment"` key for inline notes, as below.
320
+
321
+ ### Example
322
+
323
+ A realistic config for a pnpm + turbo monorepo (an Electron app, a web app, shared
324
+ packages) — the annotated version below is jsonc for readability; a copy-pasteable,
325
+ strictly-valid `repo-gates.config.json` (comments as `"$comment"` keys instead of
326
+ `//`) lives at [`examples/repo-gates.config.json`](./examples/repo-gates.config.json):
327
+
328
+ ```jsonc
329
+ {
330
+ "$comment": "Anything omitted falls back to DEFAULT_CONFIG.",
331
+ "runner": "pnpm run",
332
+
333
+ // Each package/app's vitest coverage-summary.json — check-coverage holds each to
334
+ // its own floor (gates/coverage-budgets.json), seeded by `check-coverage --init`.
335
+ "coverage": {
336
+ "summaryGlobs": [
337
+ "apps/*/coverage/coverage-summary.json",
338
+ "packages/*/coverage/coverage-summary.json"
339
+ ]
340
+ },
341
+
342
+ // Build + measure bundle budgets. Each target is built via
343
+ // `turbo run build --filter <filter>`, then dist is measured by bucket.
344
+ "bundleSize": {
345
+ "targets": [
346
+ { "name": "web", "filter": "@acme/web", "distDir": "apps/web/dist",
347
+ "buckets": { "js": [".js"], "css": [".css"] } }
348
+ ]
349
+ },
350
+
351
+ // check-agents: keep AGENTS.md's `pnpm run <script>` + backticked paths resolving.
352
+ "agents": { "targets": ["AGENTS.md"] },
353
+
354
+ // report-test-timing reads these junit files (non-gating dashboard).
355
+ "report": { "junitGlobs": ["apps/*/test-results/junit.xml", "packages/*/test-results/junit.xml"] },
356
+
357
+ // Architectural import rules → ESLint (see "Import boundaries" below).
358
+ "boundaries": [
359
+ {
360
+ // ONE cross-app rule for every app (message written once). Safe as long as
361
+ // apps don't import their own package by name.
362
+ "name": "no-cross-app",
363
+ "files": ["apps/**"],
364
+ "patterns": [
365
+ { "forbid": ["@acme/web", "@acme/web/**", "@acme/desktop", "@acme/desktop/**"],
366
+ "message": "Apps must not import each other — share via packages/*." }
367
+ ]
368
+ },
369
+ {
370
+ // Nested scope: inherits no-cross-app's patterns via `extends` (no copy-paste),
371
+ // and adds its own.
372
+ "name": "desktop-renderer",
373
+ "files": ["apps/desktop/src/renderer/**"],
374
+ "ignores": ["**/*.test.ts", "**/*.test.tsx"],
375
+ "extends": ["no-cross-app"],
376
+ "patterns": [
377
+ { "forbid": ["node:*", "better-sqlite3", "**/main/**"],
378
+ "allowTypeImports": true,
379
+ "message": "Renderer is a browser context — reach main via IPC, not a value import (import type is fine)." }
380
+ ]
381
+ }
382
+ ]
383
+ }
384
+ ```
385
+
386
+ ### Reference
387
+
388
+ | Key | Default | Purpose |
389
+ | --- | --- | --- |
390
+ | `runner` | `"pnpm run"` | How a gate script is invoked (`"pnpm run"`, `"bun run"`, `"npm run"`). |
391
+ | `gates` | 18-gate manifest | Ordered `{ name, conditional }[]`. A non-conditional gate in the selected manifest is required: it runs even when its script is missing, so the run fails loudly. Conditional gates run only if their script exists. Overriding this array replaces the default policy, so it is valid to omit `test` when `check:coverage` runs the complete suite with coverage (avoids executing the suite twice). |
392
+ | `scanRoots` | `["apps","packages","scripts"]` | Roots the file-size + debt + circular-import walkers scan. |
393
+ | `excludeDirSegments` | `node_modules`, `dist`, `out`, `.turbo`, `coverage`, … | Directory names pruned from scans. |
394
+ | `excludePathPrefixes` | `[]` | Repo-relative path prefixes excluded from scans. |
395
+ | `sourceExtensions` | `[".ts",".tsx"]` | Extensions the size/debt/circular-import guards treat as source. |
396
+ | `fileSize.threshold` | `600` | Default per-file line ceiling (larger files are grandfathered in the budgets file). |
397
+ | `fileSize.budgetsPath` | `gates/file-size-budgets.json` | Grandfathered per-file budgets (`check-size --init` seeds). |
398
+ | `debt.markerTokens` | `["TODO","FIXME","HACK","XXX"]` | Tokens that must carry a tracker reference. |
399
+ | `debt.trackerPatterns` | `ABC-123`, `#123`, URL | Regex sources for a valid tracker reference. |
400
+ | `debt.allowlistPath` | `gates/debt-marker-allowlist.json` | Untracked-marker allowlist (`check-debt --init` seeds). |
401
+ | `circular.allowlistPath` | `gates/circular-imports-allowlist.json` | Grandfathered circular-import groups (`check-circular --init` seeds). |
402
+ | `secrets.patterns` | AWS/GitHub/Slack/Stripe/npm/Google key shapes, PEM headers, URL creds | Regex sources tested against every git-tracked line. |
403
+ | `secrets.binaryExtensions` | images, fonts, archives, media | Extensions skipped as non-text. |
404
+ | `secrets.allowlistPath` | `gates/secrets-allowlist.json` | Grandfathered findings (`check-secrets --init` seeds — review before trusting). |
405
+ | `docsCoverage.surfaces` | `[]` (no-op) | `[{ label, glob, on: "added"\|"changed" }]` — user-facing surfaces that require docs when changed. |
406
+ | `docsCoverage.docsGlobs` | `[]` | Globs a PR must touch for a triggered surface to count as documented. |
407
+ | `docsCoverage.exclude` | `[]` | Globs removed from both surface and docs matching (tests, fixtures). |
408
+ | `coverage.summaryGlobs` | `apps/*`, `packages/*` | Globs matching each package's `coverage-summary.json`. |
409
+ | `coverage.budgetsPath` | `gates/coverage-budgets.json` | Per-package floors (`check-coverage --init` seeds; floors ratchet up). |
410
+ | `bundleSize.targets` | `[]` (no-op) | `[{ name, filter, distDir, buckets }]` — built via turbo, then raw+gzip+largest-chunk ratcheted. |
411
+ | `bundleSize.budgetsPath` | `gates/bundle-size-budgets.json` | Bundle baselines. |
412
+ | `agents.targets` | `[]` | Agent docs (e.g. `["AGENTS.md"]`) whose `pnpm run <x>` + backticked paths must resolve. |
413
+ | `report.junitGlobs` | `[]` | junit files for the `report-test-timing` dashboard. |
414
+ | `report.topN` | `20` | Slowest-tests cutoff in that dashboard. |
415
+ | `ciParity.{configPath,rootGate,entryGates,workflowPrefix}` | `gates/ci-parity-config.json`, `check:all`, `["check:all","verify"]`, `ci` | Inputs to the CI-parity reachability graph. |
416
+ | `boundaries` | `[]` | Import-boundary rules → ESLint (below). |
417
+
418
+ The full `RepoGatesConfig` type is exported from the package for editor autocompletion.
419
+
420
+ ### Import boundaries (ESLint)
421
+
422
+ Architectural import rules are _data_ in `repo-gates.config.json` under `boundaries`;
423
+ the transform `@fantastic.dev/repo-gates/eslint-boundaries` turns them into
424
+ `@typescript-eslint/no-restricted-imports` flat configs you spread into your
425
+ `eslint.config.mjs`:
426
+
427
+ ```js
428
+ import { boundariesToEslintConfigs } from "@fantastic.dev/repo-gates/eslint-boundaries";
429
+ import repoGates from "./repo-gates.config.json" with { type: "json" };
430
+
431
+ export default [
432
+ // …your other flat configs…
433
+ ...boundariesToEslintConfigs(repoGates.boundaries ?? []),
434
+ ];
435
+ ```
436
+
437
+ Each boundary is one file scope carrying pattern groups; per-group `allowTypeImports`
438
+ lets a browser context still `import type` a Node-only module. `ignores` exempts files
439
+ (commonly tests, which run in Node).
440
+
441
+ Because ESLint flat config is **last-wins per rule**, a file matched by several
442
+ boundaries only keeps the *last* one's patterns — so a nested scope (`renderer/**` ⊂
443
+ `apps/**`) must carry the broader patterns too. Instead of copy-pasting them, use
444
+ **`extends`**: `{ "name": "desktop-renderer", "extends": ["no-cross-app"], … }` merges
445
+ the named boundaries' patterns in ahead of its own (resolved transitively, cycles
446
+ rejected). Author each rule — and its message — **once**, at its natural scope.
447
+
448
+ **Scores protocol:** any gate contributes a headline to `check-all`'s success
449
+ `Scores:` block by printing `SCORE: <label> — <value>` on success; `check-all`
450
+ collects and aligns them.
451
+
452
+ ## CI
453
+
454
+ Run the battery as one job step (Node ≥ 18, deps installed):
455
+
456
+ ```yaml
457
+ - run: pnpm exec repo-gates check-all
458
+ ```
459
+
460
+ Keep the CI workflow and the `check-all` manifest in lock-step with
461
+ `repo-gates check-ci-parity`.
462
+
463
+ Full copy-paste-able GitHub Actions workflows, from a single `check-all` step
464
+ up to a per-gate turborepo battery with remote caching, live in
465
+ [`examples/github-actions/`](./examples/github-actions):
466
+
467
+ - [`minimal.yml`](./examples/github-actions/minimal.yml) — one `check-all` step.
468
+ - [`single-package.yml`](./examples/github-actions/single-package.yml) — gates
469
+ broken into individual steps for a single-package (or lightly-workspaced) repo.
470
+ - [`monorepo-turborepo.yml`](./examples/github-actions/monorepo-turborepo.yml) —
471
+ the full battery (deps/dups/size/debt/circular/secrets/agents/bundle-size/coverage
472
+ ratchets + Turbo remote cache) for a pnpm + turbo monorepo.
473
+ - [`docs-coverage.yml`](./examples/github-actions/docs-coverage.yml) — `check-docs-coverage`
474
+ wired as its own `pull_request`-triggered job, passing `GITHUB_TOKEN`/`PR_NUMBER`/`PR_BODY`
475
+ from the event and checking out the PR's **base** commit (tamper-resistant — a PR can't
476
+ narrow its own docs-coverage policy to dodge the gate). Separate from the other examples
477
+ because it's a PR-diff gate, not part of the local `check:all` battery — see the config's
478
+ `docsCoverage` docs above.
479
+
480
+ ## Programmatic use
481
+
482
+ ```ts
483
+ import { loadContext, runCheckAll } from "@fantastic.dev/repo-gates";
484
+
485
+ process.exitCode = runCheckAll(loadContext(), { verbose: false });
486
+ ```
487
+
488
+ ## Development
489
+
490
+ ```bash
491
+ pnpm install
492
+ pnpm test # vitest
493
+ pnpm run typecheck # tsc --noEmit
494
+ pnpm run build # tsup → dist/ (esm + d.ts)
495
+ ```
496
+
497
+ ## License
498
+
499
+ [MIT](./LICENSE) © Kelly Kampen
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node