This bundle provides ETHOS-oriented Git hooks through the bundled Go runner. The bundle supports two layouts:
- Source repo:
pre-commit/ - Vendored/submodule repo:
coding-ethos/pre-commit/
The hook runners resolve either layout automatically. Installed Git hooks call the Go runner directly.
Run from the bundle repo root:
make install-hooks
make cutover-install
make cutover-verifyIn a consuming repo, run the same target from coding-ethos/.
When coding-ethos/ is a submodule, the root Makefile resolves the parent
repo automatically and installs hooks into the parent repo's .git/hooks.
Before the hook entrypoints are installed, make install-hooks also generates the
consumer repo's pyrightconfig.json, mypy.ini, ruff.toml, .pylintrc,
.yamllint.yml, .bandit.yml, .sqlfluff, tombi.toml, .golangci.yml, and
.coding-ethos/gemini/prompt-pack.json from the shared bundle inputs plus any
consuming-repo overrides.
make install-hooks installs .git/hooks/pre-commit, pre-push, and
commit-msg as small executable scripts that call
bin/coding-ethos-run git-hook <hook> with the original Git arguments. The
runner repairs missing checkout-local runtime artifacts with make build and
dispatches to the built hook binary under coding-ethos/bin/.
make cutover-install installs the Git hook entrypoints, syncs Claude, Codex, and
Gemini repo-local agent hook settings, and then verifies the full cutover
surface. make cutover-verify checks the installed Git hook entrypoints, runs
agent-hooks verify, verifies required runtime ignores through the compiled
repo.required_ignores policy, runs the policy runtime validation hook,
and prints a concise TOON readiness report. Blocked reports include fix_first
entries that name the stale or missing surface and the next action.
Each top-level hook runner invocation logs stdout, stderr, and run metadata
under .coding-ethos/hook-runs/<run-id>/ in the repo being checked. Keep
generated runtime subpaths such as .coding-ethos/hook-runs/,
.coding-ethos/lint-runs/ and .coding-ethos/cache/
ignored in both the bundle repo and consuming repos; do not ignore the parent
.coding-ethos/ directory, because .coding-ethos/memories/ is repo source.
The cutover gate reports missing or overly broad ignore rules before
installation, and normal hook execution still fails before writing logs when
required runtime paths are not ignored.
Required tools:
go1.26 or neweruv
make build installs required checkout-local managed tools under
build/toolchain/. shfmt and golangci-lint are built into
build/toolchain/go-bin/; ShellCheck, actionlint, hadolint, and dotenv-linter
are installed from pinned GitHub release assets into
build/toolchain/github-bin/.
The compiled coding-ethos-toolchain install-managed-toolchain command owns
manifest parsing, install-skip decisions, Go/Rust/GitHub installer dispatch,
digest verification, archive extraction, and installed-manifest writes.
build/toolchain/manifest.tsv records the installed toolchain. Hook execution
prepends managed tool directories to PATH, and Go hook commands resolve
managed absolute paths; host-global linter installs are not a runtime contract.
Agent shell remediation uses the short cerun --rewrite -- <command> binary.
cerun delegates to coding-ethos-run agent-shell --rewrite -- <command> so
blocked providers can resubmit unmanaged shell commands through the
coding-ethos runtime boundary and command rewrite rules without copying a long
command prefix.
Use cerun --check -- <command> to run the same policy preflight without
executing the command. The runner records preflight and execution attempts in
.coding-ethos/code-intel.duckdb, carries --intent <text> or the
CODING_ETHOS_STRATEGIC_INTENT/Gemini update-topic environment into sandbox
evidence and CEL event facts, emits SARIF agent_remediation for blocked
runner commands, and blocks command argv that appears to contain secrets or
local-machine paths before host execution.
cerun git <args> and cerun python <args> are short forms for
cerun --rewrite -- git <args> and cerun --rewrite -- python <args>;
cerun lint <args> dispatches to the managed policy-lint path. Recursive
runner invocations are rejected at the runtime edge, so the accepted shape is a
single cerun boundary around the real target command.
Claude Bash hooks must not emulate file tools with shell commands. cat,
sed, awk, tee, and echo/printf write redirection are blocked in Bash
events so file reads and edits stay on provider file tools where hooks receive
structured file paths. Claude /permissions allowlists should grant only the
literal runner boundary, for example Bash(cerun -- *) and
Bash(cerun --check -- *); direct Git, absolute Git, shell file tools, nested
runner commands, and inline environment preludes remain policy violations even
when a provider permission prompt would otherwise allow Bash.
Provider-owned memory and permission request systems are integration inputs, not alternate enforcement paths. coding-ethos records blocked command context, SARIF remediation, active TodoWrite context, strategic intent, runtime duration, and code-intelligence events for provider memory/MCP consumers, while the hook and runner boundary remain the fail-closed enforcement floor.
From the bundle repo root:
make doctor
make pre-commit
make pre-commit-all
make pre-push
make validate
make hook-planRun a single group directly:
cd "$(git rev-parse --show-toplevel)"
bin/coding-ethos-run run-group python-static path/to/file.py
bin/coding-ethos-run run-group syntax path/to/config.yamlRun commit-message checks directly:
tmp="$(mktemp)"
printf 'feat(hooks): update bundle\n' > "$tmp"
make commit-msg MSG="$tmp"
rm -f "$tmp"Hook bypass is forbidden. Do not use --no-verify.
Primary files:
../Makefile- root-level hook entry points and Git hook installation../config.yaml- repo-root bundle policy and per-check defaults../pyrightconfig.json,../mypy.ini,../ruff.toml,../.pylintrc,../.yamllint.yml,../.bandit.yml,../.sqlfluff,../tombi.toml,../.golangci.yml- generated consumer-repo tool configs../.coding-ethos/gemini/prompt-pack.json- generated consumer-repo Gemini prompt pack with rendered prompts and per-check runtime metadatahooks/pyproject.toml- Ruff, mypy, pyright, and tool dependency config for the hook project../bin/coding-ethos-run- compiled hook/runtime entrypoint; policy metadata validation, managed GitHub asset installation, cutover reporting, and Git hook entrypoint installation/verification are delegated to compiled Go helpersgo/cmd/coding-ethos-hook-runner/main.go- Go-backed hook commands, including the active Gemini AI review runner
The active Go Gemini runner now executes file batches concurrently, applies
repo-local response caching under .coding-ethos/cache/,
supports per-check model_overrides and service_tier_overrides, reuses
Gemini cachedContents entries when the same batch corpus is reviewed by
multiple prompts, and can run standard, flex, or priority requests from
merged config.yaml plus repo_config.yaml.
The hook runtime is built into the checked-out coding-ethos repository:
bin/contains built hook and policy binaries.build/policy/contains the compiled policy bundle and source-hash metadata.
The old .git/coding-ethos-hooks/ runtime cache is legacy. The current runtime
model is documented in docs/HOOK_RUNTIME_BOOTSTRAP.md: installed consumer
hooks are generated runner entrypoint scripts; all repo-discovery, build-repair,
and dispatch
behavior lives in compiled Go while binaries and compiled runtime files are
built and executed from the checked-out coding-ethos repository.
The same wrapper also exposes local policy-runtime entrypoints:
bin/coding-ethos-run agent-hook
bin/coding-ethos-run agent-hooks print
bin/coding-ethos-run agent-hooks sync
bin/coding-ethos-run agent-hooks doctor
bin/coding-ethos-run agent-hooks verify
bin/coding-ethos-run cutover install
bin/coding-ethos-run cutover verify
bin/coding-ethos-run policy-lint --staged
bin/coding-ethos-run policy-git --check-only commit -m test
bin/coding-ethos-run hook-log-analyze
bin/coding-ethos-run hook-log-summaryagent-hook reads agent hook JSON from stdin and never calls Gemini. Gemini
checks stay in the Git hook stages: changed-file review on pre-commit and
full review on pre-push.
agent-hooks print|sync|doctor|verify always covers every supported agent
surface.
There is no single-agent generation path because partial protection is not a
valid install state. Claude output uses Claude Code's native hooks map.
Claude MCP output writes project .mcp.json with a coding-ethos stdio server.
Codex output enables [features].hooks in .codex/config.toml, writes
managed native [hooks] entries and [mcp_servers.coding-ethos] in that same
TOML file, and removes stale .codex/hooks.json. Gemini output writes native
.gemini/settings.json hooks with hooksConfig.enabled = true and
mcpServers.coding-ethos. doctor verifies those native activation files and
fails when a provider does not point at the expected hook or MCP command.
Codex hook generation uses one native command hook per supported lifecycle
event, while the runtime normalizes aliases such as exec_command,
run_shell_command, shell, write_file, and apply_patch; shell and edit
policy does not depend on a single Codex tool name spelling. Codex settings are
generated without inline PATH= mutation, use explicit shell/edit matchers for
tool hooks, and use one matcher-free command hook for supported lifecycle
events. A nested checkout only enforces the Codex hook whose consumer root is
the nearest repo root, preventing parent and nested repos from both reporting
the same tool event.
verify executes provider-shaped runtime probes through the configured hook
command after doctor succeeds. It proves the settings point at a runnable
policy path; it is not a substitute for a real provider binary executing a live
tool call.
agent-hook accepts each provider's supported event shape and normalizes it
before policy evaluation. Claude may send native hook_event_name,
tool_name, tool_input, and tool_response fields. Codex and Gemini CLI
callers should send the provider-neutral shape:
{
"provider": "gemini-cli",
"event": "PreToolUse",
"tool": "Bash",
"input": {"command": "git status"}
}The decoder also accepts camelCase hook fields (hookEventName, toolName,
toolInput, toolResponse, exitCode), Gemini's BeforeTool,
run_shell_command, and write_file names, Codex shell aliases
(exec_command, run_command, run_shell, run_shell_command, shell,
shell_command), and nested Codex-style tool_call.name plus
tool_call.arguments. Provider identity does not weaken policy: the same git
wrapper, filesystem, Python-edit, continuation, and post-tool output rules
apply wherever the provider exposes the corresponding lifecycle hook.
Tamper and bypass blocks are intentionally louder than normal lint findings.
Direct attempts to inspect, delete, rebuild, replace, chmod, or write managed
hook binaries under coding-ethos/bin/ are treated as employment
violations. Agent-facing output starts with a uniform
CODING-ETHOS EMPLOYMENT VIOLATION warning before the policy-specific message,
states that the actor has done something wrong, and warns that continued
circumvention attempts may result in termination.
Hook responses are provider-aware. Claude keeps the full hookSpecificOutput
contract, including updatedInput for transparent git-wrapper rewrites. Codex
does not currently support updatedInput, so coding-ethos returns native block
output (decision: "block" plus permissionDecision: "deny" for
PreToolUse) when a raw git command must be rerun through the wrapper. Codex
receives compact native additionalContext for supported lifecycle and
post-tool advice, while blocked tool calls use compact reason text suitable
for exit-code-2 stderr. Codex uses compact systemMessage only for supported
events that do not expose additionalContext. Gemini uses native
decision: "deny" and systemMessage for tool blocks, and maps AfterTool to
the same internal PostToolUse feedback path for shell and edit advice.
Agent-facing post-tool context replaces absolute repo, home, and temp paths
with stable tokens, collapses multiline commands, and renders hook output as
TOON line tables instead of escaped newline cells.
Every logged hook run is captured by the managed coding-ethos-hook-log Go
tool. It creates .coding-ethos/hook-runs/<run-id>/metadata.env plus stdout
and stderr logs. Adding --coding-ethos-debug to a hook-runner or Bash tool
command strips that external flag before execution, enables structured debug
logging for the run, and writes debug events to both debug.log and stderr.
Agent-hook runs also write sanitized event.json diagnostics containing
provider, event, tool, cwd, referenced files, command preview and hash, policy
IDs, status, and output shape. The trace is meant for debugging policy routing
and provider adaptation; it intentionally avoids dumping raw tool input.
Post-edit feedback for Write, Edit, and MultiEdit includes focused context,
language-specific advice, compiled lint findings for the edited files, and a
fast Ruff probe for Python files when ruff is available. Expensive external
tool suites still belong to the Git hook/check path.
Fast Ruff findings use the same ETHOS evidence maps as captured lint output,
so known codes carry policy-grounded repair advice in post-edit context.
When captured lint history has relevant failures for the same file area,
post-edit feedback also surfaces recurring checks, recurring tool/code pairs,
and unmapped tool/code pairs that still need ETHOS evidence-map coverage.
hook-log-summary summarizes .coding-ethos/hook-runs/ and hook-log-analyze
ranks failed tools, codes, repeated findings, and output-quality problems such
as raw output, escaped newline cells, or leaked absolute repo paths. The
analyzer scans newest runs first and caps scanned runs plus examples so it stays
interactive on large agent log directories. Both commands honor the same human,
JSON, and TOON output selection as hook execution output.
Compiled lint preflights also persist normalized result traces under
.coding-ethos/lint-runs/. These are intended for offline trend analysis:
which policies fail most often, which linter codes drive the most churn, and
which ETHOS-backed advice should become more specific. Future guidance synthesis
may use a very small LLM or local model over these traces, but hook-time
enforcement remains deterministic and policy-bundle driven.
Analyze those traces with:
bin/coding-ethos-run policy-lint --analyze-log
bin/coding-ethos-run policy-lint --analyze-log --for-files lib/python/app.py
bin/coding-ethos-run policy-lint --analyze-log --json
bin/coding-ethos-run policy-lint --replay .coding-ethos/lint-runs/<trace>.jsonAnalysis output uses the same human, JSON, and auto-selected TOON formats as
other lint output, so agent callers get compact structured summaries by default.
The analyzer reports top failing checks, top tool/code pairs, unmapped
tool/code pairs, repeated file-policy patterns, ETHOS IDs, and deterministic
guidance candidates. The
--for-files filter narrows output to prior findings from the same file or
same high-level file area so post-edit feedback can stay focused.
Replay renders one saved normalized result without invoking the underlying
linter, so malformed or low-quality agent output can be reproduced from the
persisted trace.
Direct agent lint runs are captured too. The agent hook rewrites common forms
for ruff, mypy, pyright, pylint, shellcheck, golangci-lint,
actionlint, yamllint, hadolint, bandit, sqlfluff, tombi, and
dotenv-linter to the managed
policy-tool <tool> wrapper when the provider supports command rewrites. This
covers plain tool names, absolute tool paths, uv run <tool>, and
python -m <tool> for Python-backed tools. The installed hook PATH also
contains managed shims for tools that execute by name. Captured runs preserve
exit codes while forcing machine-readable tool output, parsing diagnostics into
the shared lint schema, enriching known findings with ETHOS evidence-map advice,
writing normalized lint traces, and returning coding-ethos human or TOON output
instead of raw linter output.
Raw Python execution follows the same repo-owned environment rule. The hook
runtime prepends <repo>/.venv/bin after coding-ethos-managed directories so
repo virtualenv tools are found after protected shims. Agent shell commands that
invoke python, python3, or python3.x are rewritten, when possible, to
uv run --project <repo> python ... for uv projects, or to
<repo>/.venv/bin/python ... when only a virtualenv is present. Providers that
cannot accept command rewrites are blocked with a message telling them to use
the documented repo Python command.
Captured tool execution is intentionally controlled by coding-ethos. The target
repo is an untrusted file tree and trace destination, not a source of trusted
tool binaries, tool configuration, PATH, aliases, shell state, or uv
project behavior. Python linters execute from the coding-ethos hook project via
coding-ethos-managed versions and explicit generated config flags
(ruff.toml, mypy.ini, pyrightconfig.json, .pylintrc, .yamllint.yml,
.bandit.yml, .sqlfluff, and tombi.toml). Parent repo config files with
matching names are ignored.
Binary linters such as ShellCheck, actionlint, hadolint, dotenv-linter, and golangci-lint are
installed by coding-ethos init into the managed runtime before they are trusted
capture backends; host binaries are not a policy boundary.
Captured lint runs are treated as structured events. Each trace should preserve
the original argv, rewritten argv, exit code, selected parser, parser outcome,
normalized diagnostics, normalized findings, and a bounded redacted
stdout/stderr excerpt when a tool fails before producing parseable diagnostics.
Tool/config failures are first-class findings: a failed run with no parsed
diagnostics must never render as findings[0].
Troubleshooting and regression coverage should work from the same contract:
- replay a saved
.coding-ethos/lint-runs/*.jsontrace without rerunning the underlying linter - maintain golden TOON output tests for normal findings, clean runs, invalid config, malformed JSON/text output, timeouts, and tool crashes
- fail tests if blocked output contains empty finding tables, unredacted local paths, internal group/duration noise, or generic guidance without an actionable finding
- keep parser failures visible as parser/tool failures instead of degrading to silent success or empty diagnostics
Bundle defaults live in the coding-ethos repo-root config.yaml. Consuming repos
can override them with one of these root-level files:
repo_config.yamlrepo_config.yml
You can also point the bundle at an explicit override file with
CODE_ETHOS_PRECOMMIT_CONFIG.
Use bin/coding-ethos-run policy config-trace --json after
enforcement config edits to validate known top-level sections, compile the
merged policy bundle, and report policy/evidence/dispatch counts.
Legacy override names like coding-ethos.pre-commit.yaml are still accepted, but
repo_config.yaml is the preferred consuming-repo entry point.
Set repo.protected_branch_work.enabled: false in repo_config.yaml when a
consumer intentionally permits direct work on main/master. The switch disables
branch-switch blocking and protected-branch file-write blocking together; it
does not disable history-rewrite prevention. Amend commits, force pushes,
branch-moving resets, git checkout -B, and git branch -f remain blocked;
make a new commit instead.
License enforcement is intentionally not inherited from the bundle defaults.
Consumers opt in with repo.license.spdx_identifier in repo_config.yaml; the
compiler downloads that SPDX license text into the policy bundle, the hook
verifies LICENSE without overwriting it, and source files must carry the
configured SPDX license and copyright headers. The same compiled file-policy
path also enforces configured PII scrub patterns and required runtime ignore
paths such as .coding-ethos/hook-runs/.
Generated config drift is checked with:
make check-tool-configs
make check-gemini-promptsRegenerate derived files with:
make sync-tool-configs
make sync-gemini-promptsThe Go runner invokes Python tools with
uv run --project coding-ethos/pre-commit/hooks or
uv run --project pre-commit/hooks. Ruff, mypy, pyright, pylint, yamllint,
Bandit, SQLFluff, Tombi, and golangci-lint read policy from the generated consumer-repo config files at the
repo root. The hook project hooks/pyproject.toml remains the isolated
toolchain environment. Parent uv workspace membership is optional, not
required.
Known external diagnostics can be enriched through policy.evidence_maps.
Mapped findings keep their raw tool, code, location, severity, and message, then
add policy ID, principle IDs, confidence, meaning, advice, and rerun commands.
Unmapped diagnostics still flow through as ordinary lint findings.
Agent-facing JSON, TOON, SARIF, provider-native block responses, hook traces,
and lint traces derive a compact agent_remediation payload from that
normalized result. The payload names a stable remediation ID, policy, ETHOS
principle, skill, location or failed action, next steps, rerun commands,
skill-loading instruction, and the MCP call an agent should use for deeper
explanation. Agents with the full item can call remediation_explain.
Type-checker evidence maps cover common mypy, Pyright, and Pylint findings for
optional required dependencies, unknown type leakage, missing imports, unstable
interfaces, and import cycles. Those findings point back to ETHOS guidance such
as Protocol-first design, fail-fast required imports, and structural fixes
instead of lazy imports or broad suppressions.
When multiple tools report the same mapped policy at the same location, the
lint result keeps one actionable finding and records the secondary tool/code in
the finding detail. This keeps agent context focused on the repair instead of
repeating equivalent diagnostics.
Important configurable areas:
style.*- shared cross-cutting settings like Python version and line length;style.python_versionalso drivespyupgrade, generated tool configs, and repo-root version consistency checkspython.source_paths,python.test_paths,python.stub_paths,python.extra_paths- shared repository layout inputs for generated tool configspython.direct_imports- public-package import enforcementpython.util_centralization- banned direct utility imports and exemptionspython.sql_centralization- centralized SQL module name and exempt paths; test paths are exempt from SQL centralization enforcementpython.manifest_validation- candidate manifest paths and required sectionspython.plan_completion- plan metadata filename, root markers, and done statespython.pytest_gate- banned markers and pytest commandpython.file_docstrings- minimum sentence count and exempt filenames for file-level module docstringspython.type_check- aggregated Ruff, mypy, pyright, and optional pylint command execution, hook-project execution, config injection, per-checker enablement, and excluded path fragmentspython.docstring_coverage- native Tree-sitter docstring coverage threshold, path selection, exclude regexes, and ignore flagshooks.*- normalized output format, agent environment detection, external tool timeout, severity thresholds, and canonical hook groupstooling.pyright,tooling.mypy,tooling.ruff,tooling.pylint,tooling.yamllint,tooling.bandit,tooling.sqlfluff,tooling.tombi,tooling.golangci_lint- generated repo-root tool config defaults, including the expanded Go security, dependency, module-directive, modern-library, protobuf, test, and whitespace/style linter policygemini.*- AI review enablement, model, concurrency, timeout, repo context, and modal allowlist file patternsgo.*- compiled commitlint and commit attribution policy, text policy, line limits, and quiet-filter rules
Agent-facing hook feedback should render from normalized diagnostics instead of
raw tool output. CODE_ETHOS_HOOK_OUTPUT_FORMAT=human|json|toon|auto controls
structured hook reports; auto selects TOON when common agent caller
environment markers are present and otherwise keeps the human terminal report.
Verbose PostToolUse output is compressed before agent context injection by
preserving command-opening lines, terminal failure lines, and an explicit
omission marker instead of replaying repetitive middle output.
Failed grouped hook runs emit a runner-owned execution summary before captured
tool output, including group status, duration, failed groups, and per-command
timing for in-process group execution.
Go-owned policy checks, Python static checks, Gemini AI checks, docstring
coverage, shellcheck/yamllint, and external analyzer orchestration use this
normalized report path.
Pylint config is generated as .pylintrc, but the Pylint checker is disabled
by default in python.type_check.checkers; re-enable it per repo after the
local .pylintrc policy has been reviewed.
The canonical Go-owned hook groups are format, syntax, python-policy,
python-quality, python-static, docs, security, shell, docker,
workflow, go, and ai; compiled policy preflight owns commit message
behavior. Run make hook-plan to print the active group and command plan.
For this repo, many project-specific checks are disabled by default because the codebase does not have SQL centralization, manifest, plan, or Go worktree requirements. Consuming repos enable and tune those checks in their override config.
Typical consuming-repo overrides include:
style.line_lengthfor line-length policy shared across Ruff and yamllintpython.source_paths,python.test_paths, andpython.stub_pathsfor nested layouts likelib/python/testspython.direct_imports.packagesfor the repo's public package namespython.pytest_gate.test_commandfor nonstandard test roots likelib/python/testspython.file_docstrings.min_sentencesfor stricter module-level docstring requirementspython.docstring_coverage.check_pathsfor nested source treespython.docstring_coverage.ignore_private/ignore_nested_classeswhen a repo wants stricter coveragepython.type_check.excluded_path_fragmentsfor generated or container-specific Python treespython.sql_centralizationandpython.util_centralizationfor repo-specific wrapper modulesgemini.modal_allowlist_filesfor repo-configured file-level modal waivers instead of inline source comments
See ../repo_config.example.yaml for a minimal consumer-repo override file.
Policy lint selection can be inspected without running checks:
bin/coding-ethos-run policy-lint --scope staged --explain
bin/coding-ethos-run policy-lint --scope staged --explain --jsonThe explain output reports the selected policy checks, evaluator names, severity, ETHOS IDs, hook-owned tool selection, and the active evidence maps that turn external linter codes/messages into ETHOS-backed policy advice.
Pre-commit includes:
- formatting and whitespace normalization
- pyupgrade autofix using the configured
style.python_version - syntax validation for YAML, TOML, and JSON
- merge-conflict, shebang, private-key, and large-file checks
- shell linting and shell best-practice enforcement
- direct-import, utility-centralization, SQL-centralization, and type-policy checks
- repo-root Python version consistency checks for
.python-version,pyproject.toml,mypy.ini,pyrightconfig.json, andruff.toml - security, logging, dead-code, complexity, maintainability, and docstring checks
- optional manifest and plan workflow validation
- optional Gemini-powered ETHOS review
- optional Go vet/test/lint stages
Pre-push re-runs the higher-signal checks over the pushed diff, including full Gemini review when enabled.
Most hook runtime and policy enforcement now lives in go/cmd/coding-ethos-hook-runner/. Python
quality checks call Radon and Vulture directly from the Go runner, and shell,
YAML, Docker, workflow, Go, and AI analyzer output is normalized there as well.
Commit-message hooks enforce:
- conventional commit structure with a required scope
- no AI attribution or promotional co-author lines
To update Go helper behavior:
make autofix
make go-tidy
make go-test
make go-lintmake go-fmt formats every Go source file under
go/cmd/coding-ethos-hook-runner/. make go-tools-fmt runs the managed
golangci-lint fmt formatter set for the shared Go module: gci, gofmt,
gofumpt, goimports, and golines. make go-lint-fix runs managed
golangci-lint run --fix; remaining diagnostics are structural lint findings
that must be fixed in code. Override GO=/path/to/go or GOFMT=/path/to/gofmt
when testing with a non-default toolchain.
Hook-runner tests install a test-only go guard that fails if a test reaches
the host Go binary accidentally. Tests that intentionally exercise Go hook
commands must install an explicit fake go binary in their fixture PATH.
Run broad hook-runner tests with GO_TEST_TIMEOUT and, on Linux, an external
process budget such as systemd-run --user --scope -p TasksMax=128.
Use Go for generic file, shell, text, source-aware, and commit-message checks.
New hook behavior should land in focused files under
go/cmd/coding-ethos-hook-runner/, with shared policy facts in
go/internal/celexpr/, parser facts in go/internal/astfacts/ or
go/internal/shellparse/, and tunable policy in coding_ethos.yml or
config.yaml as appropriate. Do not add new policy-specific text scanners
before checking the AST/CEL/SARIF path documented in
docs/AST_CEL_SARIF_ARCHITECTURE.md.
Use Python only when the check genuinely depends on Python package behavior that cannot be represented by the shared Go parser/fact/CEL pipeline. The default direction for hook runtime and generated enforcement behavior is Go.
For hooks that modify files:
- set
stage_fixed: true - keep
pre-commit.fail_on_changes: never - avoid stash-based workflows
- keep output quiet unless the hook fails