fallow 2.47.1 → 2.48.1

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.
@@ -0,0 +1,817 @@
1
+ # Fallow: Common Workflow Patterns & Recipes
2
+
3
+ Step-by-step workflows for common fallow usage scenarios.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Full Project Audit](#full-project-audit)
10
+ - [PR Dead Code Check](#pr-dead-code-check)
11
+ - [CI Pipeline Setup](#ci-pipeline-setup)
12
+ - [Incremental Adoption with Baselines](#incremental-adoption-with-baselines)
13
+ - [Monorepo Analysis](#monorepo-analysis)
14
+ - [Duplication Threshold CI Gate](#duplication-threshold-ci-gate)
15
+ - [Migration from knip](#migration-from-knip)
16
+ - [Migration from jscpd](#migration-from-jscpd)
17
+ - [Safe Auto-Fix Workflow](#safe-auto-fix-workflow)
18
+ - [Production vs Full Audit](#production-vs-full-audit)
19
+ - [Debugging False Positives](#debugging-false-positives)
20
+ - [Combined Dead Code + Duplication](#combined-dead-code--duplication)
21
+ - [Custom Plugin Setup](#custom-plugin-setup)
22
+ - [GitHub Code Scanning Integration](#github-code-scanning-integration)
23
+ - [Guard `git push` with a Claude Code PreToolUse hook](#guard-git-push-with-a-claude-code-pretooluse-hook)
24
+
25
+ ---
26
+
27
+ ## Full Project Audit
28
+
29
+ Complete codebase hygiene audit.
30
+
31
+ ### Step 1: Run full analysis
32
+
33
+ ```bash
34
+ fallow dead-code --format json --quiet
35
+ ```
36
+
37
+ ### Step 2: Review issue counts
38
+
39
+ Parse `total_issues` and individual arrays (`unused_files`, `unused_exports`, etc.) to understand the scope.
40
+
41
+ ### Step 3: Find duplication
42
+
43
+ ```bash
44
+ fallow dupes --format json --quiet
45
+ ```
46
+
47
+ ### Step 4: Preview auto-fix
48
+
49
+ ```bash
50
+ fallow fix --dry-run --format json --quiet
51
+ ```
52
+
53
+ ### Step 5: Apply fixes (after user confirmation)
54
+
55
+ ```bash
56
+ fallow fix --yes --format json --quiet
57
+ ```
58
+
59
+ ### Step 6: Verify
60
+
61
+ ```bash
62
+ fallow dead-code --format json --quiet
63
+ ```
64
+
65
+ ---
66
+
67
+ ## PR Dead Code Check
68
+
69
+ Check if a pull request introduces new dead code.
70
+
71
+ ### Step 1: Analyze changed files
72
+
73
+ ```bash
74
+ fallow dead-code --format json --quiet --changed-since main --fail-on-issues
75
+ ```
76
+
77
+ Exit code 1 if the PR introduces new dead code. Exit code 0 if clean.
78
+
79
+ ### Step 2: If issues found, show specifics
80
+
81
+ ```bash
82
+ fallow dead-code --format json --quiet --changed-since main
83
+ ```
84
+
85
+ Parse the JSON to list specific files and exports that became unused.
86
+
87
+ ---
88
+
89
+ ## CI Pipeline Setup
90
+
91
+ ### GitHub Actions: Basic
92
+
93
+ ```yaml
94
+ - name: Dead code check
95
+ run: npx fallow dead-code --fail-on-issues --quiet
96
+ ```
97
+
98
+ ### GitHub Actions: With SARIF Upload
99
+
100
+ ```yaml
101
+ - name: Fallow analysis
102
+ run: npx fallow dead-code --ci > fallow.sarif
103
+ continue-on-error: true # --ci sets --fail-on-issues; continue to upload SARIF even if issues found
104
+
105
+ - name: Upload SARIF
106
+ uses: github/codeql-action/upload-sarif@v3
107
+ with:
108
+ sarif_file: fallow.sarif
109
+ ```
110
+
111
+ ### GitHub Actions: Using the Official Action
112
+
113
+ ```yaml
114
+ - uses: fallow-rs/fallow@v2
115
+ with:
116
+ command: dead-code
117
+ fail-on-issues: true
118
+ changed-since: main
119
+ ```
120
+
121
+ ### GitHub Actions: With Health Score
122
+
123
+ ```yaml
124
+ - uses: fallow-rs/fallow@v2
125
+ with:
126
+ score: true
127
+ changed-since: main
128
+ ```
129
+
130
+ Computes a health score (0-100 with letter grade) in combined mode and enables the health delta header in PR comments.
131
+
132
+ ### GitHub Actions: Inline PR Annotations (No Advanced Security)
133
+
134
+ The official action supports inline PR annotations via GitHub workflow commands. This does not require Advanced Security (unlike SARIF upload) and works on any GitHub plan.
135
+
136
+ ```yaml
137
+ - uses: fallow-rs/fallow@v2
138
+ with:
139
+ command: dead-code
140
+ changed-since: main
141
+ annotations: true
142
+ max-annotations: 50 # default: 50, limits annotation count
143
+ ```
144
+
145
+ Annotations appear as inline warnings on the PR diff. They work with all commands (`dead-code`, `dupes`, `health`, and the default combined mode). The `max-annotations` input prevents annotation flooding on large projects.
146
+
147
+ ### GitHub Actions: PR-Scoped Check
148
+
149
+ ```yaml
150
+ - name: Check for new dead code
151
+ run: npx fallow dead-code --format json --quiet --changed-since ${{ github.event.pull_request.base.sha }} --fail-on-issues
152
+ ```
153
+
154
+ ### GitHub Actions: Duplication Gate
155
+
156
+ ```yaml
157
+ - name: Duplication check
158
+ run: npx fallow dupes --format json --quiet --threshold 5 --mode mild
159
+ ```
160
+
161
+ Fails if overall duplication exceeds 5%.
162
+
163
+ ### GitHub Actions: PR-Scoped Duplication Check
164
+
165
+ ```yaml
166
+ - name: Check duplication in changed files
167
+ run: npx fallow dupes --format json --quiet --changed-since ${{ github.event.pull_request.base.sha }}
168
+ ```
169
+
170
+ Only reports duplication in files modified by the PR.
171
+
172
+ ### GitLab CI: Using the Official Template
173
+
174
+ ```yaml
175
+ include:
176
+ - remote: 'https://raw.githubusercontent.com/fallow-rs/fallow/main/ci/gitlab-ci.yml'
177
+
178
+ fallow:
179
+ extends: .fallow
180
+ variables:
181
+ FALLOW_COMMAND: "dead-code"
182
+ FALLOW_FAIL_ON_ISSUES: "true"
183
+ ```
184
+
185
+ Generates Code Quality reports (inline MR annotations) automatically. In MR pipelines, `--changed-since` is automatically set to the target branch — no manual configuration needed.
186
+
187
+ ### GitLab CI: With MR Summary Comments
188
+
189
+ ```yaml
190
+ include:
191
+ - remote: 'https://raw.githubusercontent.com/fallow-rs/fallow/main/ci/gitlab-ci.yml'
192
+
193
+ fallow:
194
+ extends: .fallow
195
+ variables:
196
+ FALLOW_COMMENT: "true"
197
+ ```
198
+
199
+ Posts a summary comment on the MR with issue counts and findings. In MR pipelines, `--changed-since` is auto-detected from `$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`, so only issues from changed files are reported. Requires `GITLAB_TOKEN` CI/CD variable (project access token with `api` scope) or enabling job token API access.
200
+
201
+ ### GitLab CI: With Inline Code Review Comments
202
+
203
+ ```yaml
204
+ include:
205
+ - remote: 'https://raw.githubusercontent.com/fallow-rs/fallow/main/ci/gitlab-ci.yml'
206
+
207
+ fallow:
208
+ extends: .fallow
209
+ variables:
210
+ FALLOW_REVIEW: "true"
211
+ ```
212
+
213
+ Posts inline review comments directly on the MR diff lines where issues were found. This gives developers precise feedback without leaving the code review flow. Can be combined with `FALLOW_COMMENT: "true"` for both a summary and inline comments. Requires `GITLAB_TOKEN`.
214
+
215
+ ### GitLab CI: Combined MR Comments + Review
216
+
217
+ ```yaml
218
+ include:
219
+ - remote: 'https://raw.githubusercontent.com/fallow-rs/fallow/main/ci/gitlab-ci.yml'
220
+
221
+ fallow:
222
+ extends: .fallow
223
+ variables:
224
+ FALLOW_COMMENT: "true"
225
+ FALLOW_REVIEW: "true"
226
+ FALLOW_FAIL_ON_ISSUES: "true"
227
+ ```
228
+
229
+ Posts both a summary comment and inline review comments on the MR. The template auto-detects the package manager (npm/pnpm/yarn) from lockfiles, so review comments show the correct commands for the project (e.g., `pnpm remove` instead of `npm uninstall`).
230
+
231
+ ### GitLab CI: With Health Score and Trend
232
+
233
+ ```yaml
234
+ include:
235
+ - remote: 'https://raw.githubusercontent.com/fallow-rs/fallow/main/ci/gitlab-ci.yml'
236
+
237
+ fallow:
238
+ extends: .fallow
239
+ variables:
240
+ FALLOW_SCORE: "true"
241
+ FALLOW_TREND: "true"
242
+ FALLOW_COMMENT: "true"
243
+ ```
244
+
245
+ Computes the health score and compares against saved snapshots. The MR comment includes a health delta header showing score changes. `FALLOW_TREND` implies `FALLOW_SCORE`.
246
+
247
+ ### GitLab CI: Manual (Without Template)
248
+
249
+ ```yaml
250
+ fallow:
251
+ image: node:20-slim
252
+ script:
253
+ - npx fallow dead-code --fail-on-issues --quiet --format json > fallow-results.json
254
+ artifacts:
255
+ paths:
256
+ - fallow-results.json
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Incremental Adoption with Baselines
262
+
263
+ For large projects with existing dead code. Adopt gradually without fixing everything at once.
264
+
265
+ ### Step 1: Save current state as baseline
266
+
267
+ ```bash
268
+ fallow dead-code --format json --quiet --save-baseline fallow-baselines/dead-code.json
269
+ ```
270
+
271
+ ### Step 2: Commit the baseline
272
+
273
+ ```bash
274
+ git add fallow-baselines/dead-code.json
275
+ git commit -m "chore: add fallow baseline"
276
+ ```
277
+
278
+ ### Step 3: CI only fails on NEW issues
279
+
280
+ ```bash
281
+ fallow dead-code --format json --quiet --baseline fallow-baselines/dead-code.json --fail-on-issues
282
+ ```
283
+
284
+ ### Step 4: Gradually fix and update baseline
285
+
286
+ As you fix existing issues, regenerate the baseline:
287
+
288
+ ```bash
289
+ fallow dead-code --format json --quiet --save-baseline fallow-baselines/dead-code.json
290
+ ```
291
+
292
+ ### Duplication baseline
293
+
294
+ Same pattern works for duplication:
295
+
296
+ ```bash
297
+ fallow dupes --format json --quiet --save-baseline fallow-baselines/dupes.json
298
+ fallow dupes --format json --quiet --baseline fallow-baselines/dupes.json --threshold 5
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Monorepo Analysis
304
+
305
+ ### Analyze the full monorepo
306
+
307
+ ```bash
308
+ fallow dead-code --format json --quiet
309
+ ```
310
+
311
+ Fallow auto-detects workspaces from `package.json` workspaces or `pnpm-workspace.yaml`.
312
+
313
+ ### Analyze a single package
314
+
315
+ ```bash
316
+ fallow dead-code --format json --quiet --workspace my-package
317
+ ```
318
+
319
+ Full cross-workspace graph is built (so imports between packages are resolved), but only issues in `my-package` are reported.
320
+
321
+ ### Per-package CI
322
+
323
+ Run analysis for each workspace package separately:
324
+
325
+ ```bash
326
+ fallow dead-code --format json --quiet --workspace package-a --fail-on-issues
327
+ fallow dead-code --format json --quiet --workspace package-b --fail-on-issues
328
+ ```
329
+
330
+ ### List all discovered files across workspaces
331
+
332
+ ```bash
333
+ fallow list --files --format json --quiet
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Duplication Threshold CI Gate
339
+
340
+ Enforce a maximum duplication percentage.
341
+
342
+ ### Step 1: Measure current duplication
343
+
344
+ ```bash
345
+ fallow dupes --format json --quiet
346
+ ```
347
+
348
+ Check `duplication_percentage` in the JSON output.
349
+
350
+ ### Step 2: Set threshold slightly above current
351
+
352
+ If current duplication is 3.8%, set threshold to 5%:
353
+
354
+ ```bash
355
+ fallow dupes --format json --quiet --threshold 5
356
+ ```
357
+
358
+ Exits with code 1 if duplication exceeds 5%.
359
+
360
+ ### Step 3: Tighten over time
361
+
362
+ As you reduce duplication, lower the threshold.
363
+
364
+ ### Cross-directory only
365
+
366
+ To ignore duplication within the same directory (local helpers, similar test files):
367
+
368
+ ```bash
369
+ fallow dupes --format json --quiet --threshold 5 --skip-local
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Migration from knip
375
+
376
+ ### Step 1: Preview migration
377
+
378
+ ```bash
379
+ fallow migrate --dry-run
380
+ ```
381
+
382
+ Shows what config would be generated. Auto-detects `knip.json`, `.knip.json`, `knip.jsonc`, `.knip.jsonc`, or `package.json#knip`.
383
+
384
+ ### Step 2: Apply migration
385
+
386
+ ```bash
387
+ fallow migrate
388
+ ```
389
+
390
+ Creates `.fallowrc.json` with mapped settings:
391
+ - knip `rules`/`exclude`/`include` → fallow `rules` (error/warn/off)
392
+ - knip `ignore` → fallow `ignorePatterns`
393
+ - knip `ignoreDependencies` → fallow `ignoreDependencies`
394
+ - Unmappable fields generate warnings with suggestions
395
+
396
+ ### Step 3: Compare results
397
+
398
+ ```bash
399
+ # Run fallow
400
+ fallow dead-code --format json --quiet
401
+
402
+ # Compare with knip output
403
+ npx knip --reporter json
404
+ ```
405
+
406
+ ### Step 4: Remove knip config
407
+
408
+ Once satisfied, remove the old `knip.json` and uninstall knip.
409
+
410
+ ---
411
+
412
+ ## Migration from jscpd
413
+
414
+ ### Step 1: Preview migration
415
+
416
+ ```bash
417
+ fallow migrate --dry-run
418
+ ```
419
+
420
+ Auto-detects `.jscpd.json` or `package.json#jscpd`.
421
+
422
+ ### Step 2: Apply migration
423
+
424
+ ```bash
425
+ fallow migrate
426
+ ```
427
+
428
+ Maps jscpd settings:
429
+ - `minTokens` → `duplicates.minTokens`
430
+ - `minLines` → `duplicates.minLines`
431
+ - `threshold` → `duplicates.threshold`
432
+ - `mode` → `duplicates.mode`
433
+
434
+ ### Step 3: Compare results
435
+
436
+ ```bash
437
+ fallow dupes --format json --quiet
438
+ ```
439
+
440
+ ### Detection mode mapping
441
+
442
+ | jscpd | fallow |
443
+ |-------|--------|
444
+ | Default (exact tokens) | `strict` |
445
+ | — | `mild` (fallow default, syntax normalized) |
446
+ | — | `weak` (literal normalization) |
447
+ | — | `semantic` (variable rename detection) |
448
+
449
+ ---
450
+
451
+ ## Safe Auto-Fix Workflow
452
+
453
+ ### Step 1: Dry-run first
454
+
455
+ ```bash
456
+ fallow fix --dry-run --format json --quiet
457
+ ```
458
+
459
+ ### Step 2: Review each proposed change
460
+
461
+ Parse the JSON `changes` array. Each entry shows:
462
+ - `path`: file to be modified
463
+ - `action`: what will happen (`remove_export`, `remove_dependency`)
464
+ - `name`: the symbol or dependency being removed
465
+ - `line`: the line number
466
+
467
+ ### Step 3: Confirm with user before applying
468
+
469
+ Show the proposed changes. Wait for user confirmation.
470
+
471
+ ### Step 4: Apply
472
+
473
+ ```bash
474
+ fallow fix --yes --format json --quiet
475
+ ```
476
+
477
+ ### Step 5: Verify
478
+
479
+ ```bash
480
+ fallow dead-code --format json --quiet
481
+ ```
482
+
483
+ ### Step 6: Run project tests
484
+
485
+ After auto-fix, always run the project's test suite to verify nothing broke.
486
+
487
+ ---
488
+
489
+ ## Production vs Full Audit
490
+
491
+ ### Full audit (default)
492
+
493
+ ```bash
494
+ fallow dead-code --format json --quiet
495
+ ```
496
+
497
+ Includes all files, all scripts, all dependencies (including devDependencies).
498
+
499
+ ### Production audit
500
+
501
+ ```bash
502
+ fallow dead-code --format json --quiet --production
503
+ ```
504
+
505
+ Differences:
506
+ - Excludes: `*.test.*`, `*.spec.*`, `*.stories.*`, `__tests__/**`, `__mocks__/**`
507
+ - Only analyzes: `start`, `build`, `serve`, `preview`, `prepare` scripts
508
+ - Skips: unused devDependency detection
509
+ - Adds: type-only production dependency detection
510
+
511
+ Use production mode for:
512
+ - Checking what ships to users
513
+ - Finding dependencies that should be devDependencies
514
+ - CI pipelines focused on production bundle
515
+
516
+ Use full mode for:
517
+ - Complete codebase hygiene
518
+ - Finding unused test utilities
519
+ - Auditing devDependency usage
520
+
521
+ ---
522
+
523
+ ## Debugging False Positives
524
+
525
+ ### Trace an export's usage chain
526
+
527
+ ```bash
528
+ fallow dead-code --format json --quiet --trace src/utils.ts:myFunction
529
+ ```
530
+
531
+ Shows where `myFunction` is imported (or not imported) and why it's flagged.
532
+
533
+ ### Trace all edges for a file
534
+
535
+ ```bash
536
+ fallow dead-code --format json --quiet --trace-file src/utils.ts
537
+ ```
538
+
539
+ Shows all imports/exports for the file and their resolution status.
540
+
541
+ ### Trace a dependency
542
+
543
+ ```bash
544
+ fallow dead-code --format json --quiet --trace-dependency lodash
545
+ ```
546
+
547
+ Shows all files that import lodash.
548
+
549
+ ### If the trace shows it IS used
550
+
551
+ The export might be consumed through a pattern fallow can't resolve (fully dynamic import, reflection). Add a suppression:
552
+
553
+ ```typescript
554
+ // fallow-ignore-next-line unused-export
555
+ export const dynamicallyUsed = createHandler();
556
+ ```
557
+
558
+ ### If the trace shows it's NOT used
559
+
560
+ The export is genuinely unused. Consider removing it or marking it as intentionally kept:
561
+
562
+ ```typescript
563
+ // fallow-ignore-next-line unused-export
564
+ export const publicApi = createWidget(); // Used by external consumers
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Combined Dead Code + Duplication
570
+
571
+ Cross-reference dead code with duplication findings to find high-priority cleanup targets.
572
+
573
+ ### Step 1: Run combined analysis
574
+
575
+ ```bash
576
+ fallow dead-code --format json --quiet --include-dupes
577
+ ```
578
+
579
+ This adds duplication context to dead code findings, identifying clone instances that exist in unused files or overlap with unused exports.
580
+
581
+ ### Step 2: Prioritize cleanup
582
+
583
+ Focus on findings that are BOTH dead code and duplicated:
584
+ - Unused files containing duplicate code → delete the file entirely
585
+ - Unused exports that are clones of other exports → remove the duplicate
586
+
587
+ ---
588
+
589
+ ## Custom Plugin Setup
590
+
591
+ For frameworks not covered by the 90 built-in plugins.
592
+
593
+ ### Option 1: Inline framework config
594
+
595
+ ```jsonc
596
+ // .fallowrc.json
597
+ {
598
+ "framework": [
599
+ {
600
+ "name": "my-framework",
601
+ "enablers": ["my-framework"],
602
+ "entryPoints": ["src/routes/**/*.ts", "src/middleware/**/*.ts"]
603
+ }
604
+ ]
605
+ }
606
+ ```
607
+
608
+ ### Option 2: External plugin file
609
+
610
+ Create `.fallow/plugins/my-framework.jsonc`:
611
+
612
+ ```jsonc
613
+ {
614
+ "name": "my-framework",
615
+ "detection": { "dependency": "my-framework" },
616
+ "entryPoints": ["src/routes/**/*.ts"],
617
+ "alwaysUsedFiles": ["src/bootstrap.ts"],
618
+ "usedExports": {
619
+ "src/config.ts": ["default"]
620
+ },
621
+ "toolingDependencies": ["my-framework-cli"]
622
+ }
623
+ ```
624
+
625
+ ### Option 3: Plugin directory
626
+
627
+ ```jsonc
628
+ // .fallowrc.json
629
+ {
630
+ "plugins": ["tools/plugins/"]
631
+ }
632
+ ```
633
+
634
+ Place `.jsonc`, `.json`, or `.toml` plugin files in that directory.
635
+
636
+ ---
637
+
638
+ ## GitHub Code Scanning Integration
639
+
640
+ Upload fallow results to GitHub's Code Scanning dashboard.
641
+
642
+ ### Step 1: Generate SARIF output
643
+
644
+ ```bash
645
+ fallow dead-code --format sarif --quiet > fallow.sarif
646
+ ```
647
+
648
+ ### Step 2: Upload via GitHub Action
649
+
650
+ ```yaml
651
+ - name: Upload SARIF
652
+ uses: github/codeql-action/upload-sarif@v3
653
+ with:
654
+ sarif_file: fallow.sarif
655
+ ```
656
+
657
+ ### All-in-one with `--ci`
658
+
659
+ ```bash
660
+ fallow dead-code --ci > fallow.sarif
661
+ ```
662
+
663
+ The `--ci` flag is equivalent to `--format sarif --fail-on-issues --quiet`. Note: `--fail-on-issues` means exit code 1 if issues exist, in CI scripts use `continue-on-error: true` or `|| true` to ensure the SARIF upload step still runs.
664
+
665
+ ---
666
+
667
+ ## Guard `git push` with a Claude Code PreToolUse hook
668
+
669
+ Use this when Claude Code is allowed to run Git commands in a repository that already uses fallow.
670
+
671
+ The pattern is a local agent gate, not a Git hook. Claude Code intercepts its own `Bash` tool calls before execution. When Claude tries `git commit` or `git push`, the hook runs:
672
+
673
+ ```bash
674
+ fallow audit --format json --quiet --explain
675
+ ```
676
+
677
+ Behavior:
678
+
679
+ - `pass`: allow the command
680
+ - `warn`: allow the command
681
+ - `fail`: exit 2 and write the raw audit JSON to stderr
682
+ - runtime error JSON like `{ "error": true, ... }`: fail open, do not block
683
+
684
+ Because Claude receives stderr as tool feedback on a blocked `PreToolUse` call, it can read the structured findings (including `_meta.docs` links and `actions`), fix the code, and retry the Git command.
685
+
686
+ Install it automatically:
687
+
688
+ ```bash
689
+ fallow setup-hooks
690
+ ```
691
+
692
+ Remove it later with:
693
+
694
+ ```bash
695
+ fallow setup-hooks --uninstall
696
+ ```
697
+
698
+ Manual files:
699
+
700
+ ### `.claude/settings.json`
701
+
702
+ ```json
703
+ {
704
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
705
+ "hooks": {
706
+ "PreToolUse": [
707
+ {
708
+ "matcher": "Bash",
709
+ "hooks": [
710
+ {
711
+ "type": "command",
712
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/fallow-gate.sh"
713
+ }
714
+ ]
715
+ }
716
+ ]
717
+ }
718
+ }
719
+ ```
720
+
721
+ ### `.claude/hooks/fallow-gate.sh`
722
+
723
+ ```bash
724
+ #!/usr/bin/env bash
725
+ set -euo pipefail
726
+
727
+ # Generated by fallow setup-hooks.
728
+ # Requires bash and jq. On Windows run via git-bash or WSL.
729
+ # Blocks Claude Code git commit and git push when fallow audit returns verdict fail.
730
+ # Runtime errors fail open with a single stderr notice so skips stay visible.
731
+
732
+ if ! command -v jq >/dev/null 2>&1; then
733
+ echo "fallow-gate: jq not on PATH, skipping audit." >&2
734
+ exit 0
735
+ fi
736
+
737
+ INPUT="$(cat)"
738
+ CMD="$(jq -r '.tool_input.command // empty' <<<"$INPUT")"
739
+
740
+ if ! printf '%s\n' "$CMD" | grep -Eq '(^|[[:space:];|&()])git[[:space:]]+(commit|push)([[:space:]]|$)'; then
741
+ exit 0
742
+ fi
743
+
744
+ if command -v fallow >/dev/null 2>&1; then
745
+ RUNNER=(fallow)
746
+ elif command -v npx >/dev/null 2>&1 && VER="$(npx --no-install fallow --version 2>/dev/null || true)" && [[ "$VER" == fallow* ]]; then
747
+ RUNNER=(npx --no-install fallow)
748
+ else
749
+ echo "fallow-gate: fallow binary not found (tried PATH and npx --no-install), skipping audit." >&2
750
+ exit 0
751
+ fi
752
+
753
+ TMP_JSON="$(mktemp)"
754
+ TMP_ERR="$(mktemp)"
755
+ cleanup() {
756
+ rm -f "$TMP_JSON" "$TMP_ERR"
757
+ }
758
+ trap cleanup EXIT
759
+
760
+ if "${RUNNER[@]}" audit --format json --quiet --explain >"$TMP_JSON" 2>"$TMP_ERR"; then
761
+ STATUS=0
762
+ else
763
+ STATUS=$?
764
+ fi
765
+
766
+ VERDICT="$(jq -r '.verdict // empty' <"$TMP_JSON" 2>/dev/null || true)"
767
+ IS_ERROR="$(jq -r '.error // false' <"$TMP_JSON" 2>/dev/null || echo false)"
768
+
769
+ if [ "$VERDICT" = "fail" ]; then
770
+ cat "$TMP_JSON" >&2
771
+ exit 2
772
+ fi
773
+
774
+ if [ "$STATUS" -eq 2 ] || [ "$IS_ERROR" = "true" ]; then
775
+ MSG="$(jq -r '.message // empty' <"$TMP_JSON" 2>/dev/null || true)"
776
+ if [ -n "$MSG" ]; then
777
+ echo "fallow-gate: fallow audit runtime error ($MSG), skipping." >&2
778
+ else
779
+ echo "fallow-gate: fallow audit runtime error, skipping." >&2
780
+ fi
781
+ exit 0
782
+ fi
783
+
784
+ if [ "$STATUS" -ne 0 ]; then
785
+ ERR_LINE="$(sed -n '1p' "$TMP_ERR" 2>/dev/null || true)"
786
+ if [ -n "$ERR_LINE" ]; then
787
+ echo "fallow-gate: fallow audit exited $STATUS ($ERR_LINE), skipping." >&2
788
+ else
789
+ echo "fallow-gate: fallow audit exited $STATUS, skipping." >&2
790
+ fi
791
+ exit 0
792
+ fi
793
+
794
+ exit 0
795
+ ```
796
+
797
+ Codex fallback (add to repo root `AGENTS.md`):
798
+
799
+ ```md
800
+ Before any `git commit` or `git push`, run `fallow audit --format json --quiet --explain`. If the verdict is `fail`, fix the reported findings before retrying. Treat JSON runtime errors like `{ "error": true, ... }` as non-blocking.
801
+ ```
802
+
803
+ Keep `fallow audit` in CI alongside this local gate. The hook only runs for Claude Code, not for human pushes or other agents, so it is a reinforcement layer rather than a replacement for server-side enforcement.
804
+
805
+ ### Remove the hook
806
+
807
+ ```bash
808
+ fallow setup-hooks --uninstall
809
+ ```
810
+
811
+ Removes the fallow-gate handler from `.claude/settings.json` (preserving any other handlers in the same matcher group), deletes `.claude/hooks/fallow-gate.sh` if it still carries the `# Generated by fallow setup-hooks.` marker, and strips the managed block from `AGENTS.md`. Idempotent: a second run reports `unchanged` / `not present` and exits 0.
812
+
813
+ Use `--force` to remove a hook script that the user has edited (the marker is no longer present). Use `--dry-run` to preview without touching files.
814
+
815
+ ### Distinguish from `fallow init --hooks`
816
+
817
+ `fallow init --hooks` is a different command: it scaffolds a shell-level Git pre-commit hook under `.git/hooks/` that runs `fallow` on changed files. That is the *human* enforcement path. `fallow setup-hooks` is the *agent* enforcement path, targeting `.claude/` and `AGENTS.md`. Both can live in the same repo: git hooks catch human commits, the setup-hooks gate catches agent commits.