> ## Documentation Index
> Fetch the complete documentation index at: https://kowshik-93.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Polyrepo

> One work item, one acceptance-criteria set, multiple repos — a branch and PR per repo it actually touches.

## 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`:

```yaml theme={null}
repos:
  - name: web                     # short id used in branches, PR cross-links, and state
    path: ../web                  # local checkout path (relative to this repo) …
    # url: https://github.com/myorg/web.git   # … or a clone URL if not checked out locally
    host: github                  # overrides code_host.host for this repo
    base: main                    # base branch for this repo
  - name: api
    path: ../api
    host: github
    base: main

cross_link_prs: true
```

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.

<Tip>
  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.
</Tip>

## 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.

<Steps>
  <Step title="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.
  </Step>

  <Step title="Plan">
    `feature-plan` **commits** to the touched set. It explores both candidate repos, confirms both
    genuinely need changes, and produces a repo table:

    | Repo  | Base   | Why touched                     | Gets a PR |
    | ----- | ------ | ------------------------------- | --------- |
    | `api` | `main` | Add `tier` to the user response | yes       |
    | `web` | `main` | Display `tier` on settings page | yes       |

    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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

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

|                      | Single-repo              | Polyrepo                                   |
| -------------------- | ------------------------ | ------------------------------------------ |
| Branches             | one                      | one per touched repo                       |
| PRs                  | one                      | one per touched repo, cross-linked         |
| AC grading           | per-file evidence        | per-file evidence, tagged with owning repo |
| `state.json`         | one branch/test/PR entry | one entry **per touched repo**             |
| Tracker state update | once                     | once, after *all* PRs are open             |

## 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.
