| name | aep-build |
| description | Autonomous feature implementation in a workspace session. Use when a workspace agent starts building, or when the user says "build", "implement", "execute implementation". Covers the full autonomous flow — initialize harness, implement linearly with one commit per task, review, test, create PR, handle review feedback, and merge. Runs in an isolated git worktree on a feature branch without user interaction. |
Build
Autonomous feature implementation inside an isolated git worktree on a fresh feat/<name> branch. Initialize the harness, implement tasks linearly (one commit per tasks.md row), review, test, create a PR, handle feedback, and merge — all without user interaction.
Phase numbering note: Phases 1-3 (explore, propose, review) were completed on main via /aep-design. This skill begins at Phase 0 (workspace init) and continues from Phase 4 (implementation).
Loop hygiene (G7): Each phase runs under a unified --max-turns runaway budget. Hitting the cap is not completion — treat it as "possibly unsolvable → escalate" (Human-Gate Protocol, distinct from a genuine clean finish). This keeps a stuck phase (e.g. a non-converging Phase 5 loop) from silently burning turns and reading as done.
Where this fits:
/aep-onboard → /aep-scaffold → [ /aep-design → /aep-launch → /aep-build → /aep-wrap ]
▲ you are here
Session: Workspace session, autonomous
Input: OpenSpec artifacts on disk (committed to main by /aep-design)
Output: Merged PR
Phase 0: Initialize Tracking
Before any work begins, set up the tracking infrastructure and environment. The branch is already created by /aep-launch (feat/<name>); you do not pre-create commits — implement linearly in Phase 4.
-
Read the worktree-onboarding guide at skills/agentic-development-workflow/build/references/worktree-onboarding.md.
-
Discover the OpenSpec change:
- List
openspec/changes/ to find the active change
- Read all artifacts:
proposal.md, design.md, specs/**/*.md, tasks.md
-
Create the tracking folder:
mkdir -p .dev-workflow
-
Add .dev-workflow/ to .gitignore if not already present:
grep -q '.dev-workflow' .gitignore || echo '\n# Development workflow tracking (per-workspace)\n.dev-workflow/' >> .gitignore
-
Create the progress file from the template:
cp skills/agentic-development-workflow/build/references/progress-template.md \
.dev-workflow/progress-$(git rev-parse --short HEAD).md
Fill in feature name, base commit SHA, date, and OpenSpec change name.
Mark design phases as pre-completed (they were done on main via /aep-design).
-
Read tasks.md to understand the task list:
cat openspec/changes/<change-name>/tasks.md
tasks.md is the skeleton. You will implement tasks linearly in Phase 4, committing once per task with conventional-commit messages — the resulting commit history will mirror the task list 1:1.
-
Run project setup (if a setup hook exists):
SETUP_HOOK=.claude/hooks/workspace-setup.sh
if [ -f "$SETUP_HOOK" ]; then
bash "$SETUP_HOOK"
else
echo "No workspace setup hook found at $SETUP_HOOK"
echo "Read the project README or ask the user for setup instructions."
fi
The project's setup hook handles all project-specific concerns:
- Package installation (bun/npm/pnpm/cargo/poetry/etc.)
- Dev server startup
- Port assignment → writes
.dev-workflow/ports.env
- Database migrations, seeding
- Docker/container management
.env file validation
Contract: The hook MUST write .dev-workflow/ports.env with at minimum:
WEB_PORT=<port>
SERVER_PORT=<port>
BASE_URL=http://localhost:<web-port>
SERVER_URL=http://localhost:<server-port>
If no hook exists and no README provides instructions, ask the user how to set up the project.
-
Generate sprint contracts:
Read specs/*.md, design.md, and tasks.md. For each task, generate a contract entry in .dev-workflow/contracts.md using the template at references/contract-template.md:
## Task: <task-description>
**Source spec:** <matching spec file>
### What will be built
- [specific files/components]
### Success criteria
- [extracted from matching spec]
### Verification steps
1. [concrete, executable step]
2. [what to check]
-
Generate feature verification list:
Extract the verification steps from contracts into .dev-workflow/feature-verification.json:
[
{
"task": "<task description>",
"commit_sha": null,
"verification_steps": ["step 1", "step 2", "step 3"],
"passes": false,
"evaluated_by": null,
"round": null
}
]
commit_sha starts as null and is filled in (8-char prefix) after each task is committed in Phase 4.
Rules:
- JSON format is intentional — models tamper with JSON less than Markdown
- The generator agent MUST NOT modify
verification_steps or passes — only the evaluator (or human) does
-
Generate session recovery script:
Create .dev-workflow/init.sh for resuming after context resets:
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
SETUP_HOOK=.claude/hooks/workspace-setup.sh
if [ -f "$SETUP_HOOK" ]; then
bash "$SETUP_HOOK"
else
echo "No workspace setup hook found. Check project README for setup instructions."
fi
source .dev-workflow/ports.env 2>/dev/null
echo "=== Branch & Commits ==="
echo "Branch: $(git branch --show-current)"
git log --oneline "$(git config --get aep.integration-branch 2>/dev/null || (git show-ref --verify --quiet refs/remotes/origin/develop && echo develop || echo main))"..HEAD 2>/dev/null || git log --oneline -10
echo "=== Progress ==="
grep '\[x\]' .dev-workflow/progress-*.md 2>/dev/null | tail -10
echo "=== Next Phase ==="
grep '\[ \]' .dev-workflow/progress-*.md 2>/dev/null | head -3
Make executable: chmod +x .dev-workflow/init.sh
-
Initialize inter-agent signals:
mkdir -p .dev-workflow/signals
Create .dev-workflow/signals/status.json:
{
"phase": 0,
"phase_name": "initializing",
"task_current": null,
"task_index": 0,
"task_total": 0,
"started_at": "<ISO 8601 timestamp>",
"blockers": [],
"completion_pct": 0,
"last_updated": "<ISO 8601 timestamp>",
"story_status": "in_progress",
"pr_url": null,
"cost_usd": null,
"completed_at": null,
"failure_log": null,
"blocked_on": null
}
Check for feedback from main session:
cat .dev-workflow/signals/feedback.md 2>/dev/null
See skills/agentic-development-workflow/launch/references/signals-spec.md for the full signal file specification.
-
Create the lessons file:
cat > .dev-workflow/lessons.md <<'TEMPLATE'
Module: <module>
Activity: <activity>
Date: <date>
Story: <story-id>
TEMPLATE
This file captures what the agent learns during builds — solutions discovered, errors encountered, missing docs, patterns that worked. Fill in the header fields from the OpenSpec change. Sections are populated during Phase 4 (opt-in) and Phase 9 (summary).
Update the Phase 0 checkbox in the progress file when done.
Signal update: Update .dev-workflow/signals/status.json with "phase": 0, "phase_name": "initialized", "completion_pct": 10.
Story Status Tracking (Product-Cycle Mode Only)
Standalone mode: If product-context.yaml doesn't exist, skip this entire section. Signal updates (status.json) still work — just omit story_status fields.
If this feature corresponds to a story in product-context.yaml (check if the OpenSpec change name matches a story's openspec_change field):
Build-Time Dependency Re-Verification (Phase 0)
Before starting implementation, re-verify that all dependencies are still completed:
Read product-context.yaml (READ-ONLY — workspace agents never write to this file)
Find this story by openspec_change match
For each dependency in story.dependencies:
If dependency.status != completed:
ABORT build
Signal via .dev-workflow/signals/status.json:
{ "phase": 0, "phase_name": "dependency_check_failed",
"blockers": ["<dep_id> is not completed"] }
Stop and wait for main session to investigate
Also check dispatched_at_epoch vs current dispatch_epoch. If the epoch advanced since dispatch, re-read the YAML to check for architecture amendments.
Why: A dependency could be rolled back after dispatch (e.g., PR reverted). This defense-in-depth catches issues that dispatch-time checks missed.
Status Updates via Signals
CONCURRENCY PROTOCOL: Only the main session writes to product-context.yaml. Workspace agents report status through .dev-workflow/signals/status.json. The main session (via /aep-wrap, /aep-dispatch) reads signals and updates the YAML.
All story status updates flow through the signal file, NOT direct YAML writes:
- Phase 0 start: Confirm story status is
in_progress in the YAML (read-only check)
- Phase 10 (PR created): Update
status.json with story_status: "in_review" and pr_url
- Phase 12 merge: Update
status.json with story_status: "completed", completed_at, pr_url, cost_usd
- On failure (escalation): Update
status.json with story_status: "failed" and failure_log (structured record: error_class, approach_summary, unexplored_alternatives)
/aep-wrap (running on main) reads these signal fields and writes the final status to product-context.yaml.
If product-context.yaml doesn't exist, skip this tracking (standalone feature mode).
Phase 4: OpenSpec Apply
Implement each task in tasks.md linearly. After each task, commit with a conventional-commit message and record the resulting commit SHA in feature-verification.json:
git status
git diff
git add -A
git commit -m "feat(<scope>): <task description>"
SHA=$(git rev-parse --short HEAD)
Invoke the apply skill for guidance on implementing each task:
/opsx:apply
The agent works one task at a time. Each task = one commit. The branch's commit history mirrors tasks.md 1:1, which makes per-task code review on the PR straightforward.
UI-facing stories — obey the Object Map. If the dispatched context package
includes an Object Map slice (objects, attributes, relationships, CTAs, screens),
treat it as binding structure: build the listed screens object-first (noun→verb —
object collection/detail, the given CTA placements), use the specified core
attributes, and do not introduce objects/screens outside the slice or collapse
a flow into a step-by-step wizard unless the slice marks it task_oriented.
Visual look (calibration/visual-design.yaml), copy voice (copy-tone), and
journey/transition (ux-flow) still govern taste — the Object Map governs object
structure and CTA grammar.
Update the progress file checkbox for each completed task, and mark the Phase 4 checkbox when all tasks are done.
If you need to amend a just-committed task (e.g., you forgot a file), use git commit --amend --no-edit before moving on. Once you've committed the next task, leave prior commits alone — fixes belong as a follow-up commit, not a rewrite.
Lesson capture (opt-in per task): After completing each task, if you encountered something noteworthy — a non-obvious solution, an error that took multiple attempts, missing documentation — append to .dev-workflow/lessons.md under the appropriate section (## Solutions, ## Errors, or ## Missing). Use a ### <title> sub-heading with a brief explanation. This is not mandatory for every task — only write when something is genuinely worth passing on to the next agent.
Signal update: Update .dev-workflow/signals/status.json with "phase": 4 at start, update task_current, task_index, task_total as tasks progress, and completion_pct proportionally.
Phase 5: Code Review & Verification
After implementation, verify the code before moving to testing. This phase uses the generator/evaluator pattern — read the shared utility for full details:
- Scoring framework:
.claude/skills/aep-gen-eval/references/scoring-framework.md (dimensions, thresholds, presets)
- Agent contracts:
.claude/skills/aep-gen-eval/references/agent-contracts.md (role separation, prompt templates)
- Eval protocol:
.claude/skills/aep-gen-eval/references/eval-protocol.md (request/response format, verification JSON, convergence rules)
Completeness check (always done by generator)
- Re-read the proposal (including any design review adjustments)
- Walk through each task's commit, reviewing with
git show <commit-sha> against its task description
- Check
.dev-workflow/contracts.md — verify each task's success criteria are met
- If any task is incomplete, add a follow-up commit (
feat(<scope>): complete <task> or fix(<scope>): <issue>) and loop back to Phase 4. Do not rewrite prior commits.
Quality review
With separate evaluator (full mode):
If .dev-workflow/evaluator-criteria.md exists (written during /aep-launch), spawn an evaluator via executor.spawn_evaluator(). The generator orchestrates the entire evaluation loop — no manual intervention needed. The spawn mechanism tracks the active executor mode (read .claude/skills/aep-executor/references/backends.md):
| Generator mode | Evaluator spawn (executor.spawn_evaluator) | eval-protocol mechanism |
|---|
| native-bg-subagent / claude-bg | foreground Task subagent in the generator's own session — inherits the worktree cwd; the evaluator prompt is the spawn prompt | Context B mechanism, worktree-bound — not its read-only /aep-validate use |
| codex-subagent / codex-exec | codex exec --cd <abs worktree> with the aep-evaluator role — enforced cwd, bounded one-shot | Context C mechanism, in-host headless — not API/SDK CI |
| legacy (tmux) | tmux split-window — evaluator in a bottom pane | Context A (tmux split) |
| workflow | the workflow's verify stage (worktree-isolated) | Context C mechanism, in-host — not API/SDK CI |
Always worktree-bound. Whatever the mode, the evaluator runs against this
workspace's worktree (files + git state), per executor.spawn_evaluator().
The Context labels name the spawn mechanism, not the read-only/CI use
cases those contexts describe in eval-protocol — a Task-subagent evaluator
is not a main-session read-only reviewer, and a codex exec evaluator is
not an API/SDK CI job. It is worktree-bound review.
For the native modes the evaluator prompt is delivered at spawn time — there
is no readiness wait, no separate send step, and no pane to kill; the agent/exec
returns when eval-response-<N>.md is written. The legacy tmux recipe is shown
below for pinned-tmux workspaces; the request/response signal files and
convergence rules are identical across modes.
Why tmux splits, not cmux splits (legacy): The generator runs inside tmux but was not spawned by cmux,
so it cannot use cmux socket commands. Use tmux split-window instead — under cmux the surface attached
to the tmux session will display both panes automatically.
Evaluation round
For each round N (starting at 1, max 5), the generator's response to a FAIL escalates along the change-strategy recovery ladder (.claude/skills/aep-gen-eval/references/recovery-ladder.md) rather than retrying the same way every round:
| Eval round | Rung | Generator move |
|---|
| 1–2 | Same fix | Same generator fixes the FAIL items in place (current default). |
| 3 | Re-ground | Same generator re-reads the FULL spec + design + contracts from scratch, then re-attempts. |
| 4 | Different approach | Spawn a fresh native-bg-subagent generator told "the previous approach failed on X; take a different design path" — not anchored on the stuck solution (it inherits the existing worktree). |
| 5 | Decompose | Split the story into sub-tasks; attempt the smallest viable slice and surface the proposed split. |
| after 5 | Human gate | Ladder exhausted → escalate with type eval_not_converging. |
Track the rung with eval_round + recovery_rung in status.json (see the ladder's State Tracking). Generator≠evaluator separation holds — the evaluator only scores; re-grounding, a fresh generator, and decomposition are all generator-side moves. Skip the ladder and escalate immediately on a hard-failure / security FAIL (auth-model gap, data-exposure risk), a spec contradiction, or a missing external dependency — these need human judgment, not a different approach. See the ladder file for full rung rationale and the rung-4 fresh-generator spawn contract (native-bg-subagent + post-spawn liveness probe).
For each round N (starting at 1, max 5):
-
Write eval-request — create .dev-workflow/signals/eval-request.md per the format in eval-protocol.md (Signal Files section).
-
Compose the evaluator prompt from agent-contracts.md (Evaluator Prompt — Code Quality template). Customize with the workspace paths:
EVAL_PROMPT = <evaluator prompt from agent-contracts.md, customized with:
criteria_file=.dev-workflow/evaluator-criteria.md
eval_request_file=.dev-workflow/signals/eval-request.md
spec_directory=openspec/changes/<change-name>/
contracts_file=.dev-workflow/contracts.md
verification_file=.dev-workflow/feature-verification.json
eval_response_file=.dev-workflow/signals/eval-response-<N>.md
>
-
Spawn the evaluator (mode-dispatched, prompt = spawn prompt):
-
native-bg-subagent / claude-bg: spawn a foreground Task subagent with
EVAL_PROMPT. It inherits the worktree cwd and returns when done — no
sleep, no send step, no teardown.
-
codex-subagent / codex-exec:
codex exec --cd "$(pwd)" --dangerously-bypass-approvals-and-sandbox \
"$EVAL_PROMPT" < /dev/null
(With the aep-evaluator role committed in .codex/agents/, prefer
spawn_agent(agent_type: "aep-evaluator", ...) from a living thread.)
-
legacy (pinned tmux):
tmux split-window -v -c "$(pwd)" "${EXECUTOR:-claude --dangerously-skip-permissions}"
tmux select-pane -t :.0
sleep 10
tmux send-keys -t :.1 -l -- "$EVAL_PROMPT"
tmux send-keys -t :.1 Enter
-
Confirm the response exists (the native spawns return on completion; the
legacy pane needs a poll):
while [ ! -f .dev-workflow/signals/eval-response-<N>.md ]; do sleep 15; done
-
Read the response. Legacy only: close the evaluator pane
(tmux kill-pane -t :.1). Native evaluators have already exited.
-
Fix FAIL items per the recovery rung — add follow-up commits addressing each FAIL item, then loop back to step 1 with round N+1. On rounds 1–2 fix in place; on round 3 re-ground (re-read the full spec/design/contracts from scratch first); on round 4 spawn a fresh native-bg-subagent generator with a different approach; on round 5 decompose to the smallest viable slice. Do not rewrite history; the PR review should see the fix as new commits on top. (Hard-failure/security FAILs skip the ladder and escalate immediately — see the rung table above.)
-
Max 5 rounds — once the ladder is exhausted (not converging after round 5), escalate to human via the human-gate
protocol (see below) with type eval_not_converging, recording the ladder history. See the convergence rules in eval-protocol.md and recovery-ladder.md.
The evaluator also updates .dev-workflow/feature-verification.json with pass/fail results per the field ownership rules in eval-protocol.md.
Without evaluator (light mode):
Self-review with awareness of its limitations:
- Correctness — Logic errors, off-by-one bugs, missing edge cases?
- Security — Input validation, auth checks, SQL parameterization?
- Performance — N+1 queries, missing indexes, unbounded loops?
- Conventions — Naming, file structure, error handling, imports?
Note: Self-review tends to be lenient. If using light mode, be extra critical and walk through feature-verification.json steps manually.
Document findings in .dev-workflow/code-review-<feature>.md. Fix any issues found.
Update the Phase 5 checkbox in the progress file when complete.
Signal update: Update .dev-workflow/signals/status.json with "phase": 5, "phase_name": "code-review".
The Human-Gate Protocol (any phase)
When you hit a decision only the human can make — design ambiguity the spec
doesn't resolve, eval non-convergence after max rounds, a product judgment call —
do not guess and do not silently stall. Raise a gate:
-
Record it (always): append to .dev-workflow/signals/needs-human.md:
## <ISO8601> — Phase <N>
**Question:** <the decision needed, with the options you considered>
**Context:** <why you can't decide autonomously>
and set "blocked_on": "human" in status.json.
-
Raise it on your mode's transport (how you were launched tells you the
mode — see the Human-Gate Protocol in aep-executor/references/backends.md).
The answer always comes back through the main agent (hub-and-spoke) —
you never need the human to visit your surface:
- codex-subagent: ask the parent thread (approvals surface natively)
- legacy: the file is the transport — the orchestrator detects it and
relays the answer via nudge
- native-bg-subagent / claude-bg / codex-exec / workflow / headless: gate-and-park — no
push channel reaches you, so after recording the gate: commit WIP (or
leave the tree clean), update
status.json, and end your run cleanly
(workflow agents: return a structured gated result carrying the
question). The orchestrator will resume a worker into this same worktree
with the human's answer — your state is in the worktree +
.dev-workflow/, so nothing is lost.
-
Block-in-place modes only: continue what you can that doesn't depend on
the answer; otherwise wait, re-checking feedback.md.
-
On answer (delivered by push, or in your resume prompt after a park):
append resolved: <summary> under your entry, clear blocked_on, and
proceed.
Phase 6: Browser Testing (Dogfood)
Light mode: Skip this phase. Otherwise do not skip just because agent-browser is absent — pick a host-aware method and degrade (see below).
Run the layer's journey. If the project has the e2e-test skill, pick the matching BDD journey from
skills/e2e-test/journeys/ and drive it (intent, not a click script). The journey's target:
(web/mobile/desktop) plus skills/e2e-test/tool-selection.md resolve which automation tool to use — that
file is the project-local projection of e2e_tool(target_type). Verify state per each Verify line.
Pick the tool, host-aware. Call e2e_tool(target_type) (web wrapper: dogfood_method()) from
.claude/skills/aep-executor/references/dogfood-validation.md to select the right native tool for
this target/host/mode:
- Claude Code (web) —
/agent-browser:dogfood if agent_browser_healthy(), else webwright; otherwise degrade (non-UI changes → API/curl checks; UI changes → human-eval) rather than skipping.
- Codex (web) — native in-app browser + computer-use (codex-subagent desktop), else a Playwright script (codex-exec headless), falling back to the agent-browser CLI, then API checks.
- mobile / desktop —
agent-device (mobile) or codex computer-use / agent-browser-Electron (desktop), per the journey's target:.
Resolve the target from skills/e2e-test/policy.md (dogfood_target) — don't assume local:
none → skip the journey dogfood (Tier-2 N/A for this project); prove the layer's criteria via
Tier-1 / Tier-3 and jump to "Close the coverage gap" below.
local → source .dev-workflow/ports.env and use $BASE_URL (target_url(local)).
deployed:<url> → use that URL (e.g. a Cloudflare preview/prod), and seed that same target
(SERVER_URL=<url> bash skills/e2e-test/scripts/seed.sh) — not local. If the policy's journey_timing
is post-deploy, the journey runs after merge/deploy (at the /aep-wrap layer gate), so Phase 6 here
may run only Tier-1/Tier-3 and leave the journey for that post-deploy gate.
source .dev-workflow/ports.env
If the selected method is /agent-browser:dogfood, run it against $BASE_URL:
/agent-browser:dogfood
Whatever the method, emit the unified severity/category/repro report format (see dogfood-validation.md → Unified report format) so the downstream classifier stays host-agnostic. Document results in .dev-workflow/dogfood-<feature>.md — write the report file, not just chat output: that path is the ingestion contract (dogfood-validation.md → On issue), so /aep-watch's dogfood_report source can auto-file bug/refinement findings. Findings left only in chat are a dead end.
Close the coverage gap (auto-remediate). After running the journey, compute the layer's coverage
matrix: list this layer's acceptance criteria (from stories[].acceptance_criteria in
product-context.yaml) and map each to the journey scenario Verify / scripted case / API check that
proves it. For every uncovered criterion, author the missing test now — a Tier-2 journey scenario
(default), a Tier-1 scripted case where the behavior is deterministic, or a Tier-3 API check for
backend/async state — run it, and repeat until coverage.criteria_covered == coverage.criteria_total. A
criterion you deliberately defer gets a WAIVER: <reason> line, not silence. This is what makes the layer
covered rather than touched — /aep-wrap refuses to flip the gate to passed while criteria are
silently uncovered. (Tier applicability is per project type — a CLI/library layer may be all Tier-1; see
the aep-e2e-skill-scaffolding references/three-tier-model.md.)
Signal update: Update .dev-workflow/signals/status.json with "phase": 6, "phase_name": "dogfood-testing".
Phase 7: Codify the Journey + Record the Layer Gate
Skip if the project has no e2e-test skill. Light mode: Skip this phase.
Codify what Phase 6 exercised as a durable BDD journey (the regression artifact), not a one-off bash
script. In skills/e2e-test/journeys/:
- Add/extend the journey for this feature's layer (copy the template in
journeys/README.md); set its
front-matter target: and layer:.
- Each
Then gets a concrete Verify line (API response / state check) — "looks done" is not a pass.
- Keep it tool-agnostic;
tool-selection.md resolves the tool. API-level assertions can use $BASE_URL /
$SERVER_URL from .dev-workflow/ports.env (never hardcoded ports).
Record the layer gate. Write the evidence to docs/layer-gates/<layer>.md (the generated e2e-test
skill ships a layer-gate-evidence template): the two coverage matrices — acceptance traceability
(criterion → proving test → Verify → PASS/FAIL) and scripted-coverage (case → asserts → PASS/FAIL) —
plus the manual checklist, screenshots / API JSON, and any WAIVER: lines. Update layer_gates[N].coverage
and evidence.{scripted,journeys,matrix} in product-context.yaml. The two-phase flip (scripted_passed
→ passed) happens at /aep-wrap once all applicable tiers are green and coverage is complete — that is
what lets /aep-dispatch advance to the next layer.
Phase 8: Review Results
Light mode: Skip this phase.
- Source
.dev-workflow/ports.env for correct ports
- Run the project's framework tests (Tier 1), replay the layer's journey (Tier 2) and prior-layer
journeys (regression), plus any applicable Tier-3 API drivers — verify they pass and that
coverage.criteria_covered == criteria_total (or remaining gaps carry a WAIVER:)
- Present to the user (or note in progress file):
- Code review from Phase 5
- Dogfood report from Phase 6 (if run)
- Journey result + layer-gate evidence + coverage summary (
criteria_covered / criteria_total) from Phase 7 (if run)
- If tests fail, loop back to the appropriate phase
Signal update: Update .dev-workflow/signals/status.json with "phase": 8, "phase_name": "review-results".
Phase 9: Cleanup & Publish
Note: Do NOT run /opsx:archive here. Archive runs on the integration branch after merge (via /aep-wrap).
0. Write lesson summary
Before publishing, write a final ## Summary for Next Agent section in .dev-workflow/lessons.md (1-3 sentences): what would you tell the next agent building in this module? If the lessons file has no entries beyond the template header, write the summary anyway — even "straightforward implementation, no surprises" is useful signal.
1. Review the commit history
git log --oneline "$(git config --get aep.integration-branch 2>/dev/null || (git show-ref --verify --quiet refs/remotes/origin/develop && echo develop || echo main))"..HEAD
The history should be a clean linear sequence: one commit per tasks.md task, optionally followed by review-fix commits. The PR will be squash-merged on merge, so per-commit hygiene matters for review readability, not the integration branch's history.
2. Rebase onto the latest integration branch
BASE=$(git config --get aep.integration-branch 2>/dev/null || true)
[ -z "$BASE" ] && { git show-ref --verify --quiet refs/heads/develop \
|| git show-ref --verify --quiet refs/remotes/origin/develop; } && BASE=develop
BASE=${BASE:-main}
git fetch origin
git rebase origin/"$BASE"
If conflicts arise, resolve them in the working tree, then git add <files> and git rebase --continue. Abort with git rebase --abort if conflicts are too tangled — surface to the orchestrator via the signal file.
3. Push the feature branch
git push -u origin feat/<name>
The -u (--set-upstream) flag is needed only on the first push; subsequent pushes can drop it.
Phase 10: Create PR / MR
Auto-detect the platform and target the integration branch:
BASE=$(git config --get aep.integration-branch 2>/dev/null || true)
[ -z "$BASE" ] && { git show-ref --verify --quiet refs/heads/develop \
|| git show-ref --verify --quiet refs/remotes/origin/develop; } && BASE=develop
BASE=${BASE:-main}
REMOTE_URL=$(git remote get-url origin)
case "$REMOTE_URL" in
*github.com*) gh pr create --title "<title>" --body "<body>" --base "$BASE" ;;
*gitlab*) glab mr create --title "<title>" --description "<body>" --target-branch "$BASE" ;;
*)
echo "Unrecognized remote host: $REMOTE_URL — create the PR/MR manually with base \"$BASE\"." >&2
exit 1
;;
esac
CRITICAL — always specify --base "$BASE" (GitHub) or --target-branch "$BASE" (GitLab), resolving $BASE in the same block as above. Workspace sessions run from a worktree whose checked-out branch is feat/<name>, not the integration branch. Without an explicit base, gh pr create may infer the wrong base from the most recent merged branch — causing the PR to target a stale base and never land on the integration branch even after merge. In two-branch mode an inferred base could be production main, which AEP must never merge feature work into directly.
Include in the PR/MR body:
- Summary of changes (from proposal)
- Test coverage notes
- Link to manual test plan (if created)
Phase 11: PR Review & CI Feedback Loop
Monitor for CI and review feedback.
Triage review comments
Fix — correctness issues, CI failures, convention violations, security.
Acknowledge but skip — style preferences, over-engineering, cosmetic suggestions.
Discuss — architectural suggestions that expand scope, conflicting comments.
Fix loop
- Triage all comments
- Create fix plan at
.dev-workflow/pr-fix-plan-<round>.md
- Reply to skipped/discussed comments
- Add follow-up commits for each fix:
git add -A
git commit -m "fix(<scope>): address review feedback on <topic>"
Squash-merge at PR-merge time keeps the integration branch's history clean, so per-commit hygiene on the feature branch only matters for reviewer readability.
- Re-run tests
- Re-push:
git push origin feat/<name>
- Repeat until CI green and reviews resolved
Phase 11.5: Human Evaluation & Iteration
After PR review fixes are resolved, the human tester evaluates the feature — typically by running the app from the workspace. If they find minor issues (UX tweaks, missing edge cases, behavior that doesn't match intent), this phase handles the iteration loop.
If no issues found: Skip this phase and proceed to Phase 12.
Iteration round
-
Document findings — Write to .dev-workflow/human-eval-round-<N>.md:
- What was found (description, steps to reproduce)
- Severity (minor / moderate)
- Category (UX, logic, edge case, visual)
-
Add a follow-up commit per fix:
git add -A
git commit -m "fix(<scope>): <human-eval finding>"
-
Align OpenSpec change — Update openspec/changes/<name>/ artifacts:
- Add completed tasks to
tasks.md for the work just done
- Update
specs/ if behavior changed
- Update
design.md only if approach details shifted
- Keep
proposal.md scope as-is (direction unchanged)
-
Re-test — Re-run Phase 5 (code review) and Phase 6 (dogfood) on the changed areas.
-
Push — Update the PR:
git push origin feat/<name>
-
Repeat — If the human tester finds more issues, start a new round.
Signal update: Create .dev-workflow/signals/ready-for-review.flag when ready for human evaluation. Update status.json with "phase": 11.5, "phase_name": "human-evaluation". This is a human gate — also follow the Human-Gate Protocol (needs-human.md + blocked_on: "human" + your mode's transport) so the orchestrator surfaces it instead of counting you as stuck.
Phase 12: Pre-merge Checks & Merge
- Up-to-date with the integration branch:
BASE=$(git config --get aep.integration-branch 2>/dev/null || true)
[ -z "$BASE" ] && { git show-ref --verify --quiet refs/heads/develop \
|| git show-ref --verify --quiet refs/remotes/origin/develop; } && BASE=develop
BASE=${BASE:-main}
git fetch origin && git rebase origin/"$BASE" && git push --force-with-lease origin feat/<name>
- CI checks green
- No unresolved review comments
- E2E tests passed (if applicable)
- Present final status summary
- Merge decision:
- Interactive mode (user present in session): Ask user for confirmation before merging
- Autopilot mode (launched via
/aep-launch into .feature-workspaces/): Merge immediately when all pre-merge checks pass — do not wait for user confirmation. The autopilot orchestrator monitors via signals, not interactive prompts.
- Detection: If your working directory is inside
.feature-workspaces/, you are in autopilot mode.
Merge:
REMOTE_URL=$(git remote get-url origin)
- GitHub:
gh pr merge <number> --squash --delete-branch
- GitLab:
glab mr merge <number> --squash --remove-source-branch
Why --force-with-lease: rebasing rewrites the feature branch's commit SHAs. --force-with-lease forces the push only if the remote hasn't advanced since you last fetched — protecting concurrent collaborators while still letting you push the rebased history.
Guardrails
- Never skip the tracking initialization (Phase 0). Every workflow needs a progress file and contracts.
- Never run
/opsx:archive from a workspace — it writes to openspec/specs/ and causes conflicts. Archive always runs on the integration branch via /aep-wrap.
- Never write to
product-context.yaml from a workspace — only the main session writes to the YAML. Report all status through .dev-workflow/signals/status.json. This is the concurrency protocol.
- Always confirm with the user before creating PRs, merging, or pushing to shared branches.
- The
.dev-workflow/ folder is ephemeral — gitignored, local to each workspace.
- Resume support: If returning to an in-progress workflow, run
.dev-workflow/init.sh if it exists, then read the progress file.
- Phase skipping: Users may ask to skip phases. Update progress file accordingly.
- One commit per task in Phase 4 — the PR review reads cleanly when the commit list mirrors
tasks.md. Don't bundle multiple tasks into one commit; don't split one task across multiple commits.
- Don't rewrite history mid-stream — once you've moved past a committed task, fixes go in as new commits, not amends or rebases. Squash-merge at PR-merge handles the cleanup.
- Use
git push --force-with-lease, never --force — the lease variant fails safely if someone else pushed to the same branch since your last fetch.
- Signal updates are required — update
.dev-workflow/signals/status.json at the start and end of every phase. Check .dev-workflow/signals/feedback.md for main session feedback at phase boundaries.
- Generator must not modify verification data — never modify
verification_steps or passes in feature-verification.json. Only commit_sha is generator-writable. The evaluator or human updates passes / evaluated_by / round.
- Evaluator loop max 5 rounds, climbing the recovery ladder — the generator escalates its strategy per round (same fix → re-ground → fresh generator → decompose) per
recovery-ladder.md; only once the ladder is exhausted (after round 5) does it escalate to human as eval_not_converging. Hard-failure/security FAILs skip the ladder and escalate on the first occurrence.
- Raise human gates, don't guess — decisions only the human can make go through the Human-Gate Protocol (
needs-human.md + blocked_on: "human" + your mode's transport). Silent stalls read as stuck; unrecorded guesses read as scope drift.
Next Step
After merge, signal the main session to run:
/aep-wrap