# Shipit — AI Agent Guide
This document describes how a coding agent can use shipit to drive a
**git flow lite** release process: feature branches land in `dev`, `dev`
promotes to `main`, and `main` is tagged for release.
---
## Workflow Overview
```
feature/my-feature ──b2b──► dev ──b2b──► main ──b2t──► v1.2.3
```
Each stage follows the same two-step **plan / apply** pattern:
1. **Plan** — collect commits, generate a description/title/notes, write a
YAML file to `.shipit/plans/<hash>.yml`. Nothing is created on the platform yet.
2. **Apply** — read the plan file and execute: open a PR/MR, or create and push
the annotated tag.
The plan file is the agent's opportunity to review, enrich, or rewrite any field
before anything is published.
---
## Setup
```bash
shipit init --platform-token <TOKEN> --platform-domain github.com
```
This writes `shipit.toml` and creates `.shipit/plans/`.
---
## Stage 1 — Feature Branch → Dev
### 1a. Generate a plan
```bash
shipit b2b plan feature/my-feature dev
```
Shipit collects commits on `feature/my-feature` that are not yet on `dev`,
enriches them with PR/MR titles from the platform API, and writes a plan file.
**Conventional-commit structured description** (recommended when commits follow
the conventional-commit convention):
```bash
shipit b2b plan feature/my-feature dev --conventional-commits
```
The description will be grouped into sections (`## Features`, `## Bug Fixes`,
`## Infrastructure`, etc.).
**Agent-provided title and description** (skip commit collection entirely):
```bash
shipit b2b plan feature/my-feature dev \
--title "feat: add payment integration" \
--description "$(cat <<'EOF'
## Summary
- Adds Stripe checkout flow
- Introduces `PaymentService` with retry logic
- Updates API contract in `openapi.yml`
EOF
)"
```
When both `--title` and `--description` are supplied, no commits are collected
and the plan is written immediately.
### 1b. Apply the plan
```bash
shipit b2b apply <plan-filename>.yml
```
`<plan-filename>` is the filename (not a full path) of the file written to
`.shipit/plans/` in the previous step. Shipit opens the pull/merge request
and prints the URL.
**Capturing the plan for agent use** — pass `--yaml` to receive the full plan
on stdout. The output includes a `plan_file` field with the filename ready for
`apply`, and the `commits` list for extracting commit SHAs:
```bash
PLAN=$(shipit b2b plan feature/my-feature dev --yaml -y)
PLAN_FILE=$(echo "$PLAN" | yq '.plan_file')
shipit b2b apply "$PLAN_FILE"
```
---
## Stage 2 — Dev → Main
Same commands, different branches:
```bash
# Auto-generate from commits (conventional-commit format)
PLAN=$(shipit b2b plan dev main --conventional-commits -y --yaml)
PLAN_FILE=$(echo "$PLAN" | yq '.plan_file')
# Apply
shipit b2b apply "$PLAN_FILE"
```
The `-y` flag skips the interactive title prompt and accepts the suggested
`"Release Candidate vX.Y.Z"` title derived from the commit history.
---
## Stage 3 — Main → Tag
### 3a. Generate a tag plan
```bash
shipit b2t plan main
```
Shipit finds the most recent tag reachable from `main`, collects commits since
that tag, and suggests the next semantic version.
**Conventional-commit structured notes:**
```bash
shipit b2t plan main --conventional-commits -y
```
**Agent-provided tag name and notes:**
```bash
shipit b2t plan main \
--tag v1.2.3 \
--description "$(cat <<'EOF'
## What's Changed
- New payment integration (#42)
- Fixed session timeout bug (#38)
EOF
)"
```
### 3b. Apply the tag plan
```bash
shipit b2t apply <plan-filename>.yml
```
Creates an annotated local tag and pushes `refs/tags/<name>` to the remote.
**Capturing the tag plan for agent use:**
```bash
PLAN=$(shipit b2t plan main --conventional-commits -y --yaml)
PLAN_FILE=$(echo "$PLAN" | yq '.plan_file')
shipit b2t apply "$PLAN_FILE"
```
---
## Agent-Enriched Plans (Recommended Pattern)
> **Important for AI agents:** Always present the final plan to the user and
> wait for explicit approval before running `apply`. Opening a pull/merge
> request or pushing a tag is irreversible — the plan step exists precisely
> to give the user a review checkpoint. Never call `apply` autonomously.
The most powerful use of shipit for an agent is:
1. Run `b2b plan` or `b2t plan` with `--yaml` to collect commits, write the
plan file, and receive the plan on stdout.
2. Use `yq` to extract the `plan_file` name (for the `apply` step) and the
boundary commit SHAs (for `git diff`).
3. Run `git diff <first-sha>^..<last-sha>` to get the full diff for the range.
4. Summarise the diff and rerun `plan` with `--description` and/or `--title` to
overwrite the auto-generated content with a human-quality summary.
5. Run `apply` on the enriched plan.
### Example: agent-enriched b2b plan
```bash
# Step 1 — write the initial plan and capture the YAML output
PLAN=$(shipit b2b plan feature/payments dev --conventional-commits -y --yaml --allow-dirty)
# Step 2 — extract the plan filename and boundary commit SHAs with yq
PLAN_FILE=$(echo "$PLAN" | yq '.plan_file')
LAST_SHA=$(echo "$PLAN" | yq '.commits[0]' | awk '{print $NF}')
FIRST_SHA=$(echo "$PLAN" | yq '.commits[-1]' | awk '{print $NF}')
# Step 3 — get the diff
DIFF=$(git diff "${FIRST_SHA}^".."${LAST_SHA}")
# Step 4 — ask the agent to summarise the diff, then rerun plan with the result
SUMMARY="<agent-generated summary goes here>"
PLAN=$(shipit b2b plan feature/payments dev \
--title "feat(payments): Stripe checkout integration" \
--description "$SUMMARY" \
--yaml --yes --allow-dirty)
PLAN_FILE=$(echo "$PLAN" | yq '.plan_file')
# Step 5 — present the plan to the user for confirmation before applying
echo "$PLAN"
# Step 6 — apply only after the user approves
shipit b2b apply "$PLAN_FILE" --allow-dirty
```
### Commit ordering in the `commits` list
Commits are stored newest-first under the `commits:` key. Each entry is the
string `"<message> <sha>"` — the SHA is always the last whitespace-separated
token. Use index `0` for the newest commit and `-1` for the oldest:
```bash
PLAN=$(shipit b2b plan feature/payments dev --yaml -y)
LAST_SHA=$(echo "$PLAN" | yq '.commits[0]' | awk '{print $NF}') # newest
FIRST_SHA=$(echo "$PLAN" | yq '.commits[-1]' | awk '{print $NF}') # oldest
git diff "${FIRST_SHA}^".."${LAST_SHA}"
```
The diff can then be passed to the agent's language model to generate a
structured description before calling `plan` again with `--description`.
---
## Flag Reference
### `shipit b2b plan <source> <target>`
| `--conventional-commits` | `-c` | Group description by commit type |
| `--title <text>` | | Override the suggested PR/MR title |
| `--description <text>` | | Override the auto-generated description |
| `--only-merges` | | Restrict commit collection to merge commits |
| `--no-sign` | | Omit the "generated by Shipit" footer |
| `--yes` | `-y` | Accept all prompts non-interactively |
| `--yaml` | | Emit the plan as YAML to stdout (includes `plan_file` field) |
| `--allow-dirty` | | Continue even if the working directory has uncommitted changes |
| `--remote <name>` | | Git remote name (default: `origin`) |
| `--dir <path>` | | Repository root (default: cwd) |
### `shipit b2b apply <plan-file>`
| `--allow-dirty` | Continue even if the working directory has uncommitted changes |
| `--remote <name>` | Git remote name (default: `origin`) |
| `--dir <path>` | Repository root (default: cwd) |
### `shipit b2t plan <branch>`
| `--conventional-commits` | `-c` | Group notes by commit type |
| `--tag <name>` | | Override the suggested tag name |
| `--description <text>` | | Override the auto-generated tag notes |
| `--latest-tag <name>` | | Compare against a specific tag instead of auto-detecting |
| `--only-merges` | | Restrict commit collection to merge commits |
| `--no-sign` | | Omit the "generated by Shipit" footer |
| `--yes` | `-y` | Accept all prompts non-interactively |
| `--yaml` | | Emit the plan as YAML to stdout (includes `plan_file` field) |
| `--allow-dirty` | | Continue even if the working directory has uncommitted changes |
| `--remote <name>` | | Git remote name (default: `origin`) |
| `--dir <path>` | | Repository root (default: cwd) |
### `shipit b2t apply <plan-file>`
| `--allow-dirty` | Continue even if the working directory has uncommitted changes |
| `--remote <name>` | Git remote name (default: `origin`) |
| `--dir <path>` | Repository root (default: cwd) |
---
## Plan File Format
Files written to `.shipit/plans/` look like this:
```yaml
# Shipit Plan - Generated by shipit v0.5.0 on 2024-06-01T12:00:00Z
shipit_version: 0.5.0
generated_at: "2024-06-01T12:00:00Z"
source: feature/payments
target: dev
title:
value: "Release Candidate v1.2.0"
generated_by: default # "user" | "default" | "conventional-commits" | "raw"
description:
value: |
## Features
- feat: add Stripe checkout flow abc123
generated_by: conventional-commits
commits:
- "feat: add Stripe checkout flow abc123 a1b2c3d4"
- "fix: handle webhook timeout def456 e5f6a7b8"
```
The `generated_by` field records what produced each value so downstream
tooling (and the agent) can decide whether to trust it or regenerate it.
When `--yaml` is passed, the stdout output adds a `plan_file` field not
present in the written file:
```yaml
plan_file: 3f9a1c2e4d7b0e5f.yml
```
Use this field to drive the `apply` step without filesystem globbing.
---
## Complete Git Flow Lite Example
```bash
# ── Feature → Dev ──────────────────────────────────────────────────────────
PLAN=$(shipit b2b plan feature/payments dev --conventional-commits -y --yaml --allow-dirty)
# ── Dev → Main ─────────────────────────────────────────────────────────────
PLAN=$(shipit b2b plan dev main --conventional-commits -y --yaml --allow-dirty)
# ── Main → Tag ─────────────────────────────────────────────────────────────
PLAN=$(shipit b2t plan main --conventional-commits -y --yaml --allow-dirty)