Skip to main content

Agent workflow — spec to merged release

This is the canonical sequence an AI agent follows to ship a feature. Every step has a source-of-truth file you should cite, not paraphrase. Worked-example references throughout point at recent shipped PRs.

1. Spec first (when the change is non-trivial)

Anything ≥ 3 file changes or any feat: PR needs a spec under .ai/specs/SPEC-<key>-<slug>.md (or .kiro/specs/<slug>/ for the older Kiro convention). The PR-requirements gate (scripts/check-pr-requirements.ts) blocks merge for feat: PRs that don't add or modify a spec file. Escape hatch: [skip spec] in the PR title — only when the rule genuinely doesn't apply.

The spec is shorter than the implementation: it captures the decisions the implementation will then encode. Real-world examples shipped late April 2026: PT-209 (promote-to-task), PT-194 (feedback attachments) — each with a 100-line spec that drove a 1,000-line implementation.

Spec format and AC anchors are CI-enforced (INF-107 + INF-110). The required H2 sections are Problem, Decision, Scope, Acceptance criteria, Implementation sketch, and every numbered AC ends with one of:

  • → path/to/file.ts:symbol — code symbol must appear in the diff
  • → path/to/file.md — non-code file must appear in the diff
  • → <!-- ui: <route> --> — gated on Playwright smoke
  • → <!-- deferred: <reason> --> — explicit waiver

The full format reference is in the spec-authoring skill; the CI gate that validates anchors is on the CI gates page.

For non-trivial specs, invoke the spec-author subagent to draft and the spec-reviewer subagent to audit before implementation begins.

See AGENTS.md for the binding rule.

2. Worktree from origin/develop — never from a stale local develop

Cut a fresh worktree off origin/develop, not a fresh branch on your current checkout. This keeps your main checkout usable for the next task, avoids stash/checkout churn, and prevents two concurrent agent sessions from fighting over the same working directory.

git fetch origin
git worktree add -b feat/<key>-<slug> ../constellation-<slug> origin/develop
cd ../constellation-<slug>
# If you have a .mcp.json (Claude Code / Cursor / any MCP-using agent), mirror it; otherwise skip:
test -f ../constellation/.mcp.json && cp ../constellation/.mcp.json .
# First-time MCP setup: see tools/mcp-server/README.md
npm install

Always cut from origin/develop after a fresh fetch. Cutting from a stale local develop is one of the top three sources of merge conflicts on the develop branch. Feature branches stay short-lived (< 3 days) and rebase on develop frequently:

git rebase origin/develop

Larger features get broken into incremental PRs behind feature flags rather than long-lived branches.

When the PR merges, update your main checkout's local develop first (otherwise git branch -d fails with "not fully merged" because the merge commit isn't reachable from a stale local develop):

cd ../constellation
git fetch origin
git checkout develop && git pull --ff-only origin develop
git worktree remove ../constellation-<slug>
git branch -d feat/<key>-<slug>
git worktree list # verify removed

See CLAUDE.md "Task lifecycle" for the full PT → spec → worktree → PR → cleanup flow and the ## Links block convention on PT tasks.

3. Implement, test locally, prepare to push

Before every git push, run the 4 mandatory pre-push gates. If any fail, fix locally — don't push and "let CI tell me what's wrong".

For UI / frontend changes, start the dev server and exercise the feature in a browser. Type-checking and tests verify code correctness, not feature correctness — say so explicitly if you can't test in a browser.

4. Add a changeset fragment

Every PR with user-visible impact must add .changeset/<short-kebab-summary>.md:

---
type: Added # Added | Changed | Fixed | Infrastructure | Security
pr: <PR-number> # optional; auto-detected from git history if omitted
---

**Bold feature title** — Free-form markdown describing what changed and why.

The release script (scripts/render-changelog.ts) collects all fragments at release time and writes them into CHANGELOG.md as a new dated section. Authors no longer edit CHANGELOG.md directly — that's what caused merge conflicts on every parallel PR before the changeset migration.

CI gate scripts/check-changeset.ts enforces this. Escape hatch: [skip changeset] in the PR title — only for genuinely no-impact PRs (typo fixes, internal refactors with no behaviour change, CI workflow tweaks).

4b. Self-review your own diff (INF-114)

Before running gh pr create, invoke the post-impl-reviewer subagent on your own diff. It maps each acceptance criterion from the spec to a file:symbol in the diff, audits the change against AGENTS.md hard rules, and emits a verdict bar (blocked | needs revision | merge-ready).

Embed the verdict bar in the PR body inside a collapsible block:

<details>
<summary>Self-review</summary>

## Post-impl review — <subject>

**Scope:** <surfaces touched>
**Verdict:** merge-ready
**Reason:** All ACs traced and CI green.

### Blockers (must fix)

- (none)

### Risks (should fix before merge)

- (none)

### Suggestions (non-blocking)

- (none)

### Checklist

| Area | Status |
| ------------ | ------ |
| AC coverage | pass |
| Tenant scope | pass |
| Tests added | pass |

</details>

The local hook .claude/hooks/pre-pr-self-review.sh intercepts gh pr create and refuses the call when the block is missing or the verdict is blocked. Escape hatch: [skip self-review] in PR title or body (own line, with a written reason) — only for true no-impact diffs.

All six module-developer subagents include this step. See the Subagents page for the full hook semantics.

5. Open the PR with the right title format

Recommended format: <type>(<scope>): <summary> [<TASK-KEY>]. Examples:

  • feat(catalog): bulk import [CAT-12]
  • fix(directory): N+1 query in list endpoint [DIR-5]
  • docs(infra): align CLI reference with actual pt CLI [INF-43]

The bracket form [KEY-N] in the PR title is what auto-closes the linked PT task on merge. The webhook regex is \[([A-Z][A-Z0-9_]{0,9}-\d+)\] and it matches anywhere — so [INF-30] in the body or in markdown link text like [INF-30](url) will also close ticket INF-30 on merge. Use plain INF-30 (no brackets) for non-closing references. This is the bracket-link footgun and it has caused at least 6 false ticket closures on Constellation.

6. Copilot review loop — every thread either fixed-and-resolved or rejected-with-reason

After pushing the branch, GitHub Copilot reviews automatically. Iterate:

  1. Read every comment: gh api repos/B2B-Online/constellation/pulls/<N>/comments
  2. Accept the suggestion → fix in a follow-up commit, then resolve the thread.
  3. Reject the suggestion → reply with a short reason (false positive, out of scope, intentional trade-off), then resolve the thread.
  4. After every push, the .github/workflows/copilot-review.yml workflow re-requests Copilot for same-repo PRs. Wait for the re-review before merging — stale Copilot feedback from an earlier commit doesn't count.
  5. Branch protection on develop requires conversation resolution before merge — silently resolving threads without a reply is enough mechanically, but the convention is to reply on rejected ones.

The Copilot loop on a real recent PR sometimes goes 20+ rounds — that's normal, not a sign something is wrong. Recent reference: PT-209 promote-to-task shipped late April 2026 with multiple Copilot rounds resolving naming, schema, and accessibility findings.

7. CI must be green on all four jobs

JobWhat it runs
Quality Gatesformat · audit coverage · changeset · PR requirements · AC traceability (INF-110) · docs-sync warn (INF-111) · openapi · scripts tests
Lint · TypecheckESLint + TypeScript
Build · Testfull build, test suite, migration verification, route wrapping check, wiki MCP boundary, E2E smoke
SonarCloudquality gate — high + medium bugs/vulns/smells

Fix and re-push until all four are green and SonarCloud's quality gate passes. The complete list of gates with bypass markers is on the CI gates page.

8. Merge the PR

PRs to develop merge via the "Merge pull request" button on the PR (or gh pr merge <N> --merge). The repo enforces merge commit only — squash and rebase are disabled at the repo level. Branch protection requires CI green before the merge button enables, so keep your branch rebased on origin/develop — without a merge queue, stale branches surface conflicts at merge time instead of on your branch.

For administrative overrides (e.g. bypassing a flaky required check), gh pr merge <n> --merge --admin works.

Release / hotfix PRs targeting main use the same "Merge pull request" button and the same merge-commit policy.

After merge, the GitHub webhook auto-closes the linked PT task (if the bracket-key form was in the title or body) and the changeset fragment sits on develop waiting for the next release.

9. Cutting a release

Releases are batched — multiple feature merges land on develop, then one release branch promotes them all to main and to production.

git fetch origin
git checkout -b release/$(date -u +%Y-%m-%d) origin/develop
npx tsx scripts/render-changelog.ts --title "Headline summary"

The release script auto-derives the version label from today's UTC date with a letter-suffix collision check — first release of the day is 2026-04-27, second is 2026-04-27b, third is 2026-04-27c, etc. Do not pass an explicit date — the script will reject one that doesn't match today (this prevents the date-as-counter drift that produced duplicate ## [2026-04-27] headings on 2026-04-25/26 before the auto-derive flow shipped). For backfilling a historical release, use --force-version.

Before opening the release PR:

  1. Apply outstanding migrations to BOTH staging and production via the Supabase dashboard SQL editor or the Supabase MCP (apply_migration / execute_sql). The Verify Release Migrations CI gate blocks the merge if either DB is missing a migration that's on the branch.
  2. Commit only the rendered changelog + critical bugfixes — no new features.
  3. Open a PR from release/*main. Use merge commit (not squash) to preserve history.
  4. Once merged, back-merge maindevelop immediately so the release label and any on-release fixes flow back.

The full release-flow doc with the migration-gate detail is at Deployment.

10. After release: pick up the next ticket

npx pt workspace returns the current user, the dogfood programme, and the top 3 IN_PROGRESS tasks per project. Run it at the start of every agent session — that's the cheapest way to orient before reading the dev plan or scrolling through PT.

For "what should I work on next?" / "is this duplicating something?" / "plan this week", invoke the pt-coordinator subagent — it does a programme-wide read pass and returns a ranked recommendation.

See also

  • Overview — ecosystem map + decision tree.
  • Tools — the pt CLI, MCP server, and the pt-coordinator subagent in detail.
  • Root CLAUDE.md — pre-push checklist, commit conventions, full release-flow text.
  • Root AGENTS.md — invariants, task-router table, project status.
  • Architecture rules — every binding rule with a stable slug-anchor.