When you need this
Skip this page entirely if your work items only ever touch the repo shipflow runs in — leaverepos: [] 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:
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.
Worked example
SayAB#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.What changes vs. single-repo
Notes
- Tests can differ per repo. The top-level
tests.framework/tests.run_commandare the default; override per repo withrepos[].tests.{framework,run_command}if, say,webuses Vitest andapiuses pytest. - An AC can span two repos. If it genuinely needs work in both (an API field and the UI that
shows it),
feature-planmaps 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.