Skip to content

How it works

Three truths with different lifetimes

Truth Where Lives Changes through
The contract .atipspec/contract.md the whole project an accepted decision
The living specs .atipspec/specs/<capability>.md the product atipspec deliver
A delivery .atipspec/deliveries/<slug>/ days or weeks the phases

The contract says how the system is built: stack, structure, dependency policy, conventions, quality gates, and a block of rules the CLI evaluates. The living specs say what the product does, one file per capability. A delivery is the unit of change: its own spec, plan, evidence, review and deferred findings, created, verified and archived as a whole.

Around them sit the decisions ledger, initiatives that group deliveries over months, and two curated documents, overview.md and glossary.md, that keep the project explainable in a page.

Who decides what

Question Answered by
What do we build? You, approving the proposal (scenarios, approach and tests) in the conversation
Under which rules? The contract you approved, recorded with atipspec accept contract --code <code> and enforced by check and audit
Is a task done? The assistant declares it with atipspec task-done, bound to the approved agreement; a declaration of progress, not proof
Does it pass its checks? atipspec verify, which runs the final verification and records exit codes and the tree hash
Does it meet the spec? A reviewer in an isolated session, declared in review.md, one verdict per criterion with a pointer the gate resolves
Is the result accepted? You, accepting the reviewed result that atipspec report shows; or, under a trust policy, check --policy with trusted CI evidence and product/engineering/QA approvals (verified)
When does it merge? Your repository's policy; atipspec merge-check confirms the branch first

The assistant presents the agreement with its approval code and offers Approve and continue, Request changes or Cancel, using the client's selector or a plain reply. After your explicit approval, it runs atipspec accept ... --code <code> and continues. Claude Code, configured by init and opened at the project root, asks you before that command runs; that prompt confirms the same approval. You do not need to run commands or edit statuses. The verifier records evidence and the reviewer records verdicts; neither can substitute for your decision. Changed agreements need renewed approval. External trust policies retain their authenticated procedure.

Acceptance you can check

There are three human decisions, each presented by a view that prints an approval code (XXXX-XXXX, the first 40 bits of the content's sha256 in Crockford base32):

Decision Presented with Recorded with
The proposal: scenarios, approach and tests atipspec proposal <slug> atipspec accept <slug> proposal --code <code>
The reviewed result atipspec report <slug> atipspec accept <slug> result --code <code>
The contract and its decisions, once per project atipspec proposal contract atipspec accept contract --code <code>

accept refuses when the code does not name the current content, so the record binds the version you saw. It writes a hash of that content, per-file digests, the code and the channel to approvals/local-<phase>.json (the contract's to .atipspec/approvals/contract.json). check compares the hash with the files on disk on every run; a single edited byte after your acceptance turns the delivery back into draft with a todo that names the file that changed. Guided deliveries, fixes included, need the proposal record; a spec and plan pair does not replace it. Legacy deliveries approve their spec, and their plan when approve_plan is on, with accept <slug> spec|plan --code <code>; a fix created before 0.3.0 approves only its spec.

The channel says how the command reached you:

Channel Meaning
terminal The code was typed at the CLI prompt in a terminal without agent markers
client-permission Claude Code ran atipspec accept itself, and this project's rules, intact, make it ask you first in a session opened at the project root
assistant Nothing asked you: a client without permission rules, altered rules, another agent's marker next to Claude Code's, Claude Code run through the Agent SDK or claude -p, or a command line the rule does not match, such as python -m atipspec.cli

The CLI sees only its own process, so each channel is what that process declares. It cannot see whether the session loaded the rules: a session opened in another folder that enters the project with cd is not asked and records client-permission. A script, or an agent that sets none of the markers listed under environment, that Claude Code runs and that calls atipspec accept inside it records client-permission too, although Claude Code asked you only about the script or the agent. And an agent can remove its markers and answer the CLI prompt from a terminal it creates, which records terminal.

This is drift detection, not authentication: the record proves what was accepted and how the command ran, not who you are. Signed approvals under an external policy are the authenticated form, and when a policy is set the gate ignores the local records. See enterprise acceptance.

Phases the CLI opens

A phase starts with its command: atipspec spec <slug>, atipspec plan <slug>, and so on. The command checks what the phase requires and refuses with one line when something is missing: the scenarios incomplete, the proposal not approved, a task not recorded. Otherwise it prints the workflow, the rules and the context, so the model works from what the CLI opened and not from its reading of the request. atipspec ship <slug> names the next phase a delivery can enter. See the flow.

The gate

atipspec check <slug> checks local consistency. Trusted acceptance additionally needs --policy. It reads the files and git, and reports three kinds of things:

  • error: something is wrong or failed. The gate is red.
  • todo: something has not happened yet. The gate is not green yet.
  • warning and info: worth attention, do not block.
password-reset  [implemented]  tasks 2/2
  error   AC-003 FAIL: the link never expires
  error   F1 [blocker] contract: shop/domain/order.py:3 contains forbidden pattern 'from shop\.infrastructure'
  todo    review.md is stale (the working tree changed since the review); run `atipspec review password-reset` and review again
  info    F2 [minor] Missing docstring
Next: Fix: AC-003 FAIL: the link never expires

Exit code 0 means the selected checks passed, 1 means errors, 2 means incomplete. The gate fails closed: without git it is red, because the base commit, the contract rules and the freshness of the evidence come from git. Without a policy, green is only checked, and deliver also requires your recorded acceptance of the result; with the external policy, it requires a green trusted gate. Before the merge, atipspec merge-check --base <target> checks the whole branch: every delivery it touches is accepted, and the code is still the code each receipt accepted.

Evidence bound to the tree

atipspec verify runs the commands a task declares and writes a JSON file with the commands, exit codes, output tails, duration and the fingerprint of the working tree: a content hash computed through a temporary git index, identical before and after committing the same content. The review records the same fingerprint. When the tree changes, both go stale and the gate asks for them again; renaming a task in the plan is the one change they survive. Taking main in changes the tree too, which is the point: there is no "it worked on my branch". A JUnit report counts only when that run wrote it: verify deletes it first, and check never runs the tests itself.

Derived status

The status is never stored: status and check compute where a delivery is from its files and git on every run. What they read are records in the repository: the approvals, progress.json, the evidence, review.md and the receipt.

draft → planned → in_progress → implemented → checked → accepted → delivered

checked is the local gate green; accepted adds your recorded acceptance of that result; under a trust policy, verified is the trusted gate green. A legacy delivery also passes ready, its spec accepted before a plan exists. atipspec accept moves a spec to ready and the contract and its decisions to accepted. The gate treats a hand-written ready without a matching acceptance record as a draft, and a contract, or a decision that affects it, marked accepted by hand without a matching record as not accepted. A decision about a capability is the one status the assistant writes: it marks it accepted after you approve it in the conversation, with no code or record, and the next atipspec proposal lists it so you see what the implementation and the review will follow.

Context that does not grow with the project

atipspec context <slug> assembles exactly what a phase needs: the contract, the overview, the glossary, the living specs the delivery's impact list names, the accepted decisions that affect them, and the delivery's own files. Never the archive. It prints the size against context_budget, and audit warns when a document outgrows its cap so the curate phase can shrink it.

Next: the flow.