Files
Skills/docs/conventions.md
T
Brummel e7009bc304 fix(implement-loop): guard against a task discarding sibling tasks' uncommitted work
During a multi-task run nothing commits between tasks, so one task's
file-level `git checkout -- <file>` / `git restore <file>` silently
destroys an earlier task's uncommitted DONE work in a shared file. The
loss was structurally invisible: spec- and quality-reviewers are scoped
to the current task's footprint, diff_fingerprint only detects
same-task repair cycles, and the end-of-iteration tree gate counts
files (the surviving changes keep it nonzero). Observed in the wild as
a PARTIAL run whose DONE reports did not match the tree. Two layers,
both plugin-side (harness-level prevention is ruled out — decision log
on the issue):

Prose guard — implementer.md Step 8 no longer sanctions file checkout
for scope curation: over-reach is undone by editing back, a broken
intermediate state is repaired forward or reported BLOCKED (new fourth
BLOCKED bucket in the status protocol). A matching Iron Law line in
implement/SKILL.md covers all in-loop agents, whole-file and --patch;
docs/conventions.md now marks the checkout discard idiom as the
orchestrator's, between iterations.

Mechanical guard — after every task of a multi-task run a snapshot
agent records `git stash create` (a dangling commit; HEAD, index, and
tree untouched — semantics verified empirically in a scratch repo) plus
the `git diff HEAD --name-only --no-renames` path set. A path that was
HEAD-modified at one boundary and gone at the next trips a hard
BLOCKED naming the lost paths, the boundary, and the recovery snapshot
sha (`git show <sha>:<path>`); the discard verdict outranks per-task
outcomes in blocked_detail since the reports and the tree have
diverged. Coarse by design, in both directions, and documented as
such: a checkout-then-re-edit or a --patch hunk restore escapes the
comparison (the snapshot keeps it diagnosable); a legitimate
back-to-HEAD edit trips it (the verdict says adjudicate against the
snapshot). --no-renames keeps a staged rename from reading as a loss;
untracked files are outside the threat model (checkout cannot discard
them). Cost: one sonnet/medium call per task, multi-task runs only;
single-task and mini runs are unchanged.

Verified: node --check on the async-wrapped script and a stub-agent
harness — discard trips the hard BLOCKED and stops the loop; an
accumulating happy path, a plan-intended deletion, a single-task run,
and a dead snapshot agent all pass without a false positive.

closes #23
2026-07-09 11:27:51 +02:00

11 KiB

Conventions

The skills plugin used to read a per-project dev-cycle-profile.yml. It no longer does. There was never a parser — the profile was prose the skill bodies told the model to read, and almost every slot was either dead, constant across all projects, or derivable. So the plugin now splits cleanly in two:

  • Fixed conventions (this file) — the things that were constant across every project. They are not configurable; the skill and agent bodies name them directly.
  • Per-project facts — the handful of things that genuinely vary per project (where the code lives, how to build and test it, the tracker slug, …). These live as prose in each project's own CLAUDE.md under a ## Skills plugin: project facts heading. See ../templates/CLAUDE.md.fragment for the template, and the section list at the bottom of this file.

File layout

Artefact Location
Specs (from specify) docs/specs/
Plans (from planner) docs/plans/

These directories are fixed. A project that keeps specs and plans elsewhere is the rare exception and states the override in its CLAUDE.md project facts.

Both directories are git-ignored — their contents are transient working files, never committed (see § Lifecycle). The ignore is project-local: each project commits a docs/.gitignore whose two entries, specs/ and plans/, are relative to docs/ and so match docs/specs/ and docs/plans/. That docs/.gitignore is the one committed thing — it travels with the repo; the spec and plan files it covers never are. It is created once (whichever skill first writes a working file — specify, or planner on a direct tidy dispatch — creates it if absent) and committed like any other repo file, after which both directories are invisible to git for every future cycle. A new project sets this up as part of adopting the plugin (../templates/CLAUDE.md.fragment).

Lifecycle

docs/specs/ and docs/plans/ hold only the active cycle's artefacts — they are working space, not an archive. A spec or plan is valid for the cycle that produces it; its code shapes drift the moment the code moves, so a stale one left lying around reads as a live API reference and misleads the next agent that opens it.

The two directories are git-ignored — their contents are never committed. A spec/plan is a working file: present on disk while its cycle is live (readable across sessions, since the working tree persists), and at cycle close — after audit is drift-clean — the orchestrator discards it with a plain shell rm. There is nothing to git rm (it was never tracked) and no deletion commit is produced; the working tree is simply left without a stale artefact. This is the same shell-delete-an-uncommitted-file mechanic specify already uses on its grounding-BLOCK failure path.

A spec/plan is therefore transient on disk: present for its own cycle, rm'd afterwards, and — never having been committed — leaving no trace in git. It is not recoverable, by design. The durable record of a past cycle's design intent is the design ledger only (where the rationale is lifted during audit); the implementation is committed as normal code.

No durable artefact may cite a spec or plan — not by number, not by path. Code, tests, docs, and ledger entries outlive the spec/plan that prompted them, so any reference to docs/specs/… or "spec NNNN" would dangle the moment the cycle closes. Cite the design-ledger contract instead (e.g. a C-id); it is the durable record the spec's rationale was lifted into.

Naming

Files are named slug.md — a short kebab-case identifier of the cycle's topic (slug separator -). There is no numeric prefix: because the directories are git-ignored, shell-rm'd at cycle close, and cited by nothing (see § Lifecycle), they hold at most the active cycle's handful of files, so a slug is already unique enough — the old scan-for-the-next-free-number ritual bought nothing and is gone. A fieldtest-produced spec takes a fieldtest- prefix (fieldtest-slug.md) so it is recognisable in the working tree as the next cycle's planning input rather than this cycle's own spec — it is written after audit's cycle-close sweep and discarded by the next cycle's sweep (see ../fieldtest/SKILL.md).

The file name is not the cycle number. The cycle number that appears in commit subjects (feat(NNNN), audit(NNNN)) and in git log cycle ranges is a distinct, monotonic identifier that must survive spec/plan discard. Source it from the highest NNNN in the feat(NNNN) / audit(NNNN) git-log subjects, plus one — never from a file name (there is none) and never from a per-directory scan. Cycle commits therefore MUST carry the zero-padded (NNNN) cycle number in their subject.

Vocabulary

The pipeline's nouns are fixed:

Term Meaning
cycle One round in the pipeline graph (brainstorm → specify → planner → implement → audit → [fieldtest]). NOT the top-level container.
iteration A sub-unit of a cycle.
milestone Tracker container spanning many cycles; closes only when complete AND functional (see pipeline.md § Milestone-close gate).
contract A single design-ledger entry.

When a project declares a glossary (in its CLAUDE.md project facts), that glossary is the source of truth for domain nomenclature and overrides these names where they collide (see glossary-convention.md).

Standing reading

Every agent reads, at the start of every dispatch:

  • the project's CLAUDE.md
  • git log -10 --format=full

A project may add more — globally or per role — in its CLAUDE.md project facts (standing reading). If the project declares a glossary, that file is implicitly standing reading for every role too.

Git discipline

  • Only the orchestrator commits. No skill agent runs git commit. Agents write into the working tree as unstaged changes; the orchestrator inspects, decides commit shape, and commits.
  • main HEAD is sacrosanct — below the session anchor. Nobody runs git reset / git revert on user-ratified main history (or any protected branch). main moves forward only via orchestrator commits; a wrong agent diff is discarded — by the orchestrator, between iterations — with git checkout -- <paths> / git stash, never by rewinding ratified history. That discard idiom is the orchestrator's alone: an agent running INSIDE a multi-task implement iteration never checkouts/restores a file — nothing commits between tasks, so it would silently destroy sibling tasks' uncommitted work; in-loop recovery is forward-editing or BLOCKED (implement/SKILL.md Iron Law, issue #23). One narrow /boss exception — the rollback sandbox: inside an autonomous run the orchestrator anchors on the main HEAD it started from (the last user-ratified state) and MAY git reset --hard its own commits above that anchor — autonomous, unpushed work only — back toward (never below) the anchor when it has run into a dead end. A pushed commit has left the sandbox and is forward-only (git revert). See ../boss/SKILL.md § Direction freedom.

These also appear in the universal-discipline fragment (../templates/CLAUDE.md.fragment) that each project's CLAUDE.md imports.

Issue tracker

The tracker is Gitea and the commit close-marker is closes #N (refs #N for non-closing work). The per-project repo slug and the list/show commands live in the project's CLAUDE.md project facts (see below) — the boss skill reads the forward queue from there and records its fork decisions on the run's reference issue there, and an ad-hoc spec-skeptic scope-fork dispatch reads single issues with their comment threads from there.

Separately, /boss files skill-system deficiencies — faults in the plugin itself, hit mid-run — against the plugin's own tracker (slug derived from the plugin checkout's git remote), provenance carried by a body block and never a new label. The skills meta-repo is not driven through its own pipeline, so this tracker and a project's tracker never coincide. See ../boss/SKILL.md § Skill-system feedback.

Pipeline

The phase set, the gates, and the conditional dispatch are fixed and documented once in pipeline.md. There is no per-project pipeline configuration. In particular: brainstorm, specify, planner, implement, audit, debug, tdd, fieldtest, and docwriter are always available. tdd is a standard entry path for test-specifiable work — not an opt-in. audit is mandatory at cycle close. fieldtest / docwriter are orchestrator-dispatched. There is no per-project behavioural toggle: spec auto-sign is always on under /boss.

Per-project facts (in each project's CLAUDE.md)

Under ## Skills plugin: project facts, where applicable:

Fact Used by Notes
code roots architect, quality-reviewer, fieldtester Directories reviewers walk. Required.
build / test command implement, audit Required. Exit 0 = success.
lint command (quality) Optional.
doc-build command docwriter Optional; prints warnings on stderr.
regression scripts audit (bencher) Optional list; non-zero exit = regress.
architect sweeps audit (architect) Optional list; non-zero exit = drift suspicion.
design ledger architect, most agents Optional path (e.g. design/INDEX.md).
glossary every role Optional path; implicitly standing reading.
design contracts / models docwriter, specify Optional dirs. Aspirational-source frontmatter marker recommended (see below).
bench dir fieldtest, bencher Optional path.
public interface fieldtester Optional list — the only surface the fieldtester may read; everything else (code roots, bench) is forbidden to it.
fieldtest examples fieldtester Optional path where fixtures are written.
by-role standing reading named agent Optional; extra files/commands a specific role reads.
issue tracker boss, spec-skeptic Repo slug + list command + show command (the latter MUST render an issue WITH its comments).

Aspirational-source marker (recommendation)

Files under a project's design-models / RFCs / proposals directory commonly carry aspirational code — constructs written before the surface that parses them exists. To let a later specify tell aspirational content from validated contract, give each such file a frontmatter marker:

---
status: aspirational
validated-against: <commit-sha | "no validation">
---

When the marker is absent the signal is simply absent — specify degrades to treating the content as unmarked, never hard-failing. Content lifted from an aspirational source is flagged as a target, not verified fact, before it ships in a spec.