Skip to content

chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15) - #40

Merged
rohithgollapalli merged 5 commits into
mainfrom
chore/fallow-stop-hook
Jun 12, 2026
Merged

chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)#40
rohithgollapalli merged 5 commits into
mainfrom
chore/fallow-stop-hook

Conversation

@rohithgollapalli

Copy link
Copy Markdown
Collaborator

Summary

Commits the fallow audit Stop hook directly into the repo at .claude/hooks/fallow-stop.sh so any contributor can wire it with no external dependency on the owner's private ~/tools/fallow-hook (which is macOS-only and inaccessible to others). The hook runs fallow audit --changed-since <base> over changed TS/JS files and drops a JSON report under .fallow/.

This unblocks #15 Step 2: once merged, wiring becomes a one-line Stop hook entry in .claude/settings.local.json pointing at the in-repo script.

Design — informational, never gates a session. The hook exits 0 on every path: no fallow binary, nothing changed vs base, audit error, even a failed symlink on Windows. The CI SARIF gate in #15 is the enforcement point, not this hook.

  • Honors FALLOW_DISABLE=1 (immediate no-op) and FALLOW_BASE_REF; base ref falls back mainmasterorigin/HEAD.
  • One-time install hint to stderr when fallow is absent.
  • 🌾 fallow audit (vs main): dead:N | dupes:N | complexity:N | circular:N summary to stderr.
  • .fallow/ added to .gitignore (runtime output); the script itself stays tracked + executable (100755).
  • .gitattributes pins *.sh to LF so a fresh clone on Windows (core.autocrlf=true) gets a runnable shebang instead of CRLF corruption — also hardens the existing scripts/hooks/*.sh.

Closes #36

Test plan

  • bash -n syntax check passes
  • Smoke-tested: fallow missing → one-time hint + exit 0 (no repeat on 2nd run)
  • Smoke-tested: no TS/JS changed vs base → silent no-op exit 0
  • Smoke-tested: FALLOW_DISABLE=1 → immediate no-op exit 0
  • Verified script staged 100755 and stored LF in index; .fallow/ ignored, script not
  • npm run typecheck clean (no src/ changes — tooling only)
  • npm run test green

Note: the audit summary parser is deliberately schema-tolerant (falls back to 0/?) since fallow isn't installed until #15 Step 1. It can be tightened against the real audit --format json schema once the devDependency lands.

Commit the fallow audit Stop hook directly into the repo so any
contributor can wire it with no external dependency on the owner's
private ~/tools/fallow-hook. The hook runs fallow audit --changed-since
over changed TS/JS files and writes a JSON report under .fallow/.

- Informational only: exits 0 on every path (no fallow binary, nothing
  changed, audit error, broken symlink on Windows). CI (#15) is the gate.
- Honors FALLOW_DISABLE=1 and FALLOW_BASE_REF; base ref falls back
  main -> master -> origin/HEAD. One-time install hint when fallow absent.
- .fallow/ added to .gitignore (runtime output); the script stays tracked.
- .gitattributes pins *.sh to LF so a fresh clone on Windows (autocrlf=true)
  gets a runnable shebang instead of CRLF corruption.

Closes #36

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
s-annam
s-annam previously approved these changes Jun 11, 2026

@s-annam s-annam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)

Summary

Clean, defensive, well-documented prerequisite. Commits an informational fallow audit Stop hook in-repo, removing the private ~/tools/fallow-hook dependency. Exits 0 on every path. All 8 acceptance criteria in #36 are met. Tooling-only (no src/), so no test suites apply.

Acceptance criteria — verified

Criterion Status
Tracked + executable (100755) ✅ diff shows new file mode 100755
Script not in .gitignore ✅ only .fallow/ added
.fallow/ in .gitignore
Exit 0 + one-time hint when fallow missing ✅ marker in .fallow/
Exit 0 silent when no TS/JS changed ✅ gate runs before binary check
FALLOW_DISABLE=1 immediate no-op ✅ first line after set
Fresh-clone wiring documented ✅ in header block
Windows note ✅ + bonus .gitattributes LF pin

Verified locally

  • bash -n clean on the fetched script.
  • Embedded python summary parser AST-parses and runs correctly on all 3 documented schema shapes: dict summary (dead:3 | dupes:2 | complexity:5 | circular:1), findings-list (dead:2 | dupes:1 | complexity:0 | circular:1), and garbage input (graceful all-0s). No crash path — missing python3 falls back to the ? summary.
  • set -uo pipefail without set -e is correct here: every && exit 0 and unchecked pipeline still reaches exit 0.

Highlights

  • .gitattributes *.sh text eol=lf is a genuine catch — protects the shebang from CRLF corruption on Windows core.autocrlf=true, and retroactively hardens the existing scripts/hooks/*.sh.
  • Install hint is gated behind the "files changed" check, so it never nags on no-op sessions.
  • Schema-tolerant parser with a documented provisional-status rationale tied to #15 Step 1.

Minor notes (non-blocking, optional)

  1. [Nit] Hook directory split: existing Claude hooks live at scripts/hooks/ (registered in .claude/settings.json); this lands at .claude/hooks/. The path is mandated by #36, so it's correct — just flagging that two hook locations now coexist.
  2. [Suggestion] The gate computes its own merge-base...HEAD + working-tree diff, but the audit relies on fallow's --changed-since. The two file sets can diverge slightly. Fine as a conservative gate; consider passing an explicit file list once the fallow JSON schema is pinned in #15.

Verdict

Action: APPROVE
Rationale: All acceptance criteria met, functionality verified locally, zero blocking items. The notes are optional and don't gate merge.

Pass the exact changed-file list the gate computed to `fallow audit` instead of relying on fallow re-deriving its own set via --changed-since, which can diverge (different diff base, deletions, non-TS/JS noise).

Falls back to --changed-since if this fallow build does not accept a positional file list, so behavior never regresses below the prior path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rohithgollapalli

rohithgollapalli commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review @s-annam! Addressed suggestion #2 (explicit file list) in 023b0e3.

The audit now runs against the exact set the gate computed ($changed) via fallow audit --format json -- <files…>, so the gate stays authoritative instead of letting fallow re-derive its own set from --changed-since (which can diverge on diff base, deletions, and non-TS/JS noise). Since fallow's CLI isn't pinned until #15, it falls back to --changed-since "$base_ref" if this build doesn't accept a positional file list — so behavior never regresses below the prior path, and every code path still reaches exit 0.

Left note #1 (the scripts/hooks/ vs .claude/hooks/ split) as-is since the path is mandated by #36.

@s-annam s-annam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)

Summary

Re-review. The prior APPROVE (s-annam) was dismissed when 023b0e3a ("audit the gate-selected file set (#36 review)") landed — a commit that implements the prior review's only open Suggestion: pass an explicit changed-file list rather than relying on fallow's --changed-since. The fix is correct and defensive (try explicit -- <files>, fall back to --changed-since if the report is empty). Still tooling-only, still exits 0 on every path.

Acceptance criteria (#36) — all 8 still met

Criterion Status
Tracked + executable (100755) new file mode 100755
Script not in .gitignore ✅ only .fallow/ added
.fallow/ in .gitignore
Exit 0 + one-time hint when fallow missing
Exit 0 silent when no TS/JS changed ✅ gate before binary check
FALLOW_DISABLE=1 immediate no-op
Fresh-clone wiring documented ✅ header block
Windows note ✅ + bonus .gitattributes LF pin

Path .claude/hooks/ is mandated by #36 — the two-hook-location split vs existing scripts/hooks/ is per-spec, not drift.

Verified

  • bash -n clean on the fetched script.
  • No src/ touched; no .ts/.tsx/.js changed → no test suites apply.
  • Explicit-list → --changed-since fallback logic reviewed; the [[ -s "$report" ]] guard correctly drives the fallback.

Highlights

  • The 023b0e3a fix makes the gate authoritative over the audited file set, with a documented provisional rationale tied to #15.
  • .gitattributes *.sh text eol=lf retroactively hardens the existing scripts/hooks/*.sh against CRLF shebang corruption.

Minor note (non-blocking)

  1. [Suggestion] mapfile (line 96) is bash 4+; stock macOS ships bash 3.2. Only reached once fallow is installed (post-#15), and env bash resolves to Homebrew bash 5 on the owner's box — so narrow. Given how hard the script bends for portability elsewhere (Windows git-bash, CRLF pin), a while IFS= read -r loop would close the last gap. Safe to defer to #15.
  2. The garbage-stdout edge (an unsupported audit -- <files> printing usage to stdout → non-empty report → fallback skipped) is already flagged in the code's own comment as provisional-until-#15. Fine as-is.

Verdict

Action: APPROVE
Rationale: All acceptance criteria met, prior review's suggestion now implemented, zero blocking items. The remaining note is optional and tied to #15.

# gate authoritative. Until fallow's CLI is pinned (#15) we don't assume it
# accepts positional files: try the explicit form, and if that yields no
# usable report, fall back to --changed-since (the prior behavior).
mapfile -t changed_files <<< "$changed"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion]: mapfile is bash 4+; stock macOS ships bash 3.2, so a contributor without Homebrew bash whose env bash resolves to /usr/bin/bash would hit mapfile: command not found here. Only reached once fallow is installed (post-#15), so narrow — but the script otherwise bends hard for portability (Windows git-bash, the .gitattributes CRLF pin). A while IFS= read -r line; do changed_files+=("$line"); done <<< "$changed" loop closes the gap. Safe to defer to #15.

@rohithgollapalli
rohithgollapalli merged commit b0df074 into main Jun 12, 2026
1 check passed
@rohithgollapalli
rohithgollapalli deleted the chore/fallow-stop-hook branch June 12, 2026 20:11
s-annam pushed a commit that referenced this pull request Jun 15, 2026
chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)
s-annam pushed a commit that referenced this pull request Jun 25, 2026
chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)
s-annam pushed a commit that referenced this pull request Jun 28, 2026
chore: add tracked fallow-stop.sh Stop hook (prerequisite for #15)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add fallow-stop.sh to repo as tracked hook script (prerequisite for #15)

2 participants