e7009bc304
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
222 lines
11 KiB
Markdown
222 lines
11 KiB
Markdown
# 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:
|
|
|
|
```yaml
|
|
---
|
|
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.
|