Skip to content

chore: pre-push fmt hook to stop fmt-fail CI recurrence - #503

Merged
briansrls merged 2 commits into
mainfrom
chore/pre-push-fmt-hook
Apr 17, 2026
Merged

briansrls merged 2 commits into
mainfrom
chore/pre-push-fmt-hook

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Summary

Three fmt-fail CI incidents in the last week (#491, #496, earlier). Each costs a CI round-trip and, mid-review, a small doc-or-code rewrite turns into a formatting correction. Fixable with a local pre-push gate.

  • `.githooks/pre-push` runs `cargo fmt --all --check`; fails fast with a clear next step.
  • `scripts/install-hooks.sh` one-shot: `git config core.hooksPath .githooks` + chmod. Contributors opt in explicitly — matches the repo's no-silent-magic convention (no auto-install on clone).
  • CLAUDE.md updated: setup command + fmt check in Key Commands.

Not in scope

  • Making this mandatory. CI still catches fmt failures; the hook is a developer-side convenience. Mandatory enforcement would need a different mechanism (e.g., GitHub action rejecting PRs whose commits have fmt drift).
  • Pre-commit (not pre-push). Pre-commit runs more often and creates friction for WIP commits. Pre-push is the narrow gate.

Test plan

  • `scripts/install-hooks.sh` runs clean on a fresh clone
  • Introduce a deliberate fmt drift → push → hook rejects with the right message
  • Fix fmt → push succeeds

🤖 Generated with Claude Code

Three fmt-fail CI incidents in the last week motivated a local gate.
Catching fmt drift at push time saves a CI round-trip and avoids
mid-review doc rewrites blocking on a one-char formatting diff.

- .githooks/pre-push runs 'cargo fmt --all --check'; fails fast
  with a clear instruction to run 'cargo fmt --all'.
- scripts/install-hooks.sh one-shot: 'git config core.hooksPath
  .githooks' + chmod. Contributors opt in explicitly — matches the
  repo's no-silent-magic convention.
- CLAUDE.md updated with the setup command and a fmt check line
  under Key Commands.
@briansrls

briansrls commented Apr 17, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

Principle audit.

FAIL-CLOSED. Satisfied. This is the only principle the diff really exercises, and it does the right thing: .githooks/pre-push blocks the push on cargo fmt --all --check failure and prints a clear remediation path instead of silently skipping the check, while scripts/install-hooks.sh uses set -e so installation errors do not get masked. This is behavioral shell-level enforcement rather than substrate-level enforcement, but for repo-local tooling that is the right shape. chatgpt-review-ed028e0f-8ddc-48…

ILLEGAL STATES UNREPRESENTABLE. Satisfied for scope. The PR does not introduce or widen any compiler data model, so there is no new ambiguous None, no product with mutually-exclusive fields, and no new runtime-checked invariant pretending to be a type. This is implementation glue, not substrate work. chatgpt-review-ed028e0f-8ddc-48…

FACTS FLOW FORWARD. Satisfied. No parse/lower/infer/emit boundary is touched here. The only new fact is “this repo wants a local fmt gate,” and it is carried consistently through the installer, the hook, and the contributor doc update rather than being computed in one place and dropped in another. chatgpt-review-ed028e0f-8ddc-48…

COPROD DISSOLUTION. Satisfied / not engaged. No new Rust enum or substrate coproduct appears in the diff, so the dissolution test does not really come into play. This is exactly the kind of implementation-local change the coproduct rule is not meant to over-police. chatgpt-review-ed028e0f-8ddc-48…

SINGLE AUTHORITY. Basically satisfied. core.hooksPath = .githooks is the single switch, and .githooks/pre-push is the single executable implementation of the new behavior. The only thing I’d keep an eye on is that cargo fmt --all --check now appears in more than one consumer (.githooks/pre-push and CLAUDE.md), which is fine at this size but is where drift would start if more local gates accumulate. The thesis’s sustainability test is still the right lens here. chatgpt-review-ed028e0f-8ddc-48…

chatgpt-review-d0ca1204-da31-40…

API-LEVEL ENFORCEMENT. Satisfied for the chosen scope. This does not make formatting impossible to forget globally, because hook installation is still opt-in, but once a clone enables repo hooks the enforcement becomes automatic at push time rather than “please remember to run fmt.” For workflow tooling, that is forward progress, not debt. chatgpt-review-ed028e0f-8ddc-48…

Design question. Should repo-local quality gates eventually come from one canonical entrypoint instead of re-embedding cargo fmt --all --check in each consumer?

What is at stake is the thesis’s cost-of-change test: today this is small and harmless, but if fmt/lint/test/ratchet gates each get mirrored across hooks, docs, and CI by hand, the repo will slowly accumulate parallel representations of the same process rule. I would not block this PR on that; I just think this is the structural direction to watch. chatgpt-review-d0ca1204-da31-40…

Verdict. APPROVE.

This looks clean. It is a small implementation-local ergonomics change that moves a repeated CI-only failure earlier into the local workflow without introducing a new substrate shape, scaffold, or bridge.

LOOP HEALTH: converging — this round turns a recurring fmt-only CI failure into an earlier local gate and does so without shifting debt elsewhere.


View conversation

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

codex · gpt-5.4 · bad97acb

✅ Review (blocking: 0, non-blocking: 1+/0-)

Non-blocking — Strengths

  • .githooks/pre-push The hook stays explicitly opt-in and fails closed with a concrete remediation, which fits the repo’s preference for loud, documented workflow checks rather than letting formatting drift surface only in CI.

ROADMAP — Incomplete

  • M5 Meta-process modeling: ROADMAP.md still treats full dev-process modeling as future work, so this lands as a bootstrap shim rather than the final dag run-centered end state.

✅ I don’t see any blocking issues in the changed lines; the change is small, explicit, and consistent with the project’s current bootstrap stage.

@briansrls

Copy link
Copy Markdown
Contributor Author

codex · gpt-5.4 · bad97acb

✅ Review (blocking: 0, non-blocking: 1+/0-)

Non-blocking — Strengths

  • .githooks/pre-push The hook stays explicitly opt-in and fails closed with a concrete remediation, which fits the repo’s preference for loud, documented workflow checks rather than letting formatting drift surface only in CI.

ROADMAP — Incomplete

  • M5 Meta-process modeling: ROADMAP.md still treats full dev-process modeling as future work, so this lands as a bootstrap shim rather than the final dag run-centered end state.

✅ I don’t see any blocking issues in the changed lines; the change is small, explicit, and consistent with the project’s current bootstrap stage.

Changed from 'fail, tell developer to run cargo fmt' to 'run cargo
fmt and commit as chore' based on review feedback. Rationale:
automation is the point of the hook; failing and asking the developer
to do two more steps (fmt + re-push) defeats it.

Behavior:
- Clean tree required at push time — bails if uncommitted changes
  exist (don't silently sweep unfinished work into a fmt commit).
- If fmt --check passes: exit 0, push continues.
- If drift: run cargo fmt --all, stage tracked files, commit as
  'chore: apply cargo fmt'. Push continues with the new commit.

The chore commit lands cleanly on top of developer intent — original
commits unchanged. Audit trail preserved.
@briansrls
briansrls merged commit b325a10 into main Apr 17, 2026
3 checks passed
briansrls added a commit that referenced this pull request Apr 17, 2026
Hand-written shell scripts (.githooks/pre-push from PRs #503/#509,
scripts/install-hooks.sh, scripts/check-stage0-freshness.sh,
scripts/regenerate-stage0.sh) bypass the compiler's dependency-
analysis machinery. Per the compiler-as-dependency-analyzer thesis
(tonight's framing), they should be .dag programs composing existing
service operations from extdeps/ and emitted as standalone shell
scripts at build time.

Recon done on existing substrate:

- dsl/extdeps/git.dag: service git.Core with 7 operations + shell
  transport + mock_response pattern. 181 lines.
- dsl/extdeps/cargo.dag: Build, Test, Clippy, Doc, Run. MISSING Fmt.
- dsl/extdeps/shell.dag: POSIX Find, Env. Adequate.
- dsl/extdeps/github/: auth, pulls, gists, actions (not on critical
  path for pre-push hook).

Separable prerequisite deferrals named:

- cargo.dag Fmt operations (S) — mechanical extension
- Shell-emission target (M, needs design) — v3 emits Rust/Go/Python
  today; shell is used as transport but not as emission target.
  Needs a DB clarifying what "emit to shell" means structurally
  (likely E-9-pattern with output as standalone executable text).
- "Hook-as-program" pattern (M, needs design) — how a .dag program
  declares its invocation contract (stdin format, env, exit codes).
- Test coverage via DB-15 R2's MockBackedInvariant predicate.

Yellow-flag threshold: triggers actively when a *second* hand-
written workflow script needs the same modeling. Until then, the
hand-written pre-push hook (PRs #503 + #509) is tolerated as the
one instance.

Added under Active Deferrals §"Cross-cutting — workflow scripts
modeled in .dag". When PR #507 (Scheduled Deletions) merges, the
hand-written .githooks/pre-push also gets a row there with trigger
"emitted pre-push hook replaces it."
briansrls added a commit that referenced this pull request Apr 17, 2026
… as first use case) (#510)

* docs: ROADMAP — track the commit-pipeline-in-dag modeling work

Hand-written shell scripts (.githooks/pre-push from PRs #503/#509,
scripts/install-hooks.sh, scripts/check-stage0-freshness.sh,
scripts/regenerate-stage0.sh) bypass the compiler's dependency-
analysis machinery. Per the compiler-as-dependency-analyzer thesis
(tonight's framing), they should be .dag programs composing existing
service operations from extdeps/ and emitted as standalone shell
scripts at build time.

Recon done on existing substrate:

- dsl/extdeps/git.dag: service git.Core with 7 operations + shell
  transport + mock_response pattern. 181 lines.
- dsl/extdeps/cargo.dag: Build, Test, Clippy, Doc, Run. MISSING Fmt.
- dsl/extdeps/shell.dag: POSIX Find, Env. Adequate.
- dsl/extdeps/github/: auth, pulls, gists, actions (not on critical
  path for pre-push hook).

Separable prerequisite deferrals named:

- cargo.dag Fmt operations (S) — mechanical extension
- Shell-emission target (M, needs design) — v3 emits Rust/Go/Python
  today; shell is used as transport but not as emission target.
  Needs a DB clarifying what "emit to shell" means structurally
  (likely E-9-pattern with output as standalone executable text).
- "Hook-as-program" pattern (M, needs design) — how a .dag program
  declares its invocation contract (stdin format, env, exit codes).
- Test coverage via DB-15 R2's MockBackedInvariant predicate.

Yellow-flag threshold: triggers actively when a *second* hand-
written workflow script needs the same modeling. Until then, the
hand-written pre-push hook (PRs #503 + #509) is tolerated as the
one instance.

Added under Active Deferrals §"Cross-cutting — workflow scripts
modeled in .dag". When PR #507 (Scheduled Deletions) merges, the
hand-written .githooks/pre-push also gets a row there with trigger
"emitted pre-push hook replaces it."

* docs: commit-pipeline deferral — address codex BLOCKING + chatgpt
   non-blockings

Three reviewer concerns addressed:

1. BLOCKING (codex): yellow-flag threshold understated. Four hand-
   written scripts already exist, so the deferral is active now — not
   'tolerated until a second instance.' Reframed: marked ACTIVE;
   THESIS.md's meta-process claim is the justifying authority, not
   an arbitrary 'second script' trigger.

2. NON-BLOCKING (codex): 'First concrete use case' over-described
   the pre-push hook. At PR #509 HEAD the hook fmt-checks / fmt-fixes
   / commits; the stdin/delete/HEAD contract is implementation detail
   that belongs in the .dag design work, not the ROADMAP entry.
   Trimmed.

3. NON-BLOCKING (chatgpt facts-flow-forward): Track 15 (CLI tool
   modeling — bare command names are hidden PATH dependencies) wasn't
   threaded into the prerequisite list. Added as a prerequisite
   deferral referencing ROADMAP.md:810-826 directly.

4. SINGLE-AUTHORITY pressure (chatgpt): Shape A vs Shape B was an
   implicit lean toward Shape A ('shell-emission target'). Per
   Track 16 (ROADMAP.md:920-935), the thesis puts shell scripts as
   Shape B — .dag programs build them via concat/fold/match, parallel
   to Track 16's YAML emission. Updated entry to make Shape B
   explicit, remove the 'shell-emission target' framing, and align
   with tools/ratchet.dag's grep-command generation as precedent.

Prerequisite list rewritten:
- cargo.dag Fmt operations (S, mechanical)
- Track 15 tool resolution (already-tracked prerequisite)
- Shape B emission via existing Track 16 pattern (no new compiler
  concept)
- Hook invocation contract as structural declaration (small type in
  a .dag program)
- Test coverage via DB-15 R2 MockBackedInvariant

Scope for the other three scripts (install-hooks.sh,
check-stage0-freshness.sh, regenerate-stage0.sh) noted: same Shape
B pattern, dissolves individually once pre-push proves it.
@briansrls
briansrls deleted the chore/pre-push-fmt-hook branch June 1, 2026 18:41
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.

1 participant