1 added, 1 removed. Audit A to A.
# CLAUDE.md — house rules for this repository
Conventions for any agent working in `task-pipeline`. This is the file the
pipeline's own stage-0 harvest reads first, so keep it true.
## What this repo is
A **skill**, not an application. Almost every file is prose an agent will read and
act on. There is no build step and no runtime service. The executable code is two
installers, the structural validator, and — since v1.69.0 — **`scripts/graph.py`**,
which is the first thing here that ships *inside* the skill bundle and therefore
travels to every channel rather than only to Claude Code.
It exists for one reason worth stating here: the work graph is walked by a script so
the **model never reads it**. Measured on this build — a 400-node graph (51 KB) and a
4-node graph produce the same 27-byte frontier. Context cost is flat in graph size,
and that is the property the whole role-agent design rests on.
Human-facing entry points: [`README.md`](README.md) (what it is),
[`CONTRIBUTING.md`](CONTRIBUTING.md) (how to change it),
[`CHANGELOG.md`](CHANGELOG.md) (why each release happened).
## Commands
| Job | Command |
|---|---|
- | Test | `npm test` (= `python3 test/validate.py && python3 test/graph_test.py`) — the validator must print `PASS: task-pipeline structure valid` and the graph suite `PASS: graph.py — N cases`. The gloss said only `validate.py` until 2026-08-20, dropping 129 cases from what the command was documented to run; `test/validate.py` now compares every documented `npm` equation against `package.json` |
+ | Test | `npm test` (= `python3 test/validate.py && python3 test/graph_test.py && python3 test/project_audit_test.py`) — the validator must print `PASS: task-pipeline structure valid` and the graph suite `PASS: graph.py — N cases`; `project_audit_test.py` exercises the shipped audit collector against planted defects. The gloss said only `validate.py` until 2026-08-20, dropping 129 cases from what the command was documented to run; `test/validate.py` now compares every documented `npm` equation against `package.json` |
| Prove the guards | `npm run test:negatives` — feeds every guard a planted defect and requires it to reject one. `npm run test:all` is the full suite: `validate.py`, `graph_test.py`, `negatives.py`, then `test:exposure`, `test:probe`, `test:hooks`, `test:artifacts` and `test:docs` — eight scripts, ~20 minutes here, and the list lives in `package.json`, not in this cell. Corrupt files in **python, never `sed -i`** (not portable; the validator rejects it) |
| Lint | none separate; the validator is the lint |
| Install locally | `./install.sh` or `node bin/task-pipeline.js` — **both refuse when the Claude Code plugin is installed**, because a plain `~/.claude/skills/` copy shadows it; `--force` overrides deliberately |
| Behavioural evals | `python3 evals/run.py` — validates the suite and prints the protocol; it never reports a pass it did not observe |
| Deploy / release | push a `vX.Y.Z` tag → `.github/workflows/release.yml` (armed by the repo variable `RELEASE_ENABLED`) |
| Post-deploy check | the release workflow's own `npx` smoke test; then `npm view task-pipeline-skill version` |
`npm publish` runs in the release workflow's second job, armed per repository by the
variable `PUBLISH_NPMJS` (auth: the `NPM_TOKEN` secret — a **granular automation**
token — or npm trusted publishing via OIDC). Unarmed, it falls back to a human 2FA
step.
## Coordination — how this project is wired
The generated snapshot is [`docs/AGENT_SYNC.md`](docs/AGENT_SYNC.md): the record plane, the
lease semantics, the guarded files and what the coordination layer does **not** guarantee.
It is written by `agent_sync.py setup` from the live configuration — if the two disagree,
the tool is right and the file is stale, so regenerate rather than edit.
## Allocating an id, and a version, without a reservation
`.claude/agent-sync.json` **declares no id registers, and that is the decision.** It
declared three — `DEC`, `OQ`, `B` — over the `fs` backend, whose `reserve` refuses by
design: *"backend 'fs' cannot reserve ids safely (atomicAppend is false) … Pretending would
hand two agents the same id."* Only the `outline` backend can allocate, and standing one up
is an infrastructure decision nobody has taken.
A declaration that cannot be served is worse than none: it reads as a capability, so nobody
writes the procedure it hides, and `agent_sync.py check` correctly called it a problem for
as long as it stood. It was removed on 2026-08-16 — **restore the registers only together
with a backend that can allocate.** Until then allocation is manual, and manual is fine if
it follows the lease.
So allocation here is manual, and it is race-free on **this machine** if it follows the
lease. On 2026-08-15 it did not, and two sessions filed a different `B-073` on the same
afternoon; one had to be renumbered afterwards, which also rewrote the other session's
reference to their own row.
**The procedure, in order:**
1. **Take the lease on the register's file first.** All three are in `guardedFiles`, so the
guard already refuses the edit without one. The lease is exclusive on this machine and
advisory across machines — that is the whole guarantee, and it is enough here.
2. **Compute the next id from the committed file, never from your working copy.**
`git show HEAD:docs/evidence/backlog.md | grep -o 'B-[0-9]\{3\}' | sort -u | tail -1`.
A working copy holds your own unpushed row and hides somebody else's.
3. **Write the row and commit before releasing the lease.** An id held only in an
uncommitted file is an id nobody else can see you took.
**A version number is the same problem and has no register at all.** Before choosing one,
read the remote rather than incrementing the local:
```bash
git ls-remote --tags origin | grep -o 'v[0-9.]*$' | sort -V | tail -3
```
Two branches both claimed `v1.56.0` on 2026-08-15 because both incremented from what their
own checkout knew. The tag list is the only place the answer actually lives.
## Branch and commit policy
- Small doc/doctrine fixes land on `main`; anything that changes the stage list,
the gates or a public contract goes on a branch and through a PR.
- **Conventional commits.** Append the version when the change ships one:
`feat: <what changed>; v0.19.0`.
- Never force-push `main`. Never push a tag before `npm test` is green on the
commit the tag points at.
## Invariants that must not drift
Full list with reasoning in [`CONTRIBUTING.md`](CONTRIBUTING.md) → *The
invariants*. (No count here on purpose: a hand-written one drifts, and this one
already had — it said sixteen while the list held twenty-four.) The two that bite
most often:
1. **Version sync — every surface below, no number in the name.** `package.json`,
`.claude-plugin/marketplace.json` (`plugins[0].version`),
`plugins/task-pipeline/.claude-plugin/plugin.json`, the top `## vX.Y.Z` heading in
`CHANGELOG.md`, **and the `Version` row of `SKILL-CARD.md`**. This said *four-way*
and listed four until 2026-08-08 while `test/validate.py` enforced five; the miss
surfaced on a release bump, from the validator rather than from a reader. The list
is the count — same reason invariant counts are not written down here.
2. **The stage list lives on every surface below, and the list is the count.** Three
are compared mechanically — `SKILL.md`'s table, `references/stages.md` and
`pipeline.example.json` (ids, names **and gate types**, plus each stage's own
doctrine file). The rest enumerate the flow for a human and must name the final
stage, last: `package.json`, `marketplace.json`, `plugin.json`, `SKILL.md`'s
frontmatter description, the command, the Cursor rule and the README. Change one →
walk all of them. (It said *nine surfaces* above a list of ten until 2026-08-08. A
count of an enumeration inside one sentence cannot be computed from outside it, so
unlike the numbers in the claim registry this one is **deleted**, not gated.)
Also: no hardcoded vendor model ids anywhere in the shipped skill (name the tier);
every `references/*.md` must be reachable from `SKILL.md`; the Cursor rule stays
self-contained with no relative links; every new validator guard needs a matching
negative self-test in `.github/workflows/validate.yml`.
3. **`npm test` reads git, and prints two disclosures.** Its verdict is followed by the
claim-registry states, `learned.md`'s shape (rules · rules with an incident ·
incident words · binding rows), and `unlooked: N` — what this run could not look at,
listed. None of the three is a ratchet: no floor, no direction, **never a target**.
One check compares `learned.md`'s high-water mark against **every value that file's
history has held** (`git log -p`, last 80 commits) — comparing against `HEAD` was the
first draft and never fired on a committed checkout, which is what CI runs. Outside a
checkout it prints the skip rather than going quiet.
4. **Guard corpora are discovered, not listed.** Three hand-written lists each missed a
shipped surface and none of the misses was found by the guard holding the list. A new
surface joins a check by existing — see `_discover_md` in `test/validate.py`. If you
add a corpus, give it a predicate, and give every exclusion a reason in the code.
5. **`npm test` now runs bash.** One guard **executes** `templates/docgate.sh` over
a scratch project seeded from the templates and requires exit `0` — a scaffold
whose own gate rejects its own seeds teaches every new project that the gate is
noise. Touching any of `templates/{docmap,decisions,open-questions,retro}.md` or
`docgate.sh` means running `npm test`, because the four are one contract.
## Docs to update in the same change
- `CHANGELOG.md` — a section per version, written as *what changed and why it
mattered*, never a diff summary.
- `README.md` — when a user-visible capability, install path or stage changes.
- `references/artifacts.md` — when the repo layout changes.
- `docs/evidence/retro.md` — when a run of the pipeline **on this repo**
diverged: stamp the run first (its commit makes the cold-retirement trigger
computable), then prune the standing instructions (retirement triggers, cap of ten,
every deletion logged), then write the entry
([`references/retrospective.md`](plugins/task-pipeline/skills/task-pipeline/references/retrospective.md)).
- `docs/evidence/` holds this repo's **historical** design records (v0.1.0).
They carry a "superseded" banner; do not update them to the current shape and do
not treat them as the source of truth.
## Knowledge graph
Optional here, recommended by the skill itself
([`references/knowledge-graph.md`](plugins/task-pipeline/skills/task-pipeline/references/knowledge-graph.md)).
If `graphify-out/` exists in this checkout, refresh it in the same change as the
docs (`/graphify . --update`) and check it against them — a hub no doc names, a doc
naming a file the graph no longer has. It is git-ignored and derived: never edit it
by hand. Not installed →
`uv tool install graphifyy` → `graphify install` → `/graphify .`.
## Knowledge wiki
This project's distilled knowledge lives in the operator's local Obsidian vault
under `projects/task-pipeline/`, managed with
[obsidian-wiki](https://github.com/ar9av/obsidian-wiki). The vault itself is
private; only the tool is public. Sync it with `wiki-update` at the end of a substantial
change — decisions and seams, not a diff summary.
## Style
- Line-wrap prose at ~80 characters; the doctrine files are read in narrow panes.
- State the rule, then the failure it prevents. Doctrine files end with a
*Rationalizations* table — the excuse an agent will reach for is worth writing
down alongside the rule.
- Markdown hazard: a wrapped line that **starts** with `>` becomes a blockquote.
Reflow precedence chains like `code > docs > wiki > memory` so no line begins
with the character.