# Structural Contract — henxels Adoption (#138) The repo's shape is machine-checkable. `henxels.yaml` (repo root) holds the contract; `henxels sync` renders it into the `AGENTS.md` digest agents read; `henxels check --all --plain` enforces it (exit 0 = green, 1 = red) and runs as a CI lint-job step. ## What the contract governs | Henxel | Scope | Rule | | --- | --- | --- | | Canaries registered in CI | `./src/rune/canary_*.py` | Custom statement `ci_runs_all_canaries` (in `henxels_checks.py`): every canary on disk must appear in `.gitea/workflows/ci.yml` | | No secrets | whole repo | `no_secrets` — no credentials in tracked files | | Plans | `./plans` | markdown only, kebab-case filenames | | Docs | `./docs/*` | `.md`/`.yaml` only, except `docs/v1/**` and `docs/wiki/**` | | File-size debt | `./src/rune` | `max_lines: 1200`, except the documented debt list below | Warnings (non-blocking): near-duplicate file detection (the `shared/` vs `src/rune/` copies and `backups/` snapshots are pre-existing debt the warning correctly keeps visible). ## Debt table Known violations, explicitly excepted in `henxels.yaml` instead of silently ignored. Each entry lists the finding that forced the exception. | File / spot | Rule | Finding | Disposition | | --- | --- | --- | --- | | `src/rune/rune_tui.py` | max_lines 1200 | 4231 lines (drafted exception) | except-listed; split is future work | | `src/rune/rune_operator.py` | max_lines 1200 | 1265 lines (drafted exception) | except-listed; split is future work | | `src/rune/local_tool_runtime.py` | max_lines 1200 | 506 lines — drafted as pre-emptive headroom, not currently over budget | except-listed (as drafted) | | `src/rune/memory_store.py` | max_lines 1200 | 2171 lines (live finding) | except-listed; split is future work | | `src/rune/curator.py` | max_lines 1200 | 1597 lines (live finding) | except-listed; split is future work | | `src/rune/tool_executor.py` | max_lines 1200 | 1523 lines (live finding) | except-listed; split is future work | | `src/rune/warm_router.py` | max_lines 1200 | 1292 lines (live finding) | except-listed; split is future work | | `tests/test_gitea_provision.py` | no_secrets | flagged as hardcoded credential — actually synthetic fixture tokens (`plaintext-transport-test-secret` etc.) whose purpose is asserting tokens never leak | except-listed (false positive) | | `src/rune/canary_native.py` | canary gate | tui_validator's import allowlist drifted from `rune_tui.py` (`from __future__ import annotations`, bare `import rune`) — 2 false-failures; validator fix is issue #139, not here | canary tolerates exactly those 2 messages (prints `⚠ known false-failure, #139`, does not count them); any other TUI error still fails the canary | ## How to add a rule 1. Prefer a built-in: `henxels catalogue` lists every statement (built-ins and this repo's custom ones). 2. Add a henxel to `henxels.yaml`. Custom statements live in `henxels_checks.py` (auto-loaded; a `.henxels/` folder of check modules also works). Custom statements REQUIRE an `in:` scope — without one they never fire. 3. Run `henxels check --all --plain` locally; fix findings cheaply or add an explicit `except:` and a row to the debt table above. 4. `henxels sync` to refresh `AGENTS.md` and commit both together. 5. The contract must be green at birth: never merge a rule that fails on master. ## Why no git hooks Subagents work in throwaway worktrees (`/mnt/workdrive/rune` and siblings) and commit there; a pre-commit hook would either miss those commits or block the agent mid-flight. Enforcement is CI-only: the lint job runs `henxels check --all --plain` on every push/PR, so the gate is the merge, not the commit.