Metadata-Version: 2.4
Name: henxels
Version: 0.5.1
Summary: A repo-level harness for coding agents — file-level structure constraints that keep every agent true to your repo
Author: benquemax
License: MIT
Project-URL: Homepage, https://github.com/benquemax/henxels
Project-URL: Repository, https://github.com/benquemax/henxels
Project-URL: Issues, https://github.com/benquemax/henxels/issues
Keywords: harness,agent-harness,coding-agent,structure,contract,agents,ai,lint,guardrails,pre-commit,validation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Documentation
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Provides-Extra: markdown
Requires-Dist: pymarkdownlnt>=0.9; extra == "markdown"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: pymarkdownlnt>=0.9; extra == "dev"
Dynamic: license-file

<!-- markdownlint-disable -->
```
   ╭───────────────╮
   │  ╷  ╷   ╷  ╷  │   h e n x e l s
   │  ╵‖ ╵   ╵ ‖╵  │   suspenders for your repo
   │   ‖       ‖   │   keep your ADHD agent in henxels
   ╰───────────────╯
```
<!-- markdownlint-enable -->

# henxels

**A repo-level harness for coding agents** — file-level rules that steer agents (and
humans) to keep a repository true to a contract. Each rule is a *henxel* (from Finnish
_henkselit_, "suspenders").

Most agent harnesses wrap the *agent*. henxels is a harness that lives in the *repo*: a
structural contract that holds **every** agent — Claude Code, OpenCode, Aider, Hermes,
Pi, or a human — to the same shape, no matter which one made the change.

henxels is for repos where small, eager, easily-distracted coding agents keep writing
the right thing in the wrong place. It puts the expected structure **in front of the
agent** (in `AGENTS.md` and on demand) and makes breaking it **impossible by accident**:
to disobey a henxel you must change the contract — a conscious, reviewable act.

Under the hood henxels is **a framework + a growing community library of checks**:
the contract just lists which checks apply where; the checks are functions, and you can
add your own in three lines.

---

## The contract reads like a whiteboard

`henxels.yaml` is a list of rules. Each **henxel** is a sentence (which doubles as the
failure message) plus the **statements** that must all pass. Logic lives inside the
statements, so the YAML stays a dumb, readable list.

```yaml
settings:                      # behaviours, not tests
  ask_me_before_staging: true
  confirm_before_push: true
  confirm_before_deleting: { over_lines: 5 }

henxels:
  - henxel: "Docs are kebab-case markdown, each with a title and summary"
    in: ./docs                 # ./docs = this level; ./docs/* = recursive
    allowed_filetypes: .md     # a scalar; lists are OR: [.md, .txt]
    filename_casing: kebab-case
    required_frontmatter: [title, summary]   # a list here is AND: both required

  - henxel: "Project config lives only in pyproject.toml"
    forbidden_files: [setup.py, setup.cfg]   # no `in:` = whole repo

  - henxel: "The test suite passes before every commit"
    run_before_commit: "uv run pytest -q"
```

Browse every statement you can use with **`henxels catalogue`**.

---

## Principles

1. **The contract is the single source of structural truth.** If it isn't in
   `henxels.yaml`, it isn't a rule.
2. **Read it like a document.** A human — or a small model — understands the repo's
   shape without reading a validator.
3. **A henxel is a test that must pass.** Logic hides in the statement functions; the
   YAML just lists them. Failure returns an *instruction*, not a bare boolean.
4. **Steer before you stop.** Every henxel says, in words, what to do instead.
5. **Disobey responsibly.** The only escape hatch is editing the contract.
6. **Awareness beats blocking** — especially for duplication.
7. **Beautiful for humans, silent for machines.** Fancy in a terminal, plain in a pipe.

---

## Quick start

Henxels can be installed by your agent or by you manually.

### Agentic instructions

Paste this to your coding agent:

> Install henxels (`uv tool install henxels`, or `pipx install henxels`, or
> `npm i -g henxels` — it shims to Python; install a prerequisite if one's missing).
> Run `henxels init`, then tailor `henxels.yaml` to this repo's folders (run
> `henxels catalogue` to see the statements), and finish with `henxels sync` and
> `henxels check --all`.

### Manual instructions

```bash
uv tool install henxels   # or: pipx install henxels · uvx henxels · npm i -g henxels
henxels init              # scaffold contract + git hooks + AGENTS.md digest
henxels catalogue         # browse the statements you can use
henxels check --all       # run the contract
```

> If henxels lives only inside a project venv, invoke it as `uv run henxels …` — the git
> hooks resolve it either way.

---

## Custom checks in three lines

Need a check that doesn't exist? Write a statement. Drop it in `henxels_checks.py` at
the repo root (auto-loaded — no config) and use it like any built-in.

```python
from henxels import statement

@statement("max_lines", help="source files stay under a line budget")
def max_lines(param, file, scope):        # asks for `file` → per-file, no loop
    if scope.line_count(file) > param:
        return f"split it — keep under {param} lines"   # fail = the instruction itself
```
```yaml
  - henxel: "No source file exceeds 500 lines"
    in: ./src/*
    max_lines: 500
```

Arguments are injected by name (`param`, `scope`, `file`, `root`, `settings`) — take
only what you need. Return `None`/`True` to pass, or a string instruction to fail.

---

## Tips & tricks

### Explain the *why*, not just the rule

A henxel's sentence says **how** the structure must be; a **`why:`** (aliases `context:` /
`comment:`) says *why it exists and how the thing it governs should be used*. It isn't a
test — it rides into `AGENTS.md`, so the agent reads the **purpose** of your structure,
not just the constraint. This is some of the cheapest, highest-leverage steering you can
give a small model.

```yaml
  - henxel: "_now, _next, _later exist in roadmap"
    in: ./roadmap
    required_subfolders: [_now, _next, _later]
    why: "The roadmap is planned now -> next -> later; route each new item into its bucket."
```

### Make an exception — on purpose

`except:` carves paths out of a rule's scope, so a general rule can have a sanctioned,
reviewable hole:

```yaml
  - henxel: "No secrets in tracked files (the vault is the one allowed home)"
    in: ./*
    except: ["./vault/*"]
    no_secrets: true
```

### Hard-enforce "ask me before staging" in OpenCode

`ask_me_before_staging` is advisory by default (git has no pre-add hook). In OpenCode you
can make it enforced — and the agent installs the guard itself:

```bash
henxels integrate opencode
```

### Budget files in tokens, not just lines

A file too big for the agent's context window is one it can't reason over — so warn in the
unit the agent actually feels:

```yaml
settings:
  warn_about_large_files: { over: 8000 tokens }   # also: 200 lines | 3 kb
```

---

## Example: keeping an LLM wiki from scattering

An **LLM wiki** — a markdown knowledge base an agent reads and writes (the pattern
popularized by Andrej Karpathy) — is a perfect henxels use case. The idea is great, but
small models drift hard: they write against the conventions, and knowledge that belongs
in **one** page ends up **scattered across several near-duplicate files**. henxels gives
the wiki a structure the agent has to follow, and warns it the moment it's about to
fragment a topic.

```yaml
settings:
  confirm_before_deleting: { over_lines: 10 }    # don't lose knowledge to a diff slip
  warn_about_similar_files:                        # the anti-scatter henxel: nudges the
    above: 0.82                                    # agent to UPDATE a page, not clone it
    ignore: ["**/index.md"]

henxels:
  - henxel: "Every wiki page is kebab-case markdown with findable metadata"
    in: ./pages/*
    filename_casing: kebab-case
    allowed_filetypes: [.md]
    required_frontmatter: [title, tags, updated]
  - henxel: "Pages are clean markdown with no dead links"
    in: ./pages/*
    markdown_lint: true
    links_resolve: true          # a custom check (see "Custom checks" above)
  - henxel: "The wiki has a single index"
    in: ./pages
    required_files: index.md
```

Because the contract is mirrored into `AGENTS.md`, the agent reads *"one page per topic,
kebab-case, with these fields"* **before** it writes — and `warn_about_similar_files`
catches it when it's about to create the fifth slightly-different page about the same
thing. Strong guidance is exactly what small LLMs need to stay tidy.

---

## Ouroboric by design

henxels eats its own tail, and it's the better for it. Its own repo is governed by its
own `henxels.yaml`, so every feature is dogfooded on the tool before it ships.
`well_formed_statements` is a check that checks the checks. `markdown_links_absolute`
guards the README that documents henxels. The pre-commit hook runs `henxels check` —
henxels gating henxels. The contract even mirrors itself into `AGENTS.md` to steer the
agent that edits the contract.

The tail-eating *is* the test.

---

## Guards & bless

`settings` can guard hard-to-undo actions. They don't forbid — they make you mean it:

```text
$ git push
✗ Push is guarded — a push is hard to take back
    → henxels bless push   (then push again)
```

`henxels bless push` mints a one-time token bound to the exact commit. The delete guard
covers deleted files **and** net-removed lines (diff-edit mistakes lose rows), released
by `henxels bless delete`.

---

## Contributing — the agentic era

henxels thrives on contributions. **We'd rather get a ready-to-merge PR than an issue.**
If you (or your agent) write a check that's *reusable* — general, not tied to your repo —
contribute it upstream: `henxels contribute`. Quality gates (ruff + the test suite) run
in pre-commit and CI, so a green local run means your PR is merge-ready. See
[`CONTRIBUTING.md`](https://github.com/benquemax/henxels/blob/main/CONTRIBUTING.md).

---

## Commands

| Command | Purpose |
|---------|---------|
| `henxels init` | scaffold contract + hooks + digest |
| `henxels check [--all\|--staged] […]` | run the contract |
| `henxels explain <path>` | what governs this location |
| `henxels catalogue` | browse the statements you can use |
| `henxels create-new-statement <name>` | scaffold a custom statement |
| `henxels contribute [name]` | how to upstream a reusable statement |
| `henxels bless <push\|delete>` | consciously override a guard |
| `henxels integrate <harness>` | install an agent-harness integration (e.g. `opencode`) |
| `henxels sync` / `henxels doctor` | refresh the digest / check the setup |

## License

MIT.
