Teams and scale¶
AtipSpec is built for projects that run for months with several people and their agents. Two ideas carry the weight: the unit of assignment is the delivery, and the cost of a request is bounded by the delivery, not by the project.
Deliveries, not tasks¶
| Jira | AtipSpec | Unit of |
|---|---|---|
| Epic | initiative | months of work, several people |
| Story | delivery | assignment to one person, one branch |
| Sub-task | task | one agent run with a clean context |
Tasks inside a delivery share a spec and a branch and run in sequence. A
delivery is sized to close within a working day: check warns above
max_requirements and max_tasks, status shows each delivery's lead time,
from its creation to the acceptance of its result, and check, status and
the dossier warn above max_age_hours (8 by default). Bigger work becomes
several deliveries under an initiative. Work that two people should do in
parallel becomes two deliveries, split by ownership, with the interface
between them fixed in the initiative:
atipspec initiative orders --title "Orders"
atipspec new orders-api --title "Orders API" --capability orders --owner ana --initiative orders --worktree
atipspec new orders-screen --title "Orders screen" --capability orders-ui --owner luis --initiative orders --worktree --impact orders
Creating a delivery with --initiative lists it in the roadmap. The roadmap
has an Interfaces section for what both sides agree on (an API shape, an
event, a schema) and the file that is its source of truth.
- orders-screen [in_progress] tasks 1/3 @luis SHOP-12 (https://example.atlassian.net/browse/SHOP-12), open 1d 3h: Orders screen
accepted: proposal accepted by luis@example.com at 2026-10-02T09:12:00Z via client-permission (Claude Code) (current)
next: T2 is not complete: implement it and record `atipspec task-done orders-screen T2 --note <summary>`
initiatives:
- orders 1/2: orders-api delivered; orders-screen in_progress
atipspec status --format json gives the same state to scripts and
--format markdown as tables for a pull request or a job summary; with a
slug, the delivery's entry and its checks.
One delivery, one branch, one worktree¶
creates the branch delivery/orders-api in a sibling folder
../<repo>-orders-api. Each person, and each agent, works in its own tree;
nothing is shared until the pull request. --branch does the same in the
current folder. A team with its own branch names passes --branch-name
feature/SHOP-12-orders-api with either, and --reuse-branch with
--branch-name to start the delivery on a local branch that already exists.
A delivery that needs another one first names it with --depends-on:
atipspec new orders-screen --title "Orders screen" --capability orders-ui --depends-on orders-api --worktree
Until orders-api is archived with an accepted receipt in this checkout,
check orders-screen keeps a todo Waiting for accepted dependency
orders-api, so its gate is not green and its result cannot be accepted.
Integrating main changes the tree, which makes the evidence and the review
stale. The gate makes you verify and review again after integrating, so
there is no "it worked on my branch". Integrate before the final verification:
after deliver, merge-check blocks any change to the branch's code,
integration included, until the delivery is reopened and accepted again. One
delivery per branch keeps that cheap; delivering another delivery on the same
branch also makes the open ones stale.
Start a delivery from an up-to-date main, and take main in with a merge only
when the branch needs it. What main brings does not void the approved
proposal, nor count as the delivery's change in check, the review packet and
the dossier, once AtipSpec knows which branch is main: see when main
moves. A rebase onto main rewrites the branch: when
new ran after commits of the branch's own, it rewrites the commit the
delivery started from, and check then stops until you restore the history
from before the rebase (git reflog lists it) and merge instead.
When main moves¶
Several deliveries often change the same capability, each on its branch. When one is integrated into main and another branch takes main in, the second delivery's living spec now holds the first one's requirements too. AtipSpec attributes them to the integration branch:
integrationinteams.json, such as"refs/remotes/origin/main";- without it, the first of
refs/remotes/origin/HEAD, whichgit clonesets,refs/remotes/origin/mainandrefs/remotes/origin/masterthat resolves, so a CI checkout withoutorigin/HEADfinds the same branch; declareintegrationwhen main has another name, since a CI checkout has noorigin/HEAD;statusandauditwarn whenorigin/HEADnames such a branch andintegrationis not set; - under a trust policy, only the policy's
integration:teams.jsonis part of the candidate and names nothing there.
What main brought is what changed between the commit where the delivery's
base meets main and the merge base of HEAD with main, as in the diff of a
pull request: each file the delivery has not changed since, and each
requirement as that merge base holds it. It does not matter whether main
merged, squashed or rebased the other deliveries. Then:
- The proposal's approval stays current, and so do the
task-donerecords: it hashes the requirements the delivery lists, not the rest of the living spec. Text outside the requirements, before the first one or under a heading between them, the frontmatter, and an HTML comment or fenced code in a requirement the delivery does not list are in no approval:checkwarns when they change, compared with what main holds where main changed them, and only the acceptance of the result, which binds the whole tree, covers them. checkand the proposal's diff compare the living spec with what main holds, so a requirement main brought is not the delivery's change. Editing it, removing it or putting it back as it was is: a requirement the delivery changes without listing it is an error, at every risk level, andmerge-checkblocks it too.- The files main brought are not outside the plan's
scope, and the review packet and the dossier list them apart instead of in the diff. A file the delivery changes too stays its own, in the index or not, and so does one it puts back as the base had it, such as a file main deleted: the review packet and the dossier show it against the merge base, as a pull request's diff does, andcheckwarns when the delivery undoes there, in whole or in part, what main changed: a line main added that the delivery takes out or rewrites, or one main took out that the delivery puts back where it was, or anywhere when main left no such line in the file. - The evidence and the review are stale, as after any change of the tree: verify and review again.
Nothing is attributed without a ref that resolves, nor when main holds the
delivery's own folder, as when it was committed on main itself. merge-check
covers what the dossier shows, so without a ref, or when the pull request
targets a branch other than the integration branch, such as a release branch,
a change of that branch that the branch puts back as the base had it is code
outside any delivery. The dossier the human accepted is what counts. When
an open delivery puts back what the integration branch changed, the
acceptance of its result names the merge base its dossier shows those files
against, so taking that branch in again afterwards voids it, even with the
tree kept, as git merge -s ours keeps it. An archived delivery covers the
files its dossier showed when it was delivered, as its receipt lists them
(under a trust policy, as its dossier shows them at the commit it was
delivered from), not what a dossier would show at the head, nor a file the
target branch changed after the commit that archived it: keeping the
delivered code, the branch takes that change back. The accepted
risk: a change main made outside a delivery, such as a hotfix, counts as
brought too, as it does in the diff of a pull request: it is judged where it
entered main, in its own pull request, whose merge-check blocks code
outside any delivery unless exempt_paths on main exempts it. A file that a delivery archived earlier in the same
branch changed, and main changed too, no longer matches what main holds once
the branch takes main in, so it counts as the open delivery's change, outside
its scope included: one delivery per branch avoids it. In local mode the ref
and the receipts of the deliveries archived in the branch are local files
that could be moved or written, and a receipt written by hand and committed
vouches for the requirements its spec lists, and covers the files it lists,
as one deliver wrote would: this detects drift. A trust policy authenticates in merge-check, whose ref
the protected worker fetches: it checks each delivery archived in the branch
again where it was delivered from and takes the requirements only of those
it authenticates; check reads the receipts as committed.
A merge with conflicts that is not committed yet counts as the delivery's
change until the merge commit: the attribution reads HEAD. Two deliveries
that add requirements to the same capability conflict in git where both
append; keep both blocks when resolving.
Team configuration¶
.atipspec/teams.json is optional and written by hand:
{
"schema": 1,
"refs": ["refs/remotes/origin/delivery/*", "refs/heads/release"],
"owners": {"orders": ["ana"], "orders-ui": ["luis", "marta"]},
"repositories": {"api": "../shop-api"},
"integration": "refs/remotes/origin/main",
"ticket_url": "https://example.atlassian.net/browse/{ticket}"
}
| Key | Meaning |
|---|---|
refs |
full ref names the board and check read; a * at the end takes every ref it matches in that last segment |
owners |
capability to its human owners: a guided delivery of an owned capability needs an owner in spec.md |
repositories |
name to a local checkout, for contract-pin; system defaults to system in config.yaml |
integration |
the integration branch, as a full ref name; see when main moves |
ticket_url |
an https address with {ticket} where a ticket key or issue number goes |
A guided proposal's approval hashes only the owners of its capability and
integration, under a trust policy too; editing the other keys voids no
approval.
The board: atipspec team¶
lists every open delivery of the working checkout and of each ref in
refs: its status, owner, ticket, ref, capability, requirements and
dependencies. Run it before you touch a capability others may be changing.
check reports a requirement two deliveries declare differently. It reads
only local refs and never fetches; the first line says when the repository
last fetched. --fetch first fetches, without prompting for credentials,
each remote a refs/remotes/<remote>/... entry names: git and ssh run
without a terminal or an askpass program, so a remote that needs a password
or a passphrase the credential helper or ssh-agent does not hold fails. A
remote that fails gets a warning and the board shows what was fetched last.
It is the only command that uses the network.
Tickets¶
atipspec new --ticket takes a key such as SHOP-12 (Jira, Linear), an
issue number such as #42, or an https URL; anything else is refused. With
ticket_url in teams.json, a key or a number becomes a link in status,
team, the proposal, the dossier (ticket_url in its JSON, a link in
Markdown and HTML) and merge-check. The ticket is not part of what the
proposal's approval hashes: add or correct it in spec.md at any time.
AtipSpec links the ticket; it never writes to the tracker. A free-text
ticket written before 0.3.0 is shown as it is, without a link.
Pull requests¶
github-local.yml and gitlab-local.yml from atipspec enterprise-init run
merge-check as the pull request's check; its job summary lists each delivery
the branch touches with its state and ticket. When the user asks the agent to
open a pull request, the agent puts the delivery's ticket and the output of
atipspec report <slug> --format markdown in its description. In local mode
an accepted receipt is not authenticated: the check confirms that the branch
matches what was accepted, not who accepted it.
Overlaps¶
status and audit warn when two open deliveries declare the same living
spec in their impact:
That is the conflict worth catching before code exists. Git conflicts stay git's business.
Several repositories¶
Each repository has its own .atipspec/, because approvals and evidence are
bound to the tree of their repository. For the shared parts, a small system
repository holds the system contract, the shared glossary, the API contracts
and schemas, and the initiatives that span repositories. Point each project
at a checkout of it:
atipspec new then pins the system's contract.md, and its glossary.md
when it has one, at the checkout's current commit, in the delivery's
contracts.json:
The delivery's context reads them from that commit, so the system moving on
changes neither what the agent reads nor the approved proposal. When the
checkout is not available, new warns and the proposal cannot be approved
until the pin exists:
Any other contract, such as an API schema in another repository, is pinned
the same way after naming its checkout in teams.json repositories:
--ref is resolved once to a full commit id; check refuses a pin that names
a branch or a tag, or whose content no longer has the recorded digest.
Pinning again changes contracts.json, which voids the proposal's approval:
a shared contract changed, and the human approves the delivery against the
new one. Legacy deliveries keep reading the checkout as it is.
Keeping the context small¶
Every phase command prints the output of atipspec context <slug>, which
loads the contract, overview, glossary, the living specs in the impact list,
the accepted decisions that affect them, and the delivery's own files. It
prints the size:
Context for orders-api: 184 lines (budget 800)
43 contract (.atipspec/contract.md)
22 overview (.atipspec/overview.md)
10 glossary (.atipspec/glossary.md)
37 decision DEC-001
48 spec (.atipspec/deliveries/orders-api/spec.md)
24 plan (.atipspec/deliveries/orders-api/plan.md)
Two things keep it bounded over time: the curate phase with its caps, and the impact list, which is why a spec declares what it touches instead of the model guessing.
Policies per project¶
Ceremony is a dial in config.yaml, and in each delivery's
risk, not a property of the tool:
approve_plan: true # legacy deliveries: build waits for the plan's own approval
review_rounds: 3 # reviews per approved proposal, the first included, before `review` refuses and you decide
context_budget: 800 # lines; context warns above it
max_requirements: 5 # check warns above it: split the delivery
max_tasks: 8 # same, for the plan
max_age_hours: 8 # check and status warn when a delivery has been open longer
The two stop points, the proposal and the result, stay in every project: the
dials change what the gate asks between them. A regulated project adds
require_proof: true, require_isolated_review: true, a low budget and, for
authenticated approvals, trust: required with an external policy.