Skip to main content

When you need this

Skip this page entirely if your work items only ever touch the repo shipflow runs in — leave repos: [] and everything works exactly as the single-repo flow describes elsewhere in these docs. Read on if a work item can span more than one repo — a common shape is a UI repo plus an API repo, or a services repo plus a shared libraries repo.

The core idea

Acceptance criteria belong to the work item, not to a repo. A work item might need one repo, or several — feature-plan decides which, per work item. shipflow then produces one branch and one PR per touched repo, all carrying the same work-item reference and cross-linked to each other, while feature-verify aggregates the AC set across every touched repo. No repo ships a half-met acceptance criterion.

Configuring repos

In shipflow.config.md:
Each entry needs a name (used everywhere — branch naming, PR cross-links, state.json), a way to find the code (path if already checked out locally, or url to clone), a host, and a base branch. host can differ per repo (e.g. one repo on GitHub, another on Azure Repos) — it overrides the top-level code_host.host just for that entry.
If a repo isn’t checked out yet, feature-context flags it during its first read, and feature-plan notes how to obtain it (clone from url, or ask you) before implementation needs a working tree.

Worked example

Say AB#1234 is “show the user’s subscription tier in the account settings page.” The API needs a new field; the frontend needs to display it.
1

Context

feature-context reads the requirement, normalizes two ACs (AC1: API returns tier field, AC2: settings page displays it), and — since repos is non-empty — takes a first pass at which repos are likely touched: api for AC1, web for AC2. This is a first read, not a commitment.
2

Plan

feature-plan commits to the touched set. It explores both candidate repos, confirms both genuinely need changes, and produces a repo table:Every task is tagged with the repo it belongs to. A repo from repos: that turns out unnecessary for this work item is simply left untouched — it gets no branch.
3

Implement & Tests

Runs per touched repo, on that repo’s own branch (feature/1234-subscription-tier in both api and web, each off its own base), with that repo’s own test framework.
4

Verify

Aggregates across repos. AC1 is MET only if api’s code + test satisfy it; AC2 only if web’s do. If api’s change lands but web’s doesn’t yet, the whole item is NOT READY — a gap in one repo blocks the item, not just that repo’s slice of it.
5

PR

Opens one PR per touched repo (api and web), each carrying the same AB#1234 reference. Because cross_link_prs: true, each PR body links to its sibling — api’s PR notes “see also” the web PR and vice versa. Each PR lists the ACs it owns; the other repo’s ACs are noted with a pointer to where they’re covered, so the full set is legible from either PR.
The work item’s tracker state moves once, after every touched repo’s PR is open — not per repo — and only if that gate is approved.

What changes vs. single-repo

Notes

  • Tests can differ per repo. The top-level tests.framework / tests.run_command are the default; override per repo with repos[].tests.{framework,run_command} if, say, web uses Vitest and api uses pytest.
  • An AC can span two repos. If it genuinely needs work in both (an API field and the UI that shows it), feature-plan maps it to a task in each — it’s MET only when both land.
  • Repos left out of repos: don’t exist to shipflow. It won’t discover repos on its own; every repo a work item might ever touch needs an entry, even if most work items only touch one of them.