coding-ethos turns an engineering ethos into runnable repository policy.
It keeps agent instructions, generated documentation, static-analysis config, Git hooks, and agent tool-use guards on the same source contract. The result is defense in depth for human and AI contributors: the repo tells agents what good work looks like, gives developers the same standards in normal tool output, and blocks critical bypasses before bad changes land.
AI coding agents fail most dangerously when advice, tooling, and enforcement
disagree. A Markdown rule says one thing, a linter checks another thing, and a
hook lets a third thing through. coding-ethos closes that gap by compiling the
repo's working agreement into every place contributors interact with the code:
- Agent context:
AGENTS.md,CLAUDE.md,GEMINI.md,ETHOS.md, and deep per-principle reference docs. - Tool configuration: Pyright, mypy, Ruff, yamllint, and golangci-lint config generated from the same policy inputs.
- Git enforcement: repo-local Git hooks backed by Go policy evaluators and deterministic failure output.
- Agent tool-use enforcement: Claude hook settings and runtime policy that can rewrite safe Git commands, block bypass attempts, capture continuation context, and preserve audit logs.
- AI review grounding: Gemini prompt packs generated from ethos, repo overlays, enforcement config, and checked-in prompt templates.
coding-ethos does not rely on one hook or one instruction file. Policy is
layered so failures are caught at multiple points:
- Source policy lives in YAML:
coding_ethos.ymlfor shared principles,repo_ethos.ymlfor repo-local context,config.yamlfor enforcement, and optionalrepo_config.yamlfor consumer overrides. - Generated guidance makes the same contract visible to agents and humans through root agent files and deep reference docs.
- Generated tool config aligns standard linters and type checkers with the policy instead of relying on hand-maintained config drift.
- Git hooks run compiled policy preflight, static checks, AI review checks, and repo-specific gates before commits and pushes.
- Agent hooks protect high-risk tool paths during the work session, before a commit even exists.
- Audit output is written under
.coding-ethos/so noisy or expensive hook runs can be analyzed later without flooding the calling agent.
Unknown linter output still flows through normally. Findings tied to ETHOS principles can receive stronger, policy-grounded advice instead of generic tool messages.
coding_ethos.yml repo_ethos.yml
│ │
├──── merged ethos ──┤
│ │
▼ ▼
AGENTS.md / CLAUDE.md / GEMINI.md / ETHOS.md
.agents/ethos/ deep docs
.agent-context/ prompt addons
config.yaml repo_config.yaml
│ │
├── merged enforcement config
│
├── generated tool configs
├── Gemini prompt pack
├── Go hook policy bundle
├── Git hook runner
└── agent hook runtime
The same inputs drive both guidance and enforcement. If a rule changes, update the source YAML or renderer, regenerate, and review the derived diff.
The project has four related surfaces:
- Agent docs:
coding_ethos.ymlplus optionalrepo_ethos.ymlrenderETHOS.md, root agent docs,.agents/ethos/,.claude/ethos/, and.agent-context/. - Enforcement config:
config.yamlplus optionalrepo_config.yamlrender Pyright, mypy, Ruff, yamllint, and golangci-lint config files. - Gemini hook prompts: ethos YAML, repo overlays, enforcement config, and
pre-commit/prompts/templates render.code-ethos/gemini/prompt-pack.json. - Hook runtime:
pre-commit/, generated configs, and the generated prompt pack run through repo-local Go hook shims and Go-backed policy checks.
Generated Markdown files are derived artifacts. Change the YAML source or renderer first, then regenerate and review the generated diff.
Install dependencies and generated local artifacts:
make installRun the Python test suite:
make testRun the current verification gate:
make checkCheck the local toolchain and resolved hook paths:
make doctorGenerate this repo's checked-in agent files:
make generateGenerate agent files into another repository:
make generate REPO=/path/to/repoPreserve existing root agent files in a target repo:
make generate-merge REPO=/path/to/repoThe package exposes the coding-ethos command. During local development, the
Makefile runs it through uv run python main.py so the repo-local sources are
used.
Generate agent docs:
uv run coding-ethos --repo /path/to/repo --primary coding_ethos.ymlSeed a primary YAML file from Markdown:
uv run coding-ethos \
--primary coding_ethos.yml \
--seed-from-markdown /path/to/ETHOS.mdSync generated tool configs:
uv run coding-ethos --repo /path/to/repo --sync-tool-configsCheck generated tool configs for drift:
uv run coding-ethos --repo /path/to/repo --check-tool-configsSync the Gemini hook prompt pack:
uv run coding-ethos \
--repo /path/to/repo \
--primary coding_ethos.yml \
--sync-gemini-promptsThe Makefile is the preferred operator interface.
make install: sync dev dependencies, generated tool configs, and Gemini prompt pack.make install-runtime: sync runtime dependencies and generated local artifacts.make status: print resolved paths and generation settings.make doctor: check required local tools and important resolved paths.make test: runuv run pytest.make check: run tests plus generated config and prompt-pack drift checks.make validate: validate the bundled Go hook runtime.make go-test: run tests for the Go hook runner.make go-fmt: format all Go hook helper source files.make go-tidy: format Go hook helper sources and rungo mod tidy.make fmt: run repo-owned source formatters currently exposed by Make.make install-hooks: install repo-local Git hook shims.make cutover-install: install Git and agent hooks, then verify readiness.make cutover-verify: verify Git, agent hook, and policy runtime readiness.make pre-commit: run staged-file pre-commit hooks.make pre-commit-all: run pre-commit hooks over all files.make pre-push: run pre-push hooks.make sync-tool-configs: write generated repo-root tool configs.make check-tool-configs: fail if generated repo-root tool configs drift.make sync-gemini-prompts: write.code-ethos/gemini/prompt-pack.json.make check-gemini-prompts: fail if the prompt pack drifts.make seed: seed or refreshPRIMARYfromSEED_FROM.make generate: generate agent-facing files intoREPO.make generate-merge: generate while preserving existing root agent docs.make generate-merge-llm: use an external agent CLI for root-file merges.
Useful overrides:
make generate REPO=/path/to/repo PRIMARY=/path/to/coding_ethos.yml
make generate REPO=/path/to/repo REPO_ETHOS=/path/to/repo_ethos.yml
make sync-tool-configs \
TOOL_CONFIG_REPO=/path/to/repo \
REPO_CONFIG=/path/to/repo_config.yaml
make go-test GO=/path/to/go
make go-fmt GOFMT=/path/to/gofmt
make seed SEED_FROM=/path/to/ETHOS.md PRIMARY=/path/to/coding_ethos.ymlThe primary ethos YAML is the shared source contract. It must use
version: 2, include metadata, and define a non-empty ordered list of
principles. Each principle needs an id, order, title, directive, and at
least one section or inline body.
Supported section kinds are:
overview, guidance, rule, policy, workflow, anti_patterns, correct_way,
rationale, examples, reference, repo_context
The loader validates duplicate ids, duplicate orders, unknown related ids, unsupported agents, malformed sections, and empty required prose.
Primary file aliases are also accepted when --primary is omitted:
coding_ethos.ymlcoding_ethos.yamlcode_ethos.ymlcode_ethos.yaml
The optional repo overlay adds local commands, paths, notes, per-agent notes,
principle overrides, and additional repo-specific principles. By default the CLI
looks for repo_ethos.yml or repo_ethos.yaml inside the target repo.
Overlay capabilities:
repo.name,repo.overview,repo.commands,repo.paths, andrepo.notesagent_notes.codex,agent_notes.claude, andagent_notes.geminiprinciples.overrides.<id>for summary, directive, tags, related ids, quick refs, merge topics, agent hints, prepend text, and append textprinciples.additionalfor new repo-local principles
config.yaml is the bundle-wide enforcement source of truth. A consuming repo
can refine it with repo_config.yaml or repo_config.yml at the repo root, or
by passing --repo-config.
The merged config drives:
- generated Pyright, mypy, Ruff, yamllint, and golangci-lint config files
- hook policy for Python, shell, text, commit-message, and Go checks
- Gemini AI review runtime settings and prompt grounding
- shared style settings such as
style.python_versionandstyle.line_length
Generated agent output in a target repo looks like this:
repo/
├── AGENTS.md
├── CLAUDE.md
├── ETHOS.md
├── GEMINI.md
├── .agent-context/
│ └── prompt-addons/
│ ├── claude.md
│ ├── codex.md
│ └── gemini.md
├── .agents/
│ └── ethos/
│ ├── README.md
│ ├── solid-is-law.md
│ └── ...
└── .claude/
└── ethos/
└── MEMORY.md
Generated enforcement output in a target repo can include:
repo/
├── pyrightconfig.json
├── mypy.ini
├── ruff.toml
├── .yamllint.yml
├── .golangci.yml
└── .code-ethos/
└── gemini/
└── prompt-pack.json
--merge-existing only preserves root agent files:
AGENTS.mdCLAUDE.mdGEMINI.md
ETHOS.md and supporting generated files are replaced with deterministic
output.
Inject merge is the default strategy:
uv run coding-ethos --repo /path/to/repo --merge-existingIt inserts managed import blocks and managed addendum blocks into existing root files. Re-running is idempotent, and locally authored content outside managed blocks is preserved.
LLM merge asks an installed agent CLI to merge existing.md and generated.md
inside an isolated temporary workspace:
uv run coding-ethos \
--repo /path/to/repo \
--merge-existing \
--merge-strategy llm \
--merge-engine gemini \
--merge-bin /path/to/gemini \
--merge-timeout-seconds 300Supported merge engines are codex, gemini, and claude. The selected CLI
must already be installed and authenticated. The merge process must write
merged.md; otherwise the command fails.
The bundled ETHOS enforcement package lives under pre-commit/.
It uses repo-local Git hook shims that call the Go runner under
pre-commit/hooks/go-hooks/.
The Go hook runner owns hook output policy. Reports honor
hooks.output_format (auto, human, json, or toon), with auto
selecting TOON when known agent/LLM environment markers are present. Successful
groups are silent by default through hooks.success_output: silent; set it to
verbose only when operator-facing pass summaries are useful. Enabled hook
groups run in parallel when hooks.parallel_groups: true, with group output
captured and replayed deterministically on failure. Default failure output is
intentionally narrow: show the failing checks and their actionable findings,
not pass tables, internal group names, or timings that do not help fix code.
The agent hook path is local-only: pre-commit/hooks/run-go-hook.sh agent-hook
compiles a policy bundle under .git/coding-ethos-hooks/policy/ and runs the
new Go policy runtime. Gemini review checks remain pre-commit/pre-push checks;
they are not invoked from agent hooks. Agent hook evaluators are runtime-covered,
but Claude hook installation and cutover are still tracked in
HOOK_REPLACEMENT_PLAN.md.
Installed Git hook shims compile the policy bundle and enter
coding-ethos-git-hook, the compiled-policy-owned Git hook runtime. That runtime
runs policy preflight and then executes the bundled hook groups as the active
quality gate. make install-hooks also installs post-commit, post-merge,
and post-checkout shims that delegate to Git LFS when it is available.
The standalone coding-ethos-lint path can now execute compiled smoke/full
policies for generated tool-config freshness and the configured pytest gate, so
those checks are no longer inert policy metadata.
External command failures can carry normalized diagnostics in the lint JSON
result. The initial parser registry covers Ruff, Pyright, mypy, Pylint,
golangci-lint, and generic file:line:column text output.
Known diagnostic codes are enriched from compiled policy.evidence_maps, so
ETHOS-significant findings can carry policy_id, principle_ids, confidence,
meaning, and repair advice while unmapped tool findings still flow through
unchanged.
The bundled Python type-check hook uses this same shared diagnostic package, so
Ruff, Pyright, mypy, and Pylint parsing have one implementation across compiled
lint and hook execution.
Python static-tool defaults now come from the shared Go tool catalog, which
captures command, parser, config flags, repo config, runtime, file-argument
behavior, and enabled-by-default state in one typed definition.
Render or verify repo-local agent hook settings without touching global files:
pre-commit/hooks/run-go-hook.sh agent-hooks print
pre-commit/hooks/run-go-hook.sh agent-hooks sync
pre-commit/hooks/run-go-hook.sh agent-hooks doctor
pre-commit/hooks/run-go-hook.sh agent-hooks verifyAgent hook generation is all-or-nothing by design. sync writes every
supported repo-local agent surface:
.claude/settings.local.json.codex/config.toml.codex/hooks.json.gemini/settings.json
Claude output preserves Claude Code's native hooks map. Codex output enables
[features].codex_hooks and writes native .codex/hooks.json. Gemini output
writes native .gemini/settings.json hooks. Generated settings cover the
events each provider exposes: Claude uses the full runtime set, Codex uses
Codex's PreToolUse, PostToolUse, and SessionStart hook names, and Gemini
maps pre-tool checks to BeforeTool for run_shell_command and write_file.
agent-hooks doctor verifies those native activation files rather than a
coding-ethos-only sidecar. agent-hooks verify runs doctor first, then invokes
the configured hook command with provider-native Claude, Codex, and Gemini
payloads to prove the installed files point at a runnable policy path. The
verification probes cover Claude's transparent git rewrite, Codex's block
response for raw git when rewrite is unavailable, Gemini's deny response for
raw shell git, and Gemini write-tool policy denial.
Use the cutover command when preparing a repo to replace old hook surfaces:
pre-commit/hooks/run-go-hook.sh cutover install
pre-commit/hooks/run-go-hook.sh cutover verifycutover install installs the repo-local Git hook shims, syncs every supported
agent hook surface, and then runs readiness verification. cutover verify
checks installed Git hook shims, runs agent-hooks verify, runs the policy
runtime validation hook, and emits a concise TOON readiness report for Git,
agent hooks, and the policy runtime.
At runtime, agent-hook normalizes Claude native payloads and first-class
Codex/Gemini CLI payloads into one internal policy event. The preferred
provider-neutral payload shape is:
{
"provider": "codex",
"event": "PreToolUse",
"tool": "Bash",
"input": {"command": "git status"}
}Gemini CLI callers may use BeforeTool, run_shell_command, and write_file;
those are normalized to the internal PreToolUse, Bash, and Write policy
surface. Codex-style nested tool_call.name plus tool_call.arguments is also
accepted. Provider identity is recorded for diagnostics, but policy enforcement
is intentionally shared across all supported agents.
Agent shell policy includes a forbidden-string gate for hook-system
reconnaissance: banned strings are rejected when they appear directly in a
command and when they appear in regular files referenced by the command, so
agents cannot hide hook inspection in helper scripts.
Provider output uses the strongest native shape each agent supports:
- Claude receives full
hookSpecificOutput, includingupdatedInputfor transparent git wrapper rewrites. - Codex receives native
decision: "block"pluspermissionDecision: "deny"for blocks, andadditionalContextfor supported context events. Codex does not currently supportupdatedInput, so raw git is denied rather than rewritten there. - Gemini receives native
decision: "deny"/systemMessagefor tool blocks andadditionalContexton supported lifecycle hooks. Gemini does not expose a directPostToolUseequivalent, so post-command hook-output advice remains provider-limited.
Continuation state is stored under
.git/coding-ethos-hooks/continuation/; hook execution never calls Gemini or
another model from the agent-hook path.
For work directly on this coding-ethos repository, an admin may authorize a
specific agent session by placing an approved process PID in
/etc/coding-ethos-admin.pids. In that repo-local, admin-supervised case only,
the git wrapper accepts --admin-approved before the git subcommand, such as
pre-commit/hooks/run-go-hook.sh policy-git --admin-approved commit -m "...".
The flag only changes git.staged_admin_files from block to record; it does not
disable any other policy and it is invalid outside this repository.
Install hooks:
make install-hooksRun hooks:
make pre-commit
make pre-commit-all
make pre-push
make hook-planSee pre-commit/PRE-COMMIT.md and pre-commit/hooks/HOOKS.md.
The CLI stays thin. Behavior belongs in focused modules:
coding_ethos/loaders.pyvalidates and merges ethos YAML.coding_ethos/renderers.pyrenders deterministic Markdown.coding_ethos/merging.pyowns managed-block injection and external merge orchestration.coding_ethos/tool_configs.pyrenders generated repo-root tool configs.coding_ethos/gemini_prompt_pack.pyrenders hook prompt packs from templates.pre-commit/hooks/go-hooks/owns active hook runtime and policy checks.
When flags, output layout, merge behavior, overlay semantics, or enforcement config behavior change, update this README, the relevant example YAML, and the tests in the same change.
Canonical local verification:
make checkBroader verification for hook work:
make validate
make go-test
make go-tools-test
make go-tools-smoke
make pre-commit-allRun make generate after changing coding_ethos.yml, repo_ethos.yml, or
renderer behavior. Run make sync-tool-configs after changing generated
tool-config behavior. Run make sync-gemini-prompts after changing prompt
templates, ethos grounding, or Gemini prompt-pack behavior.