Four workflow skills that take a feature from fuzzy idea to merged code.
Two human-in-the-loop phases (grill-me, prd), one mostly-together (prd-to-issues
files only on explicit 'create'), and one AFK (ship-it).
grill-me TOGETHER pressure-test the idea with hard interview questions
prd TOGETHER synthesize PRD; gaps stay explicit, not papered over
prd-to-issues MOSTLY thin vertical-slice issues with coverage matrix +
per-issue Slice check; self-audits before showing
ship-it AFK shell loop ships each slice end-to-end with one
commit per issue, status streams to terminal,
Ctrl-C-able, survives session close
Vertical-slice principle throughout: every issue cuts end-to-end through every
integration layer (no horizontal "do all the DB work first" issues). The
AFK loop only ships against acceptance criteria already locked in by the PRD
phase — autonomous code never runs against undefined contracts.
ship-it tracker support: gh (GitHub) and tea (Gitea). For this repo, set
SHIP_IT_TRUNK=development to override the main default.
See .claude/skills/README.md for the full how-to and a worked example.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
5.1 KiB
Markdown
71 lines
5.1 KiB
Markdown
# ship-it iterate — one issue, end-to-end
|
|
|
|
You are running ONE iteration of the ship-it AFK loop. Implement, verify, and ship exactly one issue, then exit. The outer shell loop will pick the next one.
|
|
|
|
**Mode: AFK.** Do not ask questions. If the issue is genuinely undecidable from its body + linked PRD + grilling notes already in the issue or repo, drop a comment on the issue with the specific question, label it `needs-decision`, and exit with status=needs-decision. Do not guess at user intent.
|
|
|
|
Variables provided below this prompt: `ISSUE_NUMBER`, `TRACKER_CLI` (`gh` or `tea`), `TRUNK_BRANCH`, `REPO_ROOT`.
|
|
|
|
## Steps
|
|
|
|
1. **Read the issue.**
|
|
- GitHub: `gh issue view $ISSUE_NUMBER --json number,title,body,labels`
|
|
- Gitea: `tea issues $ISSUE_NUMBER --output json`
|
|
- Parse: `Slice — layers touched`, `Scope`, `Acceptance criteria`, `Slice check`, `Notes`, linked PRD path.
|
|
- If `Acceptance criteria` is missing or non-testable → exit status=needs-decision with reason "acceptance criteria not testable".
|
|
|
|
2. **Branch from latest trunk.**
|
|
`git fetch origin && git switch -c "slice/${ISSUE_NUMBER}-<short-kebab-slug>" "origin/$TRUNK_BRANCH"`
|
|
|
|
3. **Write the failing e2e test first.** Anchored at the OUTERMOST layer named in `Slice — layers touched` (HTTP endpoint, UI smoke, dashboard query, log assertion — whatever the acceptance criterion observes). Run it. Confirm it fails for the right reason. If you can't write an e2e test for this slice, that's a sign the acceptance criterion isn't really observable end-to-end → exit status=needs-decision.
|
|
|
|
4. **Implement layer by layer.** Walk the `Slice — layers touched` list. Make the minimal change at each layer to satisfy the slice — do not gold-plate, do not refactor adjacent code, do not "improve" things outside scope. Re-run the e2e test after each layer change.
|
|
|
|
5. **Run the broader test suite.** Catch regressions caused by the slice. Fix any test that was green before and is now red — do not skip or mark tests. If a test was already red before your changes, leave it (note in PR body).
|
|
|
|
6. **Outermost-layer smoke check.** The 30-second-demo check: hit the endpoint with curl, query the dashboard, tail the log, load the page. Observe what the acceptance criterion observes. Capture the output (curl response body, log snippet, query result) — you'll paste it into the PR body as evidence.
|
|
|
|
7. **Commit.** One commit per slice (or a tight series — no WIP commits, no fixup commits, no "address review" before review exists). Read the repo's recent `git log` to match commit style. Message ends with `Closes #${ISSUE_NUMBER}`.
|
|
|
|
8. **Push and open PR.**
|
|
- GitHub: `git push -u origin HEAD && gh pr create --fill`
|
|
- Gitea: `git push -u origin HEAD && tea pr create --title "..." --description "..."`
|
|
- PR body must include:
|
|
- Each acceptance criterion as a checked `- [x]` line.
|
|
- The smoke-check evidence (curl output / log snippet / screenshot path) in a fenced block.
|
|
- `Closes #${ISSUE_NUMBER}` (so the issue auto-closes on merge).
|
|
|
|
9. **Wait for CI and decide merge.**
|
|
- Poll: `gh pr checks --watch` (or `tea pr status`).
|
|
- **All green + branch protection allows direct merge** → `gh pr merge --squash --delete-branch`. Verify the merge commit landed on trunk.
|
|
- **All green + branch protection requires human review** → leave PR open. Comment `Ready for review — all acceptance criteria verified, smoke check passed.` on the issue. Exit status=shipped with the PR number.
|
|
- **Red CI** → one fix-and-push cycle. Read the failing log, fix the actual cause (do not skip the test). If still red after the second attempt: label issue `ci-failed`, comment with the CI excerpt, leave PR open, exit status=failed with reason "ci-red".
|
|
|
|
10. **Return to trunk.** `git switch $TRUNK_BRANCH && git pull --ff-only`. If the slice was merged, run the smoke check one more time against integrated trunk. If it fails there → revert the merge, label `regression`, exit status=failed with reason "regression-on-trunk".
|
|
|
|
## Boundaries
|
|
|
|
- Never force-push, never rewrite shared history, never delete branches you didn't create.
|
|
- Never bypass branch protection (`--admin`) or skip CI hooks (`--no-verify`).
|
|
- Never auto-merge a PR whose CI is red or pending.
|
|
- Never close an issue without the outermost-layer smoke check passing.
|
|
- Never modify CI/CD config, IaC, or production data unless the slice's `layers touched` explicitly names that layer.
|
|
- Never invent acceptance criteria. If they're vague, label `needs-decision`.
|
|
- Never assign issues or change milestones.
|
|
|
|
## Final output line
|
|
|
|
The shell loop greps for this exact line to determine outcome. Print it as the LAST line before exiting, on its own line, no decoration:
|
|
|
|
```
|
|
ITERATION_RESULT: status=<shipped|failed|needs-decision> issue=#<N> pr=<#N|none> reason=<short single-line reason>
|
|
```
|
|
|
|
Examples:
|
|
```
|
|
ITERATION_RESULT: status=shipped issue=#142 pr=#287 reason=merged-to-main
|
|
ITERATION_RESULT: status=shipped issue=#143 pr=#288 reason=open-for-review
|
|
ITERATION_RESULT: status=failed issue=#144 pr=#289 reason=ci-red-after-retry
|
|
ITERATION_RESULT: status=needs-decision issue=#145 pr=none reason=acceptance-criteria-not-testable
|
|
```
|