Skip to the content.

Hook Runtime Bootstrap Model

Decision

The consumer repository hook shim must not own policy behavior.

Its job is limited to:

Policy evaluation, policy freshness, generated prompt packs, runtime command selection, managed capture, diagnostics parsing, and hook behavior belong to the coding-ethos checkout. The shim is only a bootstrap and dispatch layer.

Rationale

The coding-ethos checkout is already required for normal operation. Keeping a second runtime cache under the consumer repository .git directory creates two possible sources of truth:

That split is fragile. Worktrees, submodules, generated policy files, touched configuration, and branch switches can cause the hook shim, make build, and runtime validation to resolve different roots. When that happens, lifecycle hooks can fail even though the correct repair command has been run elsewhere.

The simpler invariant is:

make in the coding-ethos checkout builds the hook runtime.
hooks run the hook runtime from the coding-ethos checkout.

If the coding-ethos checkout is present and can build, the hook path should self-heal missing runtime artifacts. If the checkout is missing or cannot build, the error should name the exact checkout path and command required to fix it.

Target Runtime Layout

Runtime artifacts live under the coding-ethos checkout and are ignored by Git:

coding-ethos/
  bin/
    coding-ethos-git-hook
    coding-ethos-policy
    coding-ethos-lint
    coding-ethos-agent-hooks
  build/
    policy/
      policy-bundle.json
      policy-metadata.json
    gemini/
      prompt-pack.json
    toolchain/
      manifest.tsv
      go-bin/{golangci-lint,shfmt}
      github-bin/{actionlint,dotenv-linter,hadolint,shellcheck}
      prefix/bin/

Consumer repository hooks should not install or validate policy bundles under the consumer .git directory.

Managed Toolchain

Hook execution must not depend on host linters or formatters being installed on PATH. The same checkout-local runtime model applies to third-party tools:

make build is responsible for ensuring required managed tools exist before hook execution. pre-commit/hooks/managed-toolchain.tsv is the checked-in source manifest for required tool versions, release assets, and SHA-256 digests. make managed-toolchain-install installs those tools and writes build/toolchain/manifest.tsv with the installed paths.

coding-ethos-run prepends the managed tool directories to PATH before dispatching to the Go hook runtime. The Go hook runtime also resolves binary tool commands to checkout-local managed paths when possible, so shfmt, shellcheck, actionlint, hadolint, dotenv-linter, and golangci-lint do not silently fall back to host binaries. Missing required managed tools or a missing installed manifest are runtime artifact failures and should self-repair through the same bootstrap path as missing policy binaries.

The managed toolchain has two installer surfaces:

Direct host installs such as go install ... into $HOME/go/bin are not a runtime contract. They may unblock a local shell, but hooks must only rely on artifacts under the coding-ethos checkout.

Build Versus Test Boundary

make build is the explicit environment mutation target. It may regenerate configs, install managed tools, refresh provider settings, install hook entrypoints, compile policy bundles, compile Go runtime binaries, and sync parent hook runtime artifacts.

Test and diagnostic targets must not do those things implicitly. They consume the artifacts produced by make build and fail fast when required artifacts are missing. This prevents ordinary verification commands from rewriting a parent worktree, reinstalling hooks, changing generated config, or performing hidden build setup.

Go tests use the normal Go workflow. make go-test runs go test through managed capture, and make go-e2e-test runs the e2e package with go test. That preserves normalized diagnostics, CEL promotion, trace retention, and SARIF-compatible output without introducing a separate compile-and-run test path.

Hook Entrypoint Contract

The installed consumer repository hook entrypoint should be a small executable script generated from the compiled bin/coding-ethos-run binary. The script passes the hook kind and hook name explicitly, for example coding-ethos-run git-hook pre-commit "$@", so installed Git hooks do not rely on argv[0] inference.

The compiled runner owns the bootstrap contract:

  1. Resolve the consumer repository root from Git.
  2. Locate coding-ethos, preferably at $consumer_root/coding-ethos.
  3. Fail with a clear submodule checkout instruction if it is missing.
  4. Check for required artifacts in the coding-ethos checkout.
  5. If artifacts are missing, run:

    make -C "$coding_ethos_root" build
    
  6. Re-check the required artifacts.
  7. Exec the built hook binary from the coding-ethos checkout.

The hook entrypoint contract must not:

Repair Rules

Bootstrap repair should run only when required artifacts are missing or invalid enough that they cannot be executed. It should not run because a timestamp looks old.

Examples that should trigger repair:

Examples that should not block lifecycle hooks:

Strict freshness validation belongs in explicit maintainer/CI commands such as make validate, make cutover-verify, and CI. Freshness is based on the source hashes recorded in build/policy/policy-metadata.json, not mtimes.

Safety Requirements

Bootstrap needs a few guardrails:

Hook Execution Model

Hook execution follows a three-phase model designed for maximum parallelism while preserving correctness ordering.

Phase 1 — Format (Sequential Gate, Per-Language Parallel)

Formatters mutate files and must complete before linters run. Within the format phase, per-language formatter chains run concurrently:

The two lanes operate on disjoint file sets and run in parallel goroutines. A cross-language text fixer (fixText) runs first and gates both lanes.

If any formatter fails or the format phase produces a non-zero exit, the hook stops immediately.

Phase 2 — Analysis Groups (Fully Parallel)

All non-AI analysis groups run concurrently as independent goroutines:

Within a group, commands run sequentially by default. Groups may declare a ParallelAfter index to split their command list into:

For example, the go group uses ParallelAfter: 2:

Index Command Phase
0 go-format Sequential
1 go-vet Sequential
2 go-test Parallel
3 go-coverage Parallel
4 golangci-lint Parallel

If any sequential prefix command fails, the parallel suffix is skipped for that group.

Phase 3 — AI (Gated)

AI review groups (e.g., gemini-check) run only after all Phase 2 groups succeed. This avoids wasting API credits on code that has already failed deterministic quality gates.

Incremental Linting

During pre-commit, golangci-lint receives --new-from-rev=HEAD so it only reports issues in changed code. During pre-push, it runs on all files for complete coverage.

Migration Direction

Runtime artifacts are built and executed from the checked-out coding-ethos repository. New hook behavior must use that single source of truth instead of adding cache-local compatibility paths.