@alexcodeplace/slopgate 0.1.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 ADDED
@@ -0,0 +1,720 @@
1
+ # slopgate
2
+
3
+ A global code-quality / anti-slop gate for Claude Code and git. Engine is shared, rules are per-project.
4
+
5
+ **What it does:** Catches code quality violations in two tiers — a fast post-edit scan (regex + AST rules, instant feedback) and a heavy commit-tier scan (static type checkers, dead-code analysis, architecture rules, copy-paste detection). A **ratchet baseline** lets legacy repos adopt without flooding — only NEW violations block commits; pre-existing ones are baselined and tracked for paydown.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **Two-tier gate**
12
+ - **Fast tier** (post-edit hook): regex patterns + AST rules, instant feedback as you code
13
+ - **Commit tier** (pre-commit hook): includes heavy checkers (tsc, knip, jscpd, dependency-cruiser, type-coverage, diff-shape) + AST + regex, blocks commits
14
+
15
+ - **Ratchet baseline** — snapshot violations at adoption time; only NEW violations fail the gate. Track debt paydown over time.
16
+
17
+ - **Six commit-tier checkers**
18
+ - **tsc** — TypeScript type errors (full-project scope)
19
+ - **knip** — dead/unused code (exports, files, dependencies)
20
+ - **jscpd** — copy-paste duplication (token-level)
21
+ - **dependency-cruiser** — architecture rules (cycles, orphans, layer boundaries)
22
+ - **type-coverage** — propagation of `any` type (per-expression tracking)
23
+ - **diff-shape** — wide commits spanning too many directories (encourages focused changes)
24
+
25
+ - **Shared regex + AST rule packs** — fast-tier and commit-tier both run these
26
+ - Convention: `no-stubs`, `ts-suppress`, `as-any`, `raw-hex` (design tokens), `sql-safety`
27
+ - Security: `live-secrets`, `eval-ban`, `pii-logs`, `weak-hash`
28
+ - Cloudflare boundary: `kv-ban` (plus the opt-in `stack = ["cloudflare"]` pack)
29
+ - Built-in AST rules: empty-catch, unsafe `innerHTML`/`dangerouslySetInnerHTML`, `target="_blank"` without `rel`, `window` access during render
30
+
31
+ - **Native git pre-commit hook** — no daemon, no CI coupling, just git
32
+
33
+ - **Claude Code integration** — hooks into PreToolUse (commit) and PostToolUse (edit) events
34
+
35
+ - **Suppressions** — per-file, per-line, with line-hash stability across edits
36
+
37
+ - **Self-test** — `slopgate --self-test` validates rule engines + baseline checker parsers against bundled fixtures
38
+
39
+ ---
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install -g slopgate
45
+ ```
46
+
47
+ The matching prebuilt native engine for your platform (linux / macOS / Windows × x64 / arm64) is pulled in automatically as an optional dependency — no toolchain or build step required.
48
+
49
+ Then onboard a project:
50
+
51
+ ```bash
52
+ slopgate init [path-to-repo]
53
+ ```
54
+
55
+ This:
56
+ 1. Detects TypeScript roots, file extensions, and package layout
57
+ 2. Scaffolds `.slopgate/config.toml` with detected checkers enabled
58
+ 3. Writes `.slopgate/suppressions.json` and `.slopgate/depcruise.cjs` (starter)
59
+ 4. Creates `.slopgate/convention-sources.json` (hints for authoring project rules from local skills/agents/docs)
60
+ 5. Creates `.slopgate/rules/ast/` and `.slopgate/fixtures/src/` directories
61
+ 6. Installs git pre-commit hook (or appends to existing)
62
+ 7. Merges Claude Code hook settings into `.claude/settings.json`
63
+ 8. Prints next steps (including: run `slopgate baseline --config .slopgate/config.toml`)
64
+
65
+ ---
66
+
67
+ ## Quickstart
68
+
69
+ ### Run the gate on staged changes (pre-commit):
70
+ ```bash
71
+ slopgate --staged --config .slopgate/config.toml
72
+ ```
73
+
74
+ ### Run on a single file (post-edit, fast tier):
75
+ ```bash
76
+ slopgate --file src/app.ts --config .slopgate/config.toml
77
+ ```
78
+
79
+ ### Create/update the baseline:
80
+ ```bash
81
+ # Create baseline (refuses if it exists)
82
+ slopgate baseline --config .slopgate/config.toml
83
+
84
+ # Update baseline (re-snapshot all current violations)
85
+ slopgate baseline --update --config .slopgate/config.toml
86
+
87
+ # Prune baseline (remove entries no longer occurring)
88
+ slopgate baseline --prune --config .slopgate/config.toml
89
+ ```
90
+
91
+ ### Run self-test (validate the engine against bundled fixtures):
92
+ ```bash
93
+ slopgate --self-test --config "$(npm root -g)/slopgate/rules/baseline/selftest.config.toml"
94
+ ```
95
+
96
+ ### Install or reinstall hooks:
97
+ ```bash
98
+ slopgate install-hooks --config .slopgate/config.toml
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Command Reference
104
+
105
+ ### `slopgate init [dir]`
106
+ Onboard a new repository. Detects roots, extensions, installed checkers, and scaffolds project structure.
107
+
108
+ **Args:**
109
+ - `dir` (optional) — target directory; defaults to `process.cwd()`
110
+ - No `--config` required; generates config during init
111
+
112
+ **Creates:**
113
+ - `.slopgate/config.toml` — project config (roots, extensions, rule packs, checkers, baseline/suppressions paths)
114
+ - `.slopgate/suppressions.json` — line-level violation suppressions (empty initially)
115
+ - `.slopgate/depcruise.cjs` — starter dependency-cruiser rules (if depcruise detected)
116
+ - `.slopgate/convention-sources.json` — hints for authoring project-specific rule packs
117
+ - `.slopgate/rules/ast/` and `.slopgate/fixtures/src/` — directories for custom rules and fixtures
118
+ - `.git/hooks/pre-commit` — native git pre-commit hook (creates new or appends to existing)
119
+ - `.claude/settings.json` — Claude Code hook entries (idempotent merge)
120
+
121
+ **Next step:** Run `slopgate baseline --config .slopgate/config.toml` to create the initial ratchet baseline
122
+
123
+ ---
124
+
125
+ ### `slopgate --staged --config <path>`
126
+ Run commit-tier gate on staged files. Used by git pre-commit hook and Claude Code PreToolUse hook.
127
+
128
+ **Flags:**
129
+ - `--config <path>` (required) — path to `.slopgate/config.toml`
130
+ - `--tier fast|commit` (optional) — override default tier (default: commit for `--staged`)
131
+
132
+ **Exit codes:**
133
+ - `0` — no violations (or all baselined/suppressed)
134
+ - `1` — violations block the commit
135
+ - `2` — config error or missing argument
136
+
137
+ **Output:**
138
+ - Violations grouped by source (regex, ast, checker:tsc, etc.)
139
+ - Baselined count footer
140
+ - Skipped checkers (if tool/config missing)
141
+
142
+ ---
143
+
144
+ ### `slopgate --file <path> --config <path>`
145
+ Run fast-tier gate on a single file (post-edit). Used by Claude Code PostToolUse hook.
146
+
147
+ **Flags:**
148
+ - `--file <path>` (required) — repo-relative path to check
149
+ - `--config <path>` (required) — path to `.slopgate/config.toml`
150
+ - `--tier fast|commit` (optional) — override default tier (default: fast for `--file`)
151
+
152
+ **Exit codes:** same as `--staged`
153
+
154
+ **Output:** violations in the touched file only; no baseline filtering
155
+
156
+ ---
157
+
158
+ ### `slopgate baseline --config <path> [--update] [--prune]`
159
+ Manage the ratchet baseline.
160
+
161
+ **Flags:**
162
+ - `--config <path>` (required)
163
+ - `--update` — re-snapshot all current violations (overwrites baseline)
164
+ - `--prune` — remove entries whose fingerprint no longer occurs (dry-run only)
165
+ - Both flags can be combined; `--prune --update` prunes then updates
166
+
167
+ **Behavior:**
168
+ - No flags, file missing → create baseline with current violations
169
+ - No flags, file exists → error (refuses overwrite; use `--update`)
170
+ - `--update` → snapshot all violations in full commit tier scan
171
+ - `--prune` → drop resolved fingerprints (non-destructive; just removes old entries)
172
+
173
+ ---
174
+
175
+ ### `slopgate install-hooks --config <path>`
176
+ Install or upgrade the git pre-commit hook.
177
+
178
+ **Flags:**
179
+ - `--config <path>` (required)
180
+
181
+ **Behavior:**
182
+ - No hook exists → create new hook with slopgate check
183
+ - Hook exists with slopgate marker → upgrade (idempotent)
184
+ - Foreign hook exists → append slopgate block before final `exec` (preserves other hooks)
185
+
186
+ **Hook location:** `<git-dir>/hooks/pre-commit` (or respects `git config core.hooksPath`)
187
+
188
+ ---
189
+
190
+
191
+ ### `slopgate --self-test --config <path>`
192
+ Internal: validate regex + AST engines and checker parsers against fixtures.
193
+
194
+ **Flags:**
195
+ - `--config <path>` (required) — typically `rules/baseline/selftest.config.toml`
196
+
197
+ Runs in-process tests; exit 0 = all pass, exit 1 = failure. Used by `npm run self-test`.
198
+
199
+ ---
200
+
201
+ ## How the Two Tiers Work
202
+
203
+ ### Fast Tier (Post-Edit)
204
+ Runs on every Edit/Write to a `.ts`, `.tsx`, or `.astro` file.
205
+
206
+ **Scope:** Single file
207
+ **Engines:** Regex patterns + AST rules (baseline packs only)
208
+ **Baseline:** Not consulted (all violations shown)
209
+ **Latency:** < 1 second
210
+ **Feedback:** Instant, in-editor
211
+
212
+ **Rules applied:**
213
+ - All regex patterns in enabled baseline packs (`no-stubs`, `ts-suppress`, `as-any`, etc.)
214
+ - All AST rules from enabled baseline packs
215
+ - Project-owned AST rules (from `astRules` config)
216
+
217
+ ---
218
+
219
+ ### Commit Tier (Pre-Commit)
220
+ Runs before `git commit` or when `--staged` is called manually.
221
+
222
+ **Scope:** All staged files + full repo (for checkers like tsc, knip that need graph context)
223
+ **Engines:** Regex patterns + AST rules + six heavy checkers
224
+ **Baseline:** Consulted; only NEW violations block commit
225
+ **Latency:** 5–30 seconds (tsc + knip dominate)
226
+ **Feedback:** Commit blocked or passes
227
+
228
+ **Rules applied:**
229
+ - All regex patterns (same as fast tier)
230
+ - All AST rules (same as fast tier)
231
+ - **tsc** — TypeScript type errors (full-project compile)
232
+ - **knip** — unused exports/files/dependencies
233
+ - **jscpd** — copy-paste clones (staged files only are reported)
234
+ - **dependency-cruiser** — architecture violations
235
+ - **type-coverage** — NEW uncovered expressions
236
+ - **diff-shape** — staged files spanning > N top-level dirs
237
+
238
+ **Filtering:**
239
+ 1. Run all sources (regex, ast, checkers)
240
+ 2. Fingerprint violations (sha256 of source, rule, file, normalized message, line text)
241
+ 3. Filter by ratchet baseline (drop fingerprints in baseline.json)
242
+ 4. Filter by suppressions (line-level, per file + lineHash)
243
+ 5. Filter by severity gate (only show `critical`/`high` by default, configurable)
244
+ 6. Print report; exit 1 if violations remain
245
+
246
+ ---
247
+
248
+ ## Ratchet Baseline
249
+
250
+ The ratchet prevents violations from blocking adoption of new rules or onboarding legacy repos.
251
+
252
+ ### How It Works
253
+
254
+ 1. **At init:** `slopgate baseline --config ...` creates `.slopgate/baseline.json` with a snapshot of ALL current violations.
255
+
256
+ 2. **On commit:** The gate compares the current full-repo commit-tier scan against the baseline. Violations whose fingerprint is in the baseline are ignored (baselined); NEW violations block the commit.
257
+
258
+ 3. **Paydown:** As issues are fixed, their fingerprint disappears from the current scan. `slopgate baseline --prune` removes old entries from the baseline, lowering the bar.
259
+
260
+ 4. **Re-snapshot:** `slopgate baseline --update` does a full re-scan and updates the baseline (use after intentionally widening rules or adding new checkers).
261
+
262
+ ### Fingerprint Stability
263
+
264
+ Fingerprints include:
265
+ - Rule ID
266
+ - File path (repo-relative)
267
+ - Normalized message (digit runs replaced with `#`, kills line/col churn)
268
+ - First 60 chars of the source line (trimmed)
269
+
270
+ Fingerprints do NOT include the line number, so they survive unrelated edits shifting lines.
271
+
272
+ ### Suppressions vs. Baseline
273
+
274
+ - **Baseline** — temporary allowlist; debt should be paid down over time. Track in version control. Entire project-wide snapshot.
275
+ - **Suppressions** — permanent per-file exemptions (e.g., "this pattern is correct in this context"). Sparse, line-level. Also tracked.
276
+
277
+ ---
278
+
279
+ ## UX Module (optional)
280
+
281
+ The UX module provides opinionated static analysis rules for common UX anti-patterns. It is **off by default** since UX preferences vary across teams and projects. Enable selectively via the `ux:{}` config namespace.
282
+
283
+ **Why optional?** Many teams have different UX preferences, and enabling UX rules on existing projects would flag pre-existing markup. These are good-enough defaults for NEW projects where you want opinionated UX guidance but have no specific opinion yourself.
284
+
285
+ ### Configuration
286
+
287
+ ```toml
288
+ # .slopgate/config.toml
289
+ # ... other config
290
+
291
+ # UX module (optional) — off by default, opt-in per sub-module
292
+ [ux]
293
+ a11y = "high" # Accessibility violations (gate commits)
294
+ cls = "high" # Cumulative Layout Shift violations (gate commits)
295
+ feedback = "high" # Silent async / double-submit (gate commits)
296
+ taste = "advisory" # Design taste violations (report only, don't gate)
297
+ advisory = "advisory" # Heuristic nudges (report only, higher false-positive)
298
+ # taste = "medium" # equivalent to 'advisory'
299
+ # taste = true # use sub-module default severity
300
+ # omit key = that sub-module OFF
301
+ # delete whole [ux] table = entire module OFF
302
+ ```
303
+
304
+ ### Sub-modules
305
+
306
+ | Key | Catches | Default Severity | Framework § |
307
+ |-----|---------|------------------|-------------|
308
+ | `a11y` | onClick on `<div>`/`<span>` without role; `<a onClick>` without href; `<img>` without alt; `<button>` without type; positive `tabIndex` | `high` | §11 |
309
+ | `cls` | `<img>`/`<video>`/`<iframe>` without width/height | `high` | §13 |
310
+ | `feedback` | async `onClick` on a `<button>` with no `disabled` state (double-submit, silent wait) | `high` | §3/§12 |
311
+ | `taste` | emoji in UI, "trusted by" clichés, Lorem ipsum, robotic microcopy, heavy drop shadows, linear/long (>300ms) motion | `medium` | §0/§6/§26 |
312
+ | `advisory` | modal without `onClose`; array index as React `key`; view state (tab/page/filter) in `useState` instead of the URL | `medium` | §10/§14 |
313
+
314
+ Magic hardcoded colors/spacing (`#hex`, `rgb()`/`hsl()`, multi-digit `px`) are caught by the baseline `raw-hex` pack (§15), independent of the UX module.
315
+
316
+ ### Severity Levels
317
+
318
+ - **`'critical'`/`'high'`**: Gates commits (blocks by default, since default gate is `['critical','high']`)
319
+ - **`'medium'`/`'advisory'`**: Reports but doesn't block commits (useful for gradual adoption)
320
+ - **`true`**: Use the sub-module's default severity
321
+ - **Omit key**: That sub-module is OFF
322
+ - **Delete `ux:{}` block**: Entire UX module is OFF
323
+
324
+ ### Opt-out
325
+
326
+ Symmetric and trivial:
327
+ - Delete a key to disable one sub-module: `ux: { a11y: 'high' }` (cls and taste OFF)
328
+ - Delete the whole `ux:{}` block to disable the entire module
329
+
330
+ ### Companion Skill
331
+
332
+ Pair the static UX module with the `/slopgate-ux` skill for semantic UX directives that static analysis can't enforce (four-states, button hierarchy, focus-trap, optimistic UI, etc.).
333
+
334
+ ---
335
+
336
+ ## Config Reference (`.slopgate/config.toml`)
337
+
338
+ ```toml
339
+ # Repository layout
340
+ roots = ["src"] # source roots to scan
341
+ exts = [".ts", ".tsx", ".astro"] # file extensions
342
+ skipDirs = ["node_modules", "dist"] # dirs to skip
343
+
344
+ # Rule packs
345
+ baseline = ["no-stubs", "ts-suppress", "as-any"] # built-in baseline packs to enable (opt-in)
346
+ rules = [] # project regex rule packs — must be [] (PHASE-2, not yet supported)
347
+ astRules = "./rules/ast" # dir of .yml AST rules (optional)
348
+ astDisable = [] # rule ids to disable (escape hatch)
349
+
350
+ # Custom file paths (relative to repo root)
351
+ suppressions = "./suppressions.json" # line-level exemptions
352
+ fixtures = "./fixtures" # test fixture canaries
353
+ # baselinePath is auto-computed: .slopgate/baseline.json
354
+
355
+ # Commit-tier checkers (detected at init; absent = off)
356
+ # Per-checker options as key = value under each [checkers.<name>] table.
357
+ [checkers.tsc]
358
+ # e.g. timeout = 60
359
+
360
+ # UX module (optional) — off by default, opt-in per sub-module
361
+ [ux]
362
+ a11y = "high" # accessibility violations
363
+ cls = "high" # cumulative layout shift
364
+ taste = "advisory" # design taste (reports, doesn't gate)
365
+
366
+ # Severity filtering (which violations show in reports)
367
+ [gate]
368
+ file = ["critical", "high"] # fast-tier report threshold
369
+ staged = ["critical", "high"] # commit-tier report threshold
370
+ ```
371
+
372
+ **Auto-generated during `init`:**
373
+ - `roots` — detected from workspace packages and src/ dirs
374
+ - `exts` — detected from file walk
375
+ - `skipDirs` — detected from common exclusions (node_modules, dist, tests, .worktrees)
376
+ - `checkers` — detected from installed binaries and config files (all true initially)
377
+
378
+ ---
379
+
380
+ ## Rule Packs
381
+
382
+ ### Baseline Regex Packs (Shipped)
383
+
384
+ All are opt-in via the `baseline` array in config. Severity drives the gate threshold (`critical`/`high` block by default).
385
+
386
+ | Pack | Severity | Category | Catches |
387
+ |------|----------|----------|---------|
388
+ | `no-stubs` | critical | convention | Stub / placeholder / "not implemented" / deferred-work markers |
389
+ | `ts-suppress` | high | convention | `@ts-ignore` / `@ts-expect-error` — suppressing tsc instead of fixing the cause |
390
+ | `as-any` | high | convention | `as any` casts that disable type safety |
391
+ | `raw-hex` | high | convention | Hardcoded hex / `rgb()` colors + raw multi-digit `px` — use design tokens |
392
+ | `sql-safety` | critical | convention | `SELECT … FOR UPDATE` with an aggregate (Postgres rejects this at runtime) |
393
+ | `kv-ban` | critical | boundary | Cloudflare KV in read-after-write paths (eventually-consistent) |
394
+ | `live-secrets` | critical | security | Hardcoded Stripe / webhook / Google live credentials |
395
+ | `eval-ban` | critical | security | `eval` / dynamic code execution (injection surface) |
396
+ | `pii-logs` | high | security | PII fields written to logs / error trackers |
397
+ | `weak-hash` | high | security | MD5 / SHA-1 for integrity checks or passwords (cryptographically broken) |
398
+
399
+ ### Baseline AST Rules (Shipped, Always Active)
400
+
401
+ Loaded automatically alongside the regex packs (the resolver always adds `rules/baseline/ast`); disable any by id via `astDisable = [...]`.
402
+
403
+ | Rule id | Catches |
404
+ |---------|---------|
405
+ | `empty-catch` (ts + tsx) | Empty `catch` block silently swallowing an error |
406
+ | `inner-html` | Unsafe `innerHTML` / `dangerouslySetInnerHTML` assignment |
407
+ | `target-blank-norel` | `target="_blank"` anchor missing `rel="noopener"` |
408
+ | `window-in-render` | `window`/`document` access during render (SSR hazard) |
409
+
410
+ ### Stack Packs (Shipped)
411
+
412
+ Opt-in via `stack = ["cloudflare"]`:
413
+
414
+ | Pack | Rule ids |
415
+ |------|----------|
416
+ | `cloudflare` | `cf-env-spread-secrets`, `process-env-access`, `waituntil-bare-method-ref`, `cf-getCloudflareContext-banned`, `hono-env-direct-access` |
417
+
418
+ **Planned (v2+):**
419
+ - Depth rules — pass-through-fn, delegating-wrapper (Ousterhout symptoms)
420
+ - Test-slop rules — test-no-assertion, test-skip-only
421
+ - Custom project **regex** rule packs (the `rules = [...]` field — see [Project-Owned Rules](#project-owned-rules))
422
+
423
+ ### Project-Owned Rules
424
+
425
+ Add custom rules as **AST rules** — `.yml` files in ast-grep syntax:
426
+
427
+ ```yaml
428
+ id: my-ast-rule
429
+ language: tsx
430
+ severity: error # ast-grep level (error|warning|info)
431
+ message: Rule violation
432
+ note: '{"severity":"high","category":"convention","resolution":"…"}' # slopgate metadata
433
+ rule:
434
+ pattern: 'someBadCall($$$ARGS)' # code-snippet matcher; or structural kind/has/inside/all/any/not
435
+ ```
436
+
437
+ Point `astRules` at the directory holding them:
438
+
439
+ ```toml
440
+ astRules = "./rules/ast" # auto-loads all .yml files in this dir
441
+ ```
442
+
443
+ > **Note:** Custom **project regex rule packs** (the `rules = [...]` field) are **not yet supported** by the native engine. `rules` must currently be `[]`; a non-empty value errors with `slopgate: project rule pack "<path>" cannot be loaded by the native TOML resolver (PHASE-2: project rule packs)`. Project regex packs are planned (PHASE-2). For now, use ast-grep YAML for custom rules, or one of the built-in baseline/stack packs.
444
+
445
+ ---
446
+
447
+ ## How Rules Are Authored
448
+
449
+ ### Regex Rules
450
+
451
+ > **Note:** Authoring *custom project* regex rule packs is **not yet supported** by the native engine (PHASE-2 — see [Project-Owned Rules](#project-owned-rules)). The shape below describes how the **built-in** regex packs are defined (compiled into the engine); it is reference, not a workflow you can wire in via `rules` today. Use ast-grep YAML for custom rules.
452
+
453
+ Patterns are regex strings with flags (i, m, s, etc.). A pattern matches any line containing the regex.
454
+
455
+ **Example:**
456
+ ```
457
+ {
458
+ id: 'no-stubs-placeholder',
459
+ pattern: 'placeholder\\s+(?:for now|impl)',
460
+ flags: 'i',
461
+ canary: '// placeholder for now',
462
+ negativeCanary: ['placeholder={t(\'x\')}'], // should NOT match
463
+ }
464
+ ```
465
+
466
+ **Advanced:**
467
+ - `minFiles: N` — pattern must match in ≥ N files to fire (catch widespread slop)
468
+ - `excludeGlobs: ['*.test.ts']` — skip matching in these paths
469
+ - `includeGlobs: ['src/**']` — only match in these paths
470
+ - Suppressions: per-file, per-line (lineHash = sha256 of line text)
471
+
472
+ ### AST Rules
473
+
474
+ Written in ast-grep YAML syntax; scoped to source roots + extensions from config.
475
+
476
+ **Example** (modeled on the shipped `rules/baseline/ast/empty-catch-block-tsx.yml`):
477
+ ```yaml
478
+ id: empty-catch
479
+ language: tsx
480
+ severity: error # ast-grep level (error|warning|info)
481
+ message: Empty catch block swallows error silently
482
+ note: '{"severity":"high","category":"convention","resolution":"Handle or rethrow; log with context."}'
483
+ rule:
484
+ pattern: 'try { $A } catch ($E) {}' # code-snippet matcher; or structural kind/has/inside/all/any/not
485
+ ```
486
+
487
+ The top-level `severity` is ast-grep's own level; slopgate's gating severity/category/resolution live in the
488
+ JSON `note` field.
489
+
490
+ **Fixtures:** add a source canary that *triggers* the rule to `.slopgate/fixtures/src/` (built-in rules use
491
+ `rules/baseline/fixtures/src/`). A `.ts`/`.tsx` file containing the violating code is enough:
492
+
493
+ ```tsx
494
+ // .slopgate/fixtures/src/empty-catch.tsx
495
+ export function f() { try { risky(); } catch (e) {} } // should fire empty-catch
496
+ ```
497
+
498
+ `slopgate --self-test --config .slopgate/config.toml` scans the fixtures and asserts every rule fires at
499
+ least once.
500
+
501
+ ---
502
+
503
+ ## Hooks Integration
504
+
505
+ ### Claude Code Hooks
506
+
507
+ Init wires slopgate into `.claude/settings.json`:
508
+
509
+ ```json
510
+ {
511
+ "hooks": {
512
+ "PreToolUse": [{
513
+ "matcher": "Bash",
514
+ "hooks": [
515
+ {
516
+ "type": "command",
517
+ "command": "/path/to/slopgate/hooks/commit-hook.sh"
518
+ }
519
+ ]
520
+ }],
521
+ "PostToolUse": [{
522
+ "matcher": "Edit|Write",
523
+ "hooks": [
524
+ {
525
+ "type": "command",
526
+ "command": "/path/to/slopgate/hooks/edit-hook.sh"
527
+ }
528
+ ]
529
+ }]
530
+ }
531
+ }
532
+ ```
533
+
534
+ - **PreToolUse** (commit-hook.sh) — fires before Bash tool use; checks for `git commit` in the command and runs `slopgate --staged`
535
+ - **PostToolUse** (edit-hook.sh) — fires after Edit/Write; runs `slopgate --file` on the touched file (fast tier, 5-second timeout)
536
+
537
+ ### Git Pre-Commit Hook
538
+
539
+ `init` also installs `.git/hooks/pre-commit` (or appends to existing). This is the native git hook; it catches commits from any tool (terminal, IDE, other agents).
540
+
541
+ ```bash
542
+ #!/usr/bin/env bash
543
+ ROOT=$(git rev-parse --show-toplevel) || exit 0
544
+ CONFIG="$ROOT/.slopgate/config.toml"
545
+ [ -f "$CONFIG" ] || exit 0
546
+ exec slopgate --staged --config "$CONFIG"
547
+ ```
548
+
549
+ The hook can be bypassed with `git commit --no-verify`, which is intentional (user-initiated escape hatch).
550
+
551
+ ---
552
+
553
+ ## Suppressions
554
+
555
+ Edit `.slopgate/suppressions.json`:
556
+
557
+ ```json
558
+ {
559
+ "version": 1,
560
+ "entries": [
561
+ {
562
+ "ruleId": "no-stubs-placeholder",
563
+ "file": "src/app.ts",
564
+ "lineHash": "abc123def..."
565
+ }
566
+ ]
567
+ }
568
+ ```
569
+
570
+ Line hash is auto-generated: `sha256(trimmedLine).slice(0, 16)`.
571
+
572
+ To suppress a violation, grab the line hash from the report and add an entry. The line text must match exactly (trimmed); unrelated edits shift line numbers but keep line text stable.
573
+
574
+ ---
575
+
576
+ ## Testing
577
+
578
+ ### Run Self-Test
579
+
580
+ ```bash
581
+ npm run self-test
582
+ ```
583
+
584
+ Validates:
585
+ - Regex engine (patterns match canaries, skip negativeCanaries)
586
+ - AST engine (ast-grep rules parse + match fixtures)
587
+ - Checker parsers (tsc, knip, jscpd, depcruise, type-coverage outputs parse correctly)
588
+ - Ratchet fingerprints (stability under line shifts)
589
+ - Suppressions (line hashing, deduplication)
590
+
591
+ ---
592
+
593
+ ## Examples
594
+
595
+ ### Example 1: Block Unsafe Type Casts
596
+
597
+ Config:
598
+ ```toml
599
+ baseline = ["as-any"]
600
+ [gate]
601
+ staged = ["critical", "high"]
602
+ ```
603
+
604
+ Commit a file with `const x = y as any;`:
605
+ ```
606
+ slopgate: 1 violation(s)
607
+
608
+ regex › as-any-cast
609
+ src/utils.ts:42
610
+ Unsafe `as any` cast
611
+ severity: high
612
+ resolution: Use a precise type or a discriminated narrowing.
613
+
614
+ exit code: 1 (commit blocked)
615
+ ```
616
+
617
+ Fix it to `const x = y as unknown;` or a proper type, then commit.
618
+
619
+ ### Example 2: Allow Pre-Existing Copy-Paste, Block New Ones
620
+
621
+ Config:
622
+ ```toml
623
+ baseline = []
624
+ [checkers.jscpd]
625
+ minTokens = 50
626
+ ```
627
+
628
+ Run `slopgate baseline --config .slopgate/config.toml` to baseline existing clones. Now:
629
+ - Commits pass unless they introduce NEW duplications
630
+ - Track paydown via `slopgate baseline --prune` (drops resolved entries)
631
+
632
+ ### Example 3: Custom Architecture Rules
633
+
634
+ Create `.slopgate/depcruise.cjs`:
635
+ ```javascript
636
+ module.exports = {
637
+ forbidden: [
638
+ {
639
+ name: 'no-ui-to-db',
640
+ severity: 'error',
641
+ from: { path: 'src/ui' },
642
+ to: { path: 'src/db' },
643
+ },
644
+ ],
645
+ };
646
+ ```
647
+
648
+ Now commits that import database code from UI layer are blocked.
649
+
650
+ ### Example 4: Silence a Built-in Rule in One Project
651
+
652
+ Config:
653
+ ```toml
654
+ baseline = ["no-stubs", "as-any"]
655
+ astDisable = ["target-blank-norel"] # this app links only to vetted internal routes
656
+ ```
657
+
658
+ `astDisable` lists built-in AST rule ids to turn off for this repo; every other rule stays active.
659
+
660
+ ---
661
+
662
+ ## Architecture
663
+
664
+ ### Data Flow (Commit Tier)
665
+
666
+ ```
667
+ git commit
668
+ └─ .git/hooks/pre-commit
669
+ └─ slopgate --staged --config <repo>/.slopgate/config.toml
670
+ ├─ Enumerate staged files
671
+ ├─ Regex engine (patterns → violations)
672
+ ├─ AST engine (ast-grep rules → violations)
673
+ ├─ Checker adapters
674
+ │ ├─ tsc (type errors)
675
+ │ ├─ knip (dead code)
676
+ │ ├─ jscpd (duplication)
677
+ │ ├─ dependency-cruiser (architecture)
678
+ │ ├─ type-coverage (any propagation)
679
+ │ └─ diff-shape (mixed concerns)
680
+ ├─ Ratchet baseline filter (drop pre-existing)
681
+ ├─ Suppressions filter (per-file, per-line)
682
+ ├─ Severity gate (critical/high)
683
+ └─ Report + exit code (0 = pass, 1 = blocked)
684
+ ```
685
+
686
+ ### Checker Timeout and Errors
687
+
688
+ Each checker has a per-tool timeout (configurable):
689
+ - tsc: 120s
690
+ - knip: 90s
691
+ - jscpd: 60s
692
+ - depcruise: 60s
693
+ - type-coverage: 120s
694
+
695
+ Tool crash / timeout → `⚠ skipped: <id> (<reason>)` warning, gate continues (fail-open on infra). Violations still block; missing tools don't.
696
+
697
+ ---
698
+
699
+ ## Limitations & Future Work
700
+
701
+ - **Git-only** — no other VCS support
702
+ - **No auto-fix** — violations are reported, not automatically corrected
703
+ - **No CI integration yet** — hooks are local and Claude Code only; CI layer is future work
704
+ - **`slopgate audit` command** — planned for v2 (non-gating architecture-health report: hotspots, module shape, co-change coupling, ratchet progress tracking)
705
+ - **Embeddings-based semantic duplicate detection** — planned, not in v1
706
+ - **API-surface diff gate** — track breaking changes to public exports (future)
707
+ - **LLM-judge skill** — on-demand deep review of architectural debt (separate sub-project)
708
+ - **Rule harvesting** — auto-generate rules from repeated violations (separate sub-project)
709
+
710
+ ---
711
+
712
+ ## Contributing
713
+
714
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).
715
+
716
+ ---
717
+
718
+ ## License
719
+
720
+ MIT — See [LICENSE](./LICENSE) for details.