Skip to content

Latest commit

 

History

History
897 lines (644 loc) · 63.8 KB

local-first-code-quality-epic.md

File metadata and controls

897 lines (644 loc) · 63.8 KB

Local-First Code Quality Epic

Status: Complete (Phase 3 closed 2026-04-09) Scope: All smartwatermelon and nightowlstudiollc repos that are not forks of external projects Companion: WORKFLOW-DEEP-DIVE.md (live architecture reference) Plans: Phase 2 design · Phase 2 implementation · Infrastructure consolidation


1. Executive Summary

This epic moved code-quality review from a CI-first model (where every push paid for GitHub Actions runner time and waited for Sentry/Seer's external infrastructure) to a local-first model (where every keystroke through git commit and git push is reviewed locally by Claude-powered agents with full filesystem access, before any byte leaves the laptop).

The work spanned three phases over six weeks:

Phase Date What shipped
Phase 1 2026-03-25 Enhanced local review: semgrep pre-commit, full-diff pre-push review, adversarial-reviewer v1.4.0
Phase 2 2026-04-02 Whole-codebase review: shared issue library, --mode=codebase in run-review.sh, parallel pre-push codebase review
Phase 3 2026-04-09 Final alignment: Seer made truly non-blocking, redundant CI review removed across 7 repos, workflow patterns standardized, branch-protection gaps plugged

Outcome: Every owned repo now follows the same pattern. Local review is the source of truth for lint, format, security scanning, and semantic review. CI runs a focused summarizer review (the reusable claude-blocking-review.yml workflow) plus tests/build/deploy. Seer's findings flow through as advisory input, no longer gating merges.

Cost impact: Eliminated dozens of redundant CI runner minutes per push across the fleet. Pre-merge no longer stalls on Sentry's flaky/rate-limited Seer infrastructure.

Quality impact: Local Phase 2 codebase review caught two real instances of doc/code drift during this session alone (CLAUDE.md Protocol 5 in claude-config, CI workflow description in archive-resolver) that the previous CI-only model would have missed entirely. This validates the premise that local review with full tool access beats remote diff-only review.


2. The Epic at a Glance

┌─────────────────────────────────────────────────────────────────────────┐
│  BEFORE                                                                  │
│                                                                          │
│  commit ─────► push ─────► CI: lint, format, shellcheck, markdownlint   │
│                            CI: Claude full review (anthropics action)   │
│                            CI: Seer (NEUTRAL = blocking)                │
│                            CI: tests, build, deploy                     │
│                                                                          │
│  Problems: ~5-10 min CI per push, Seer flakiness blocked merges,        │
│            duplicated lint locally + remotely, CLAUDE.md drift          │
│            uncaught by diff-only review.                                │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│  AFTER                                                                   │
│                                                                          │
│  commit ─────────────────► pre-commit: prettier, eslint (where local),  │
│                            shellcheck, markdownlint, yamllint, semgrep, │
│                            black, flake8, html-tidy                     │
│                                                                          │
│                            review: code-reviewer agent + adversarial-   │
│                            reviewer agent (elevated for security files) │
│                                                                          │
│  push ──────────────────► pre-push: full-diff review + Phase 2 whole-    │
│                            codebase review (full tool access)           │
│                                                                          │
│                            CI: claude-blocking-review.yml (summarizer)  │
│                            CI: Seer (advisory, non-blocking)            │
│                            CI: tests, build, deploy                     │
│                                                                          │
│  merge ─────────────────► pre-merge-review.sh: AI analysis of all       │
│                            comments, CI status, inline reviews          │
│                            (Seer findings flow through as advisory)     │
└─────────────────────────────────────────────────────────────────────────┘

3. Background: Why Local-First?

The original model

Before this epic, code review was concentrated on the GitHub side. Each PR triggered:

  1. CI lint suite — shellcheck, shfmt, markdownlint, yamllint, html-tidy, prettier, eslint, black, flake8, etc. Each as a separate job, often on macos-latest at 10x Linux cost.
  2. CI test suite — pytest / vitest / jest / wrangler dry-run / etc.
  3. Anthropic claude-code-action — invoked directly with a verbose generic prompt asking for "code quality, bugs, performance, security, test coverage" feedback. Posted as PR comments.
  4. Sentry/Seer Code Review — Sentry's AI code review product, posted as a check-run with NEUTRAL conclusion when findings existed.
  5. Branch protection — required claude-review (the inline workflow's job name) to pass before merge.

The problems

  • Cost. Every push to every repo paid GitHub Actions billing (ubuntu $0.008/min, macos $0.08/min). Across multiple active repos with iterative pushes during development, this was tens of dollars per week of pure runner time on lint jobs that already pass locally. The user has Max 200 subscription, so local Claude usage is uncapped — the cost asymmetry favors local review heavily.

  • Latency. Push → wait 3–10 minutes for CI to come back → see lint failure that pre-commit could have caught in 200ms → fix → push again → wait again. The feedback loop was slow and wasteful.

  • Seer flakiness. Sentry's Seer runs on Sentry infrastructure, not GitHub. It is regularly rate-limited or overloaded. When Seer was a hard merge gate (via NEUTRAL = blocking in the merge wrapper), merges stalled on infrastructure outside Sentry's GitHub-side SLA. The only options were "wait indefinitely" or "bypass Seer" — neither acceptable.

  • Diff-only review can't catch cross-file bugs. The CI Claude review only saw the diff. It missed cross-file field mismatches, dead UI elements, date/timezone drift between modules, data-flow ordering, cache-key inconsistencies, and platform-specific bugs. Phase 2's design doc enumerates these explicitly with PR evidence.

  • Drift. Documentation references to CI workflows, lint commands, and protocol rules silently rot when the underlying systems change. Diff-only review can't notice that a CLAUDE.md sentence is now contradicted by the file the same PR is editing. This bit us twice in this session alone — see §11 Validation Evidence.

The local-first thesis

Three claims:

  1. Local review with full filesystem access beats CI review with diff-only access. When the agent can grep/read/walk the whole repo, it catches the same bugs Seer catches plus the cross-file drift that pure diff review misses.
  2. Pre-commit hooks are the right enforcement point for lint/format. They run in 200ms on the laptop, before the push. CI lint jobs are 100% redundant with them (assuming the developer is using the authorized commit path).
  3. CI's role shrinks to: tests + integration + a focused summarizer review + advisory bot input. Tests need a clean CI environment. Summarizer review on CI is a sanity check, not the primary review. Bot inputs (Seer, Codecov, etc.) are advisory.

If all three claims hold, the entire CI lint stack is dead weight, Seer becomes informational, and local review becomes the gate.


4. Architecture: The Local-First Stack

See WORKFLOW-DEEP-DIVE.md for source-attributed details on every layer. This section is the post-epic snapshot.

Layer 1 — Claude Code PreToolUse hooks

Block destructive or out-of-band operations before they run: direct commits to main, --no-verify bypass, git worktree outside the supported wrapper, gh pr merge via API workarounds, merge-locks-file writes, etc. Source: ~/.claude/scripts/hook-block-*.sh.

Layer 2 — Global git pre-commit hook

Source: ~/Developer/dotfiles/git/hooks/pre-commit (symlinked from ~/.config/git/hooks/).

Runs the framework pre-commit tool with the global config at ~/.config/pre-commit/config.yaml, which currently provides:

Tool Files Role
black *.py Python format
flake8 (+ bugbear) *.py Python lint
shell-lint-fix (custom) *.sh Shellcheck + shfmt via lint-shell.sh
yamllint *.yml, *.yaml YAML lint
html-tidy *.html HTML validation
markdownlint (--fix) *.md Markdown lint with auto-fix
luacheck *.lua Lua lint
prettier *.{js,jsx,ts,tsx,json,css} JS/TS/JSON/CSS format

Then semgrep scan --config auto --error --quiet . for SAST. Repos can override behavior via a project-local .pre-commit-config.yaml (every owned repo has one).

Layer 3 — Per-repo pre-commit overrides

Each repo owns a .pre-commit-config.yaml that either inherits the global setup verbatim or layers project-local hooks on top. Example: tensegrity adds local ESLint + tsc --noEmit hooks; amelia-boone adds a project-local prettier override with custom excludes.

Layer 4 — Local code review agents

Source: ~/.claude/agents/code-reviewer.md and ~/.claude/agents/adversarial-reviewer.md, invoked by ~/Developer/claude-config/hooks/run-review.sh from inside the pre-commit hook.

  • code-reviewer runs on every commit. Catches blocking issues, surfaces non-blocking concerns.
  • adversarial-reviewer runs on every commit with elevated scrutiny when security-critical files (hooks, auth, secrets) are touched. Tries to break the change.

Both agents read the staged diff, the committed files, and the local context.

Layer 5 — Pre-push hook

Source: ~/Developer/dotfiles/git/hooks/pre-push.

Runs run-review.sh --mode=full-diff (Phase 1 review of the entire push contents) AND run-review.sh --mode=codebase (Phase 2 review with full tool access against the post-push tree). Both must pass before the push reaches GitHub.

When the codebase review surfaces non-blocking issues, they're filed as GitHub issues automatically via the shared lib-review-issues.sh library so Ralph burndown can triage them overnight.

Layer 6 — Pre-merge AI analysis

Source: ~/Developer/claude-config/hooks/pre-merge-review.sh (symlinked into ~/.claude/hooks/).

Triggered by the gh wrapper before any gh pr merge call. Fetches PR review data, inline comments, CI status, and feeds them to Claude with a structured analysis prompt. Returns SAFE_TO_MERGE or BLOCK_MERGE. Hard-blocks (independent of AI) include:

  • CHANGES_REQUESTED reviews
  • NEUTRAL CI conclusions (with exclusions for Netlify informational checks AND, as of 2026-04-09, Seer Code Review)
  • merge-lock authorization missing

Layer 7 — Merge-lock authorization

Source: ~/Developer/claude-config/hooks/merge-lock.sh.

Two-party-merge enforcement: a human must run merge-lock auth <PR#> "reason" before the agent's gh pr merge call will be accepted by the pre-merge wrapper. Lock files live at ~/.claude/merge-locks/pr-<N>.lock and expire after 30 minutes.

Open follow-up: smartwatermelon/claude-config#108 — multi-PR auth (merge-lock auth 100,204,553 "ok") for batch operations.

Layer 8 — CI (the surviving slice)

After Phase 3, every owned repo's .github/workflows/ directory has at most:

  • claude-blocking-review.yml — calls the shared reusable workflow at smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v1. Runs a focused summarizer review (bugs, reliability, security, data-loss risks). Posts as required status check claude-review / run-review.
  • claude.yml — calls the shared reusable workflow at smartwatermelon/github-workflows/.github/workflows/claude-assistant.yml@v1. Responds to @claude mentions in issues, PR comments, and reviews. Optional per repo.
  • Tests — test.yml / ci.yml / etc. Real test execution that needs a CI environment.
  • Deploy/release — release.yml, deploy-*.yml, etc. Real deployment work.
  • Repo-specific automation — night-shift.yml (Ralph burndown), update-mirrors.yml, changelog-*.yml, etc.

CI no longer runs lint, format-check, typecheck (in repos where local pre-commit covers), the verbose direct claude-code-action review, or any other duplication.

Layer 9 — Branch protection

Pattern: required_status_checks.checks = [{"context": "claude-review / run-review", "app_id": 15368}], strict: true (branches must be up to date), enforce_admins: false. Free private repos (no GitHub Pro) can't have branch protection — flagged as gaps where applicable.

Layer 10 — Sentry / Seer (now advisory)

Sentry's GitHub App is installed at the nightowlstudiollc org level. Seer Code Review check-runs appear on PRs that touch Sentry-managed code. As of 2026-04-09:

  • Not in any repo's required_status_checks. (Verified across all 29 repos this session.)
  • Excluded from pre-merge-review.sh's NEUTRAL hard-block (the script filters by name prefix Seer*).
  • Explicit AI prompt rule (SEER EXCEPTION) tells the analyzer to treat Seer as advisory regardless of conclusion.
  • Inline comment fetcher still pulls Seer's findings through to the AI analysis, so any actual issues are surfaced — just not as a hard block.

5. Phase 1 — Enhanced Local Review (2026-03-25)

Goal: Get local review to parity with CI lint + basic semantic catches.

Shipped:

  • Semgrep added to the global pre-commit hook (semgrep scan --config auto --error .)
  • Full-diff pre-push review via run-review.sh --mode=full-diff (reviews the entire push contents, not just the latest commit)
  • adversarial-reviewer agent v1.4.0 with elevated scrutiny on security-critical files
  • Hard-block on CHANGES_REQUESTED reviews in pre-merge-review.sh
  • Merge-lock authorization gating in pre-merge-review.sh
  • GitHub issue auto-filing for non-blocking review concerns

Key commits in claude-config:

  • 97ba664 — chore: clean repo state for public release
  • 6f08ea1 — fix(pre-merge-review): hard-block CHANGES_REQUESTED before Claude is invoked (#62)
  • 9bd59ba — feat: create GitHub issues for non-blocking merge review concerns (#70)
  • 2ab32b6 — fix(pre-merge): prevent duplicate issues and require flags for non-interactive merges (#77)
  • bd15018 — fix: ignore netlify redirect checks when evaluating mergability (#80)

Status at end of Phase 1: Local review caught more than before, but Seer was still catching cross-file bugs that diff-only local review missed. The gap was context, not model capability.


6. Phase 2 — Whole-Codebase Review (2026-04-02)

Design doc: docs/plans/2026-04-02-phase2-local-whole-codebase-review.md Implementation plan: docs/plans/2026-04-02-phase2-implementation.md

Goal: Replace Seer's cross-file semantic catches with local review that has full filesystem access.

Shipped:

  • Extracted shared issue library (lib-review-issues.sh) from pre-merge-review.sh
  • Added --mode=codebase to run-review.sh that invokes Claude CLI with Bash/Read/Grep/Glob tools enabled
  • Wired the new mode into the pre-push hook in parallel with the existing full-diff review
  • Both reviews must pass before push

Architecture:

git push ──► pre-push hook
              │
              ├── run-review.sh --mode=full-diff   (Phase 1 — diff-only, fast)
              │
              └── run-review.sh --mode=codebase    (Phase 2 — full tool access)
                                                    │
                                                    └── Claude CLI with
                                                        Bash, Read, Grep,
                                                        Glob tool grants
                                                        against the post-
                                                        push working tree

Key commit in claude-config:

  • 6f91fb1 — feat(hooks): Phase 2 — shared issue library + whole-codebase review (#88)

Status at end of Phase 2 (2026-04-03 → validation week): Local review now catches the same bug classes as Seer. The remaining problem was that Seer was still wired up as a hard-blocker in the merge gate, and several repos still had CI lint jobs duplicating local pre-commit work. Phase 3 (this session) closed those gaps.


7. Phase 3 — Final Alignment (2026-04-09, this session)

Approach: The user approved a four-part scope:

  1. Delete lint/format CI jobs covered locally
  2. Keep claude-blocking-review.yml (the summarizer)
  3. Make Seer non-blocking everywhere it's currently blocking
  4. Keep tests, deploy, release workflows

Sequencing: Audit all repos first, present per-repo diffs, execute approved changes one PR at a time with merge-lock auth between each.

7.1 Seer made truly non-blocking

Discovery: During the audit phase, I checked every repo's required_status_checks and every recent PR's check-run rollup. No repo had Seer in its required status checks at the GitHub branch protection layer. Seer was already non-blocking as far as GitHub was concerned.

The actual blocker was inside pre-merge-review.sh: a jq filter at lines 351–361 that hard-blocked any merge with a NEUTRAL CI conclusion (with carve-outs only for three Netlify informational checks). Sentry's Seer reports findings via NEUTRAL, so the merge gate stalled on every Seer finding regardless of branch protection.

Fix (PR smartwatermelon/claude-config#107):

Two surgical edits to pre-merge-review.sh:

NEUTRAL_CHECKS=$(echo "${PR_JSON}" | jq -r '
  .statusCheckRollup // []
  | .[]
  | select(
      .conclusion == "NEUTRAL"
      and (.name | startswith("Pages changed") | not)
      and (.name | startswith("Header rules") | not)
      and (.name | startswith("Redirect rules") | not)
      and (.name | startswith("Seer") | not)         # NEW
    )
  | "- \(.name): \(.conclusion)"
' 2>&1) || true

And a new rule in the AI analysis prompt under CRITICAL RULES:

- **SEER EXCEPTION**: "Seer Code Review" check is NON-BLOCKING regardless of
  conclusion (SUCCESS/FAILURE/NEUTRAL/PENDING). Seer runs on Sentry
  infrastructure (flaky/rate-limited) and reports findings via NEUTRAL
  conclusion. Its findings flow through as inline review comments below and
  are advisory only. Do not block merge on Seer check status — examine its
  inline comments alongside other input.

The inline-comment fetcher at lines 394+ was unchanged — it still pulls all bot comments (including Seer) into the AI analysis. So Seer's findings are still surfaced for examination, just not as a hard block.

Doc drift caught: Phase 2 codebase review (running on the prior commit during pre-push) flagged that ~/.claude/CLAUDE.md Protocol 5 still said "Seer Code Review is treated as blocking — do not merge until it passes." Updated to:

Seer Code Review is advisory / non-blocking — its inline findings flow through to the local pre-merge AI analysis but do not block merge. (Seer runs on Sentry infrastructure, which is flaky and rate-limited; treating it as blocking creates merge stalls. Examine its findings as one input alongside CI, human reviewers, and the local code-reviewer agents.)

Non-blocking follow-up filed: smartwatermelon/claude-config#106 — the error message at pre-merge-review.sh:375 still lists "Sentry/Seer Code Review: Has inline comments on code" as the first common cause of NEUTRAL. After the diff, that branch is unreachable for Seer. Cosmetic, leave for Ralph burndown.

7.2 CI review cleanup

For each repo with redundant CI lint, the audit identified what was covered locally and what was not. Three repos needed actual changes:

smartwatermelon/archive-resolver (PR #8)

  • Removed: the shellcheck job from ci.yml. Covered locally by the global shell-lint-fix pre-commit hook.
  • Kept: the syntax job (validates mirrors.txt format and bash -n install.sh — repo-specific, not in any pre-commit hook) and the dry-run job (Linux-side install.sh exercise, needs CI environment).
  • Kept (separately): release.yml still runs shellcheck as a final pre-publish safeguard.
  • Doc drift fixed: CLAUDE.md line 51 listed "ShellCheck" as a ci.yml job. Updated to note shellcheck now lives in the global pre-commit hook.

nightowlstudiollc/amelia-boone (PR #30)

  • Removed: the Checking code format step (pnpm run format:check) from ci.yml. Covered locally by the global prettier pre-commit hook plus the project-local prettier override in amelia-boone's .pre-commit-config.yaml.
  • Kept: the lint step (ESLint — no global lint hook, no project-local lint hook) and the build step (Astro check + build, not in any pre-commit hook).
  • Doc drift fixed: CLAUDE.md had three references describing format:check as "CI enforces this." Updated all three to credit the pre-commit hook as the source of truth.

smartwatermelon/tensegrity (PR #75)

  • Removed: the Run linter step (npm run lint) AND the Run type check step (npx tsc --noEmit) from ci.yml. Both are covered by tensegrity's project-local .pre-commit-config.yaml (it has explicit ESLint and tsc --noEmit hooks).
  • Kept: tests + coverage upload.
  • Renamed: the job from lint-and-test to test to reflect what's actually left. Safe rename — tensegrity is private without GitHub Pro, no branch protection / required status checks reference the old job name.
  • Side fix (commit 1 of 2): added .semgrepignore excluding poc/. Reason: poc/3d-prototype.html had pre-existing missing-SRI findings on CDN-loaded Three.js scripts, blocking unrelated commits via the global semgrep scan step. The PoC files are scratch demos — never shipped, never loaded by the app, never user-facing. Pinning to integrity hashes would be churn without security benefit.

7.3 Workflow standardization

Two repos still had the old claude-code-review.yml pattern (a pull_request-triggered workflow that called anthropics/claude-code-action@v1 directly with a verbose generic prompt). Both were migrated to the standardized claude-blocking-review.yml pattern that calls the shared reusable workflow.

nightowlstudiollc/night-owl-studio (PR #22)

  • Deleted claude-code-review.yml (57 lines of inline workflow with generic full-review prompt)
  • Created claude-blocking-review.yml (19 lines, calls smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v1)
  • Branch protection: previously had no protection at all on main. After merge, enabled protection via gh api with claude-review / run-review as required check.

nightowlstudiollc/kebab-tax-netlify (PR #161)

  • Deleted claude-code-review.yml (same generic inline workflow)
  • Created claude-blocking-review.yml (standardized reusable pattern)
  • Branch protection: required status check name was the bare claude-review (the inline workflow's job name). Swapped to claude-review / run-review (the caller-job / reusable-job context produced by the shared workflow). The swap was atomic — done as a single gh api PUT call BEFORE the migration PR was opened, so the repo was never unprotected and the new workflow's check name was already required when the PR ran.
  • Doc drift fixed (commit 2 of 2): CLAUDE.md had a directory tree diagram listing the old claude-code-review.yml. Updated.

7.4 Branch-protection plugging

smartwatermelon/crazy-larry (no PR — direct API)

  • Visibility change: private → public. Repo contents are a single static HTML joke site (Crazy Larry's Used Spaceships) plus a QR code image. No secrets, no PII, nothing sensitive. Reviewed before publishing.
  • Branch protection: previously 403'd because the repo was private without GitHub Pro. After the visibility change, enabled protection via gh api with claude-review / run-review as required check. The workflow file (claude-blocking-review.yml) was already in place — no PR needed.

8. Repository Inventory

Owned, in-scope, post-epic state

Owner Repo CI lint Claude review pattern Branch protection Notes
smartwatermelon archive-resolver None (this session) claude-blocking-review reusable ✅ claude-review / run-review release.yml still runs shellcheck pre-publish
smartwatermelon claude-config n/a claude-blocking-review reusable ✅ claude-review / run-review Hosts the shared review hooks
smartwatermelon claude-wrapper n/a claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon dev-env n/a claude-blocking-review reusable ✅ claude-review / run-review This repo
smartwatermelon dotfiles n/a claude-blocking-review reusable ✅ claude-review / run-review Hosts global pre-commit hook
smartwatermelon github-workflows n/a self (reusable workflows live here) ✅ claude-review / run-review
smartwatermelon homebrew-tap tests only (test-casks) claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon mac-dev-server-setup None (pre-Phase 3) claude-blocking-review reusable ✅ claude-review / run-review Pre-Phase 3 cleanup (#25)
smartwatermelon mac-server-setup None (pre-Phase 3) claude-blocking-review reusable ✅ claude-review / run-review Pre-Phase 3 cleanup (#118)
smartwatermelon pre-commit-testing n/a claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon projectinsomnia n/a claude-blocking-review reusable ✅ claude-review / run-review Has night-shift.yml automation
smartwatermelon ralph-burndown tests only claude-blocking-review reusable ✅ claude-review / run-review Has night-shift.yml, burndown-triage.yml
smartwatermelon slack-mcp tests only (pytest) claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon smartwatermelon-marketplace n/a claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon spokane-snow n/a claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon swift-progress-indicator release only claude-blocking-review reusable ✅ claude-review / run-review
smartwatermelon crazy-larry n/a claude-blocking-review reusable ✅ claude-review / run-review (this session) Made public this session
smartwatermelon tensegrity tests only (this session) claude-blocking-review reusable ⚠️ private without Pro, no protection Will move to nightowlstudiollc; protection becomes available then
smartwatermelon .github n/a n/a (profile repo) ⚠️ private without Pro Stays private
smartwatermelon scripts n/a claude-blocking-review reusable ⚠️ private without Pro Stays private
nightowlstudiollc amelia-boone lint + build (this session) claude-blocking-review reusable ✅ claude-review / run-review
nightowlstudiollc financial-agent n/a claude-blocking-review reusable ✅ claude-review / run-review Private
nightowlstudiollc juliet-cleaning html-validation only claude-blocking-review reusable ✅ claude-review / run-review Nu HTML Checker + lychee link check kept (not pure lint)
nightowlstudiollc kebab-tax tests + release-gate (pre-Phase 3) claude-blocking-review reusable ✅ claude-review / run-review Pre-Phase 3 cleanup (#1125)
nightowlstudiollc kebab-tax-netlify tests + lint-functions + validate-* claude-blocking-review reusable (this session) ✅ claude-review / run-review (this session) Required check name swapped from bare claude-review
nightowlstudiollc networth-agent n/a n/a (no review configured) n/a Public mirror of private financial-agent — review not required
nightowlstudiollc night-owl-studio n/a claude-blocking-review reusable (this session) ✅ claude-review / run-review (this session) Newly protected
nightowlstudiollc vpn-lan-bridge n/a claude-blocking-review reusable ✅ claude-review / run-review
nightowlstudiollc yesteryear lint + tsc + test + build claude-blocking-review reusable ✅ claude-review / run-review Lint + tsc kept (no project-local pre-commit hooks for them)

Out of scope (not in audit)

  • Forks the user does not develop on: headroom, superpowers, tomatobar. Per global memory, environment files (CLAUDE.md, .gitignore changes) should not be committed to forks.
  • Archived repos: excluded by gh repo list --no-archived.

9. PR Ledger (this session)

Strict chronological order, all 7 PRs through the same protocol: branch → edit → local pre-commit review → push → pre-push codebase review → PR → CI → merge-lock auth → merge → cleanup.

Title: fix(pre-merge): make Seer Code Review non-blocking Files: hooks/pre-merge-review.sh (script + AI prompt edits), CLAUDE.md (Protocol 5 wording) Reviewer outcome: PASS local + Phase 2 caught CLAUDE.md drift on first push, fixed in second commit, second push PASS, CI PASS Side effect: filed #106 (cosmetic — stale Seer error text in now-unreachable code path) Why first: subsequent PRs in this session would trigger the merge gate, and applying the Seer fix early let them benefit from the relaxed rule. Symlink layout (~/.claude/hooks/pre-merge-review.sh → ~/Developer/claude-config/hooks/pre-merge-review.sh) means the local edit took effect immediately — but committing was still important for cross-machine reproducibility.

Title: ci: drop redundant shellcheck job Files: .github/workflows/ci.yml (delete shellcheck job), CLAUDE.md (CI workflows section) Reviewer outcome: Local PASS on first try (after CLAUDE.md fix bundled), Phase 2 PASS, CI PASS

Title: ci: drop redundant format:check step Files: .github/workflows/ci.yml (delete format:check step), CLAUDE.md (commands + before-committing sections) Reviewer outcome: Local PASS, Phase 2 PASS, CI PASS

Title: ci: drop redundant lint and typecheck steps Commits:

  1. chore: add .semgrepignore to exclude poc/ directory (separated as its own commit because the PoC SRI findings were unblocking the second commit)
  2. ci: drop redundant lint and typecheck steps; rename job to 'test' Reviewer outcome: Local PASS, Phase 2 PASS (filed #74 about job rename → branch protection orphan; verified safe and closed since tensegrity has no protection), CI PASS Note: First time Seer ran on a PR in this session — shows up in the AI's CI rollup as a passing check, not blocking, exactly as designed.

Title: ci: migrate to claude-blocking-review reusable workflow Files: delete .github/workflows/claude-code-review.yml (57 lines), create .github/workflows/claude-blocking-review.yml (19 lines) Reviewer outcome: Local PASS, Phase 2 PASS, CI PASS (new check claude-review / run-review) Post-merge action: enabled branch protection via gh api PUT /repos/.../branches/main/protection with the new check as required

Title: ci: migrate to claude-blocking-review reusable workflow Commits:

  1. ci: migrate to claude-blocking-review reusable workflow (delete old + create new)
  2. docs: update CLAUDE.md tree to reference claude-blocking-review.yml (Phase 2 caught it) Pre-PR action: swapped required status check from bare claude-review to claude-review / run-review via gh api PUT. Atomic — repo was never unprotected. Reviewer outcome: Local PASS, Phase 2 PASS, CI PASS Side effect: filed #159 (fixed in commit 2 of same PR), #160 (cosmetic — workflow tree incomplete, leave for burndown)

PR 7 — none (crazy-larry direct API change)

Action: gh repo edit smartwatermelon/crazy-larry --visibility public --accept-visibility-change-consequences followed by gh api PUT /repos/.../branches/main/protection Why no PR: the workflow file was already in place — only the visibility flag and the protection enable were needed. Both are repo-administrative actions, not file edits.


10. Reference Cards

Standard claude-blocking-review.yml

This is the canonical content for any repo's caller workflow. Same in every repo on the new pattern.

name: Claude Blocking Review

on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]

permissions:
  contents: read
  pull-requests: write
  issues: write
  id-token: write

jobs:
  claude-review:
    uses: smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v1
    with:
      pr_number: ${{ github.event.pull_request.number }}
    secrets:
      claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Required secret: CLAUDE_CODE_OAUTH_TOKEN at repo or org level.

Standard branch protection PUT body

{
  "required_status_checks": {
    "strict": true,
    "checks": [
      {"context": "claude-review / run-review", "app_id": 15368}
    ]
  },
  "enforce_admins": false,
  "required_pull_request_reviews": null,
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}

App ID 15368 is the GitHub Actions app. The contexts field must be omitted (not just empty) when using the newer checks field — passing both fails with "no subschema in anyOf matched."

Useful gh api commands

# Check current required status checks for a repo
gh api repos/OWNER/REPO/branches/main/protection \
  --jq '[.required_status_checks.contexts[]?, .required_status_checks.checks[]?.context?] | unique | .[]'

# Atomic swap of required status check
gh api -X PUT repos/OWNER/REPO/branches/main/protection \
  -H "Accept: application/vnd.github+json" --input - <<'JSON'
{
  "required_status_checks": {
    "strict": true,
    "checks": [{"context": "claude-review / run-review", "app_id": 15368}]
  },
  "enforce_admins": false,
  "required_pull_request_reviews": null,
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}
JSON

# Get check-run inventory for a PR's HEAD SHA
sha=$(gh api repos/OWNER/REPO/pulls/PR# --jq '.head.sha')
gh api repos/OWNER/REPO/commits/$sha/check-runs --jq '.check_runs[] | "\(.name) [\(.status)] :: \(.app.slug)"'

# List installed GitHub Apps at org level
gh api orgs/ORG/installations --jq '.installations[] | .app_slug'

# Audit Claude review configuration across all owned repos
~/Developer/github-workflows/claude-review-audit.sh [--verbose]

pre-merge-review.sh Seer exception (final form)

# --- Check for NEUTRAL CI status (blocking) ---
# Some checks set status to "neutral" when there are unresolved comments.
# This is a hard block - don't proceed to AI analysis.
#
# Exclusions (NEUTRAL on these is NOT a block):
# - "Pages changed" / "Header rules" / "Redirect rules" — Netlify informational
#   checks that return NEUTRAL when nothing changed. Informational only.
# - "Seer Code Review" (and any "Seer*" check) — Sentry's Seer reports findings
#   via NEUTRAL conclusion, but it runs on Sentry infrastructure (flaky/rate-
#   limited). Treated as non-blocking advisory: Seer's inline comments still
#   flow through to the AI analysis below via the inline comments fetcher,
#   so any findings are surfaced as advisory input — just not as a hard block.
NEUTRAL_CHECKS=$(echo "${PR_JSON}" | jq -r '
  .statusCheckRollup // []
  | .[]
  | select(
      .conclusion == "NEUTRAL"
      and (.name | startswith("Pages changed") | not)
      and (.name | startswith("Header rules") | not)
      and (.name | startswith("Redirect rules") | not)
      and (.name | startswith("Seer") | not)
    )
  | "- \(.name): \(.conclusion)"
' 2>&1) || true

The CRITICAL RULES section of the AI analysis prompt was extended with:

- **SEER EXCEPTION**: "Seer Code Review" check is NON-BLOCKING regardless of
  conclusion (SUCCESS/FAILURE/NEUTRAL/PENDING). Seer runs on Sentry
  infrastructure (flaky/rate-limited) and reports findings via NEUTRAL
  conclusion. Its findings flow through as inline review comments below and
  are advisory only. Do not block merge on Seer check status — examine its
  inline comments alongside other input.

Merge-lock authorization flow

# Human (any operator):
merge-lock auth <PR#> "ok"
# → writes ~/.claude/merge-locks/pr-<N>.lock, valid for 30 minutes

# Agent (after auth received):
gh pr merge <PR#> --squash --delete-branch
# → routed through the gh wrapper → pre-merge-review.sh checks merge-lock
#   first, then runs AI analysis, then proceeds with merge

Multi-PR auth (merge-lock auth 100,204,553 "ok") is filed as feature request smartwatermelon/claude-config#108.


11. Validation Evidence

This is the most important section. Phase 2 codebase review's job is to catch the kind of cross-file drift that pure diff review can't see. It demonstrably worked twice during this session, on the first commit of the very change that was meant to demonstrate it.

Catch 1 — claude-config CLAUDE.md Protocol 5

PR #107's first commit edited hooks/pre-merge-review.sh to make Seer non-blocking. Phase 2 codebase review (during the pre-push hook) examined the diff in context and reported:

VERDICT: FAIL ISSUE: CLAUDE.md:124 still declares Seer blocking, contradicting new non-blocking behavior SEVERITY: BLOCKING LOCATION: CLAUDE.md:124 DETAILS: The line "Seer Code Review is treated as blocking — do not merge until it passes." in Protocol 5 contradicts the new behavior introduced by this diff (NEUTRAL exclusion at hooks/pre-merge-review.sh:363 and the SEER EXCEPTION rule at lines 651–656 which mark Seer as NON-BLOCKING regardless of conclusion). This is Claude's own operating manual — leaving it stale will cause future sessions to block on Seer while the hook allows it, producing contradictory guidance.

The reviewer:

  1. Read the diff
  2. Recognized the change was about Seer's blocking semantics
  3. Walked the codebase looking for other references to Seer's blocking status
  4. Found the CLAUDE.md sentence that would now be contradicted
  5. Reported it as a hard block with file:line precision

A diff-only reviewer cannot do this. It would have approved the script change because the diff is internally consistent. The drift would have shipped, and future Claude sessions would have followed the stale CLAUDE.md rule until someone happened to notice.

Catch 2 — archive-resolver CLAUDE.md CI workflows

PR #8's first commit removed the shellcheck job from archive-resolver/.github/workflows/ci.yml. Both code-reviewer and adversarial-reviewer flagged it:

ISSUE: Removing ShellCheck job eliminates the only CI enforcement of shell script linting SEVERITY: BLOCKING LOCATION: .github/workflows/ci.yml:12-22 DETAILS: The project's CLAUDE.md explicitly documents ShellCheck as a core CI workflow ("ci.yml: ShellCheck, bash syntax, mirrors.txt format validation, Linux dry-run"). Removing it drops shell script quality enforcement on PRs and pushes to main.

Same pattern: the reviewers caught that the CLAUDE.md description of ci.yml would no longer be accurate after the diff. Required updating both the workflow file AND the doc in the same PR. Bundled and re-committed; second commit passed.

Other validation signals

  • PR #75 (tensegrity) showed Seer in the CI status rollup with conclusion SUCCESS and the AI analysis explicitly named Seer as a passing check — first end-to-end confirmation that the new Seer-non-blocking logic works correctly when Seer happens to run.
  • PR #161 (kebab-tax-netlify) completed a branch-protection swap atomically (old required check removed and new one added in a single gh api PUT call) without ever leaving the repo unprotected. The PR created with the new workflow was immediately gated by the new check name; CI passed; merge proceeded.
  • All 7 PRs passed local pre-commit + pre-push + CI on the first or second push. Iteration was driven entirely by Phase 2 catching real drift, not by infrastructure failures.

12. Lessons Learned

What worked

Pre-staging local commits while CI runs. During each PR's CI wait window, the next repo's branch was prepared (clone if missing, branch, edit, commit, validate locally). When the previous PR merged, the next push was instant. Cut wall-clock time roughly in half.

Atomic branch-protection swaps. For kebab-tax-netlify, the required-check rename was done as a single gh api PUT call before the migration PR was opened. The repo was never unprotected, the PR's new workflow was immediately the gating check, and the merge was uneventful. No "remove → push → wait → re-add" dance.

.semgrepignore for non-shipped PoC code. When pre-existing security findings in scratch files were blocking unrelated commits, scoping semgrep away from poc/ was the right move. Pinning a Three.js demo's CDN scripts to integrity hashes would have been pure churn with zero security benefit.

Filing non-blocking issues as GitHub issues for Ralph burndown. Phase 2 surfaces cosmetic concerns it doesn't block on. Filing them as issues meant they're tracked, triagable, and don't pollute the PR. Several were filed this session — #106, #108, #159, #160, #74 — and only the substantive ones got immediate attention.

Two-turn PR lifecycle (create + merge as separate authorizations). Protocol 6 enforced this strictly. Every PR got explicit human merge-lock auth before the agent's gh pr merge ran. No accidental merges, no "sounded like approval."

What was tricky

GitHub branch protection API schema. The required_status_checks block accepts either contexts (legacy) or checks (newer) but not both — passing both yields a confusing "no subschema in anyOf matched" error. Solution: always use checks only.

Per-repo .pre-commit-config.yaml variation. Some repos extended the global config heavily (tensegrity has full ESLint + tsc), some added thin overrides (amelia-boone adds a project-scoped prettier), some were empty (inherit-only). Determining what's "covered locally" required reading each repo's config, not assuming.

Branch protection 403s on free private repos. GitHub Pro is required for branch protection on private repos. Four repos (smartwatermelon/.github, crazy-larry, scripts, tensegrity) were affected. Made public where appropriate (crazy-larry); flagged the rest as known gaps.

Required status check name format. When migrating from a direct-action workflow (job name only) to a reusable workflow (caller-job / reusable-job), the check context changes from claude-review to claude-review / run-review. Without coordinating the branch protection update, the migration PR can't merge because the old required check disappears. The atomic swap pattern is the answer.

gh repo edit silent success. Running the visibility change initially appeared to fail (no output, visibility unchanged in immediate verification). A second run with explicit error capture showed exit 0, and the visibility was actually changed. Likely a small replication delay between the API call and the read-side cache.

What was caught only because Phase 2 exists

Both CLAUDE.md drifts (claude-config, archive-resolver). A diff-only reviewer would have approved the script/workflow changes as internally consistent and let the doc drift ship. Two months from now, future Claude sessions would have followed stale CLAUDE.md rules and gotten confused.

This is the core validation of the local-first thesis: the gap between diff-only review and full-tool-access review is real, measurable, and matters for documentation hygiene as much as for cross-file bugs.


13. Open Follow-Ups

Filed during this session

Not filed but known

  • tensegrity org migration. Will move from smartwatermelon to nightowlstudiollc. After the move, branch protection becomes available (private-with-Pro at the org level vs free private at user level). The CI changes from this session are already in place and will become enforceable on transfer.
  • tensegrity PoC SRI debt. .semgrepignore workaround merged this session. Underlying SRI fix on the Three.js CDN scripts in poc/3d-prototype.html not done. Pure tech debt; only matters if a PoC file is ever promoted to production. Worth a follow-up issue if the PoC code matures.
  • Stale local remotes/origin/... tracking refs in claude-config. Cosmetic only — git fetch --prune would clean them up. Not touched this session because the user's local git state isn't mine to mess with.
  • Sentry app coverage on smartwatermelon (user-level). The audit confirmed Sentry is installed at nightowlstudiollc org level but couldn't enumerate user-level GitHub App installations from the PAT. If Sentry/Seer is installed at the user level too, the same non-blocking treatment applies via the existing pre-merge-review.sh filter (works by check name, not org).
  • networth-agent has no branch protection at all and no Claude review configured. Confirmed acceptable per user — it's a public mirror of the private financial-agent repo, fully covered by review on the private side.

Out of scope

  • Forks: headroom, superpowers, tomatobar. Untouched by policy.
  • Archived repos: excluded from audit by --no-archived.

14. Cross-References

Document Role
docs/WORKFLOW-DEEP-DIVE.md Live architecture reference for every layer (hooks, wrappers, CI/CD) with source attribution. The canonical "how does this work" doc.
docs/local-code-review-options.md 2026-03 research notes on Semgrep, Seer, and adversarial reviewer enhancements that informed Phases 1–3.
docs/plans/2026-03-25-infrastructure-consolidation-design.md The umbrella plan for moving infrastructure into this repo as the source of truth, with install.sh symlinking everything.
docs/plans/2026-04-02-phase2-local-whole-codebase-review.md Phase 2 design doc — explains why whole-codebase review and what it replaces from Seer.
docs/plans/2026-04-02-phase2-implementation.md Task-by-task Phase 2 implementation plan.
~/Developer/claude-config/hooks/pre-merge-review.sh The merge gate. Source-of-truth for SEER EXCEPTION + NEUTRAL filtering.
~/Developer/claude-config/hooks/run-review.sh The local review entry point (--mode=full-diff and --mode=codebase).
~/Developer/claude-config/hooks/lib-review-issues.sh Shared GitHub-issue-creation library for non-blocking findings.
~/Developer/github-workflows/.github/workflows/claude-blocking-review.yml The reusable CI summarizer workflow.
~/Developer/github-workflows/.github/workflows/claude-assistant.yml The reusable CI @claude mention workflow.
~/Developer/github-workflows/claude-review-audit.sh Read-only auditor for Claude review configuration across all owned repos.
~/.config/pre-commit/config.yaml The global pre-commit hook config (black, flake8, shell-lint, yamllint, html-tidy, markdownlint, luacheck, prettier).
~/Developer/dotfiles/git/hooks/pre-commit Global git pre-commit hook that runs the framework + semgrep + review agents.
~/Developer/dotfiles/git/hooks/pre-push Global git pre-push hook that runs full-diff + Phase 2 codebase review.

Appendix A — Audit Methodology (How the inventory was built)

For reproducibility, the audit phase of this session ran the following sequence against all smartwatermelon and nightowlstudiollc repos:

# 1. Enumerate non-fork, non-archived repos
gh repo list smartwatermelon --no-archived --limit 300 --json name,isFork \
  --jq '.[] | select(.isFork==false) | .name'
gh repo list nightowlstudiollc --no-archived --limit 300 --json name,isFork \
  --jq '.[] | select(.isFork==false) | .name'

# 2. For each repo, fetch every workflow file's content
for repo in $REPOS; do
  wfs=$(gh api "repos/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null)
  for wf in $wfs; do
    gh api "repos/$repo/contents/.github/workflows/$wf" --jq '.content' \
      | tr -d '\n' | base64 -d > "wf/${repo//\//_}__${wf}"
  done
done

# 3. Cross-reference each workflow's lint/format jobs against the global
#    pre-commit config and the per-repo .pre-commit-config.yaml
gh api "repos/$repo/contents/.pre-commit-config.yaml" --jq '.content' \
  | tr -d '\n' | base64 -d

# 4. For each repo, check branch protection required status checks
gh api "repos/$repo/branches/main/protection" \
  --jq '[.required_status_checks.contexts[]?, .required_status_checks.checks[]?.context?] | unique | .[]'

# 5. For repos suspected of running Seer, find a recent PR and inspect check-runs
sha=$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha')
gh api "repos/$repo/commits/$sha/check-runs" \
  --jq '.check_runs[] | select(.app.slug=="sentry") | .name'

The classification matrix used to decide what to remove from each repo's CI:

Check Covered by global pre-commit? Covered by per-repo pre-commit? Action
shellcheck ✅ via shell-lint-fix varies Delete from CI
shfmt ✅ via shell-lint-fix varies Delete from CI
markdownlint ✅ varies Delete from CI
yamllint ✅ varies Delete from CI
html-tidy ✅ (only matches .html, not Nu HTML Checker) Delete from CI if covered
black ✅ varies Delete from CI
flake8 ✅ varies Delete from CI
prettier (--write) ✅ for .js/.ts/.jsx/.tsx/.json/.css varies Delete format:check from CI
ESLint ❌ only if explicit local hook Delete from CI only if local hook present
tsc --noEmit ❌ only if explicit local hook Delete from CI only if local hook present
pytest / vitest / jest ❌ ❌ Keep in CI
wrangler dry-run ❌ ❌ Keep in CI
nu-html-validator ❌ (different from tidy) ❌ Keep in CI
lychee (broken links) ❌ (network checks) ❌ Keep in CI
npm audit / pnpm audit ✅ via global semgrep ci --supply-chain ❌ Delete from CI
build (e.g. pnpm run build, astro build, expo export) ❌ ❌ Keep in CI
Anthropic claude-code-action direct call n/a (replaced) n/a Migrate to claude-blocking-review reusable

This matrix is the conceptual core of Phase 3's CI cleanup decisions. Future similar epics in additional repos can use it as a starting point.


Appendix B — Session timeline (this session, 2026-04-09 PDT)

Local time Event
16:41 Session start, environment check, scope clarification
16:43 Audit phase begins — workflow enumeration across 26 repos
16:50 Branch protection audit — confirmed Seer not in any required_status_checks
16:52 Audit complete — 3 repos identified for CI cleanup, scope expanded by user to also cover night-owl-studio + kebab-tax-netlify migration + crazy-larry visibility + Seer wrapper patch
17:13 PR #107 (Seer wrapper) opened
17:15 PR #107 CI: SUCCESS — first reviewer drift catch (CLAUDE.md Protocol 5) handled in second commit
17:24 PR #107 merged
17:30 PR #8 (archive-resolver) opened — second reviewer drift catch (CLAUDE.md CI workflows section) handled in first commit
17:33 PR #8 CI: SUCCESS, merged
17:36 PR #30 (amelia-boone) opened
17:38 PR #30 CI: SUCCESS, merged
17:43 PR #75 (tensegrity) opened — .semgrepignore added as separate commit to unblock the CI cleanup commit
17:46 PR #75 CI: SUCCESS, merged. Issue #74 closed as verified-safe
17:50 PR #22 (night-owl-studio) opened
17:54 PR #22 CI: SUCCESS, merged. Branch protection enabled via gh api PUT
17:58 kebab-tax-netlify required check swapped from claude-review → claude-review / run-review (atomic, pre-PR)
18:00 PR #161 (kebab-tax-netlify) opened — second commit added for CLAUDE.md tree update (Phase 2 catch)
18:05 PR #161 CI: SUCCESS, merged
18:07 crazy-larry visibility: private → public, branch protection enabled
18:10 All tasks complete, summary delivered

Total wall-clock: ~90 minutes for 7 PRs across 7 repos plus the audit. Iteration time was bounded almost entirely by CI runners (each PR's CI took 2–4 minutes), not by local review or human authorization.


15. Phase 4 — ralph-burndown pivot to mimolette (2026-04-22)

Design doc: smartwatermelon/ralph-burndown — docs/plans/2026-04-22-local-nightly-runner-design.md Implementation plan: docs/plans/2026-04-22-local-nightly-runner-impl.md

Goal: close the last CI-first hold-out in the fleet — ralph-burndown's own scheduled automation.

Background. ralph-burndown was built in March before the local-first epic landed, and its install pipeline deployed two scheduled workflows (night-shift.yml, ralph-triage.yml) into every target repo. Each workflow cloned ralph-burndown onto a GitHub Actions ubuntu-latest runner and executed label-audit + night-shift + (monthly) triage there. By 2026-04 this was the only remaining CI-first automation path across the owned fleet. Every minute of Actions billed for scheduled ralph work was already available for free on mimolette.local (24/7, sleep disabled, claude/gh/uv installed, Max-plan OAuth token authenticated).

What shipped

CI teardown PRs — removed night-shift.yml + ralph-triage.yml from every target repo:

# Repo PR
1 nightowlstudiollc/kebab-tax #1189
2 nightowlstudiollc/financial-agent #81
3 nightowlstudiollc/yesteryear #54
4 smartwatermelon/scripts #62
5 nightowlstudiollc/kebab-tax-netlify #177
6 nightowlstudiollc/night-owl-studio #31
7 smartwatermelon/projectinsomnia #56

ralph-burndown pivot PR — smartwatermelon/ralph-burndown#99 (plus follow-up #102 for path + CDPATH fixes):

  • New ralph-burndown nightly click subcommand orchestrating label-audit + night-shift (+ monthly triage on the 1st) per configured repo.
  • New module set: workclone, runner, summary, notify, nightly. TDD throughout; test suite grew from 56 to 95.
  • LaunchAgent us.nightowlstudio.ralph-nightly fires at 02:00 PT on mimolette, writing to /Volumes/extra-vieille/Workspaces/ralph-work/.
  • Summary issues posted on smartwatermelon/ralph-burndown with label nightly-report (GitHub's own notifications email the operator). Failure email via msmtp — Gmail app password sourced from a 1Password Automation vault, mirrored into a dedicated never-auto-lock keychain (~/Library/Keychains/ralph-nightly.keychain-db) because the login keychain locks outside GUI sessions.
  • Deleted: workflow_gen.py, ci_patch.py, permissions.py, scripts/tech-debt-loop.sh, and ralph-burndown's own scheduled workflows. The install command no longer emits CI workflows — it just generates config + prompt files.

Outcome

Every piece of owned-fleet code-quality and ralph-powered work now runs on-laptop (or on mimolette, for scheduled ralph tasks). Scheduled CI minutes billed for ralph-burndown drop to zero. Night-shift commits now flow through the same pre-commit + pre-push review stack as human commits — a strict quality uplift beyond the cost shift.

The Phase 3 claim "local review beats CI review with diff-only access" now applies to the autonomous fix path as well as the human PR path. A night-shift commit on mimolette runs through:

  1. Global pre-commit framework (black, flake8, shell-lint, yamllint, html-tidy, markdownlint, luacheck, prettier, semgrep)
  2. run-review.sh code-reviewer + adversarial-reviewer agents on the staged diff
  3. Pre-push run-review.sh --mode=full-diff + run-review.sh --mode=codebase — the same Phase 2 whole-codebase review that catches cross-file drift

Any CI-only night-shift pipeline would only get review at the remote summarizer layer. Local is strictly better.

Non-blocking follow-ups filed during the rollout

Pre-push codebase review and pre-merge analysis flagged several adjacent cleanup candidates while the teardown PRs ran. They were filed as GitHub issues (and thus will be surfaced to the new nightly runner's first few passes):

Lessons from Phase 4

  • CDPATH contamination is real. The first scripts/install-launchagent.sh run failed because the operator's shell sets CDPATH=.:~/Developer:..., and cd <relative> matching via CDPATH echoes the destination to stdout — contaminating $(cd ... && pwd) substitutions. Fix pattern applied to every new install-time shell script: unset CDPATH near the top.
  • Login keychain is useless for unattended agents. The login keychain locks outside GUI sessions, so the 02:00 LaunchAgent can't back a passwordeval. Solution: a dedicated never-auto-lock keychain (ralph-nightly.keychain-db) populated from 1Password by an idempotent bootstrap script. Source of truth stays in 1Password; the keychain is a mirror.
  • **/Volumes/<external>/ is root-owned by default.** When using an external volume, the mount-point root is oftenroot:wheel 0775. The Workspaces subdirectory had been created with user ownership, but the new ralph-work sibling of the ralph-burndown checkout couldn't be created without sudo. Moved ralph-work intoWorkspaces/` alongside the project checkout.
  • pip-installed black at a different version than the pre-commit black. Running a naked uv run --with black black --check on the tree flagged 3 files that the pre-commit black version didn't touch. Pre-commit's pinned black is the enforcement point; naked-install checks are cosmetic.

Closing note

The local-first epic is now complete across every owned, non-fork, non-archived repo. There is no scheduled GitHub Actions automation running for code quality or autonomous issue fixing anywhere in the fleet. CI's role is reduced to:

  1. Tests that need a clean environment (CI runner exists for this reason and this reason only).
  2. claude-blocking-review.yml summarizer (minutes-long, narrow, backed by the reusable workflow in smartwatermelon/github-workflows).
  3. @claude mention responses.
  4. Dependabot auto-merge.
  5. Deploy / release workflows where applicable.

Everything else happens on a laptop or on mimolette.


This document is the canonical record of the Local-First Code Quality Epic. It complements but does not replace WORKFLOW-DEEP-DIVE.md. For live architecture details, prefer the deep dive. For "what was the journey and why is it the way it is now," prefer this document.