SuperAPI AI Directory

aep-build

Agents & models

View repository

Updated 10-16 00:28

About this skill

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: Phases 1–3 (explore, propose, review) were completed on the integration branch via /aep-design. This skill begins at Phase 0 (workspace init) and continues from Phase 4 (implementation).

Loop hygiene: 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 clean finish, so a stuck phase (a non-converging Phase 5 loop, say) does not read 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 the integration branch by /aep-design) Output: Merged PR


Phase 0: Initialize Tracking

Set up the tracking infrastructure and environment. The branch already exists (/aep-launch created feat/<name>). Run these steps in order; each ends in a checkable postcondition.

Step 0 — Worktree guard, before anything else. Phases 4–12 run inside this story's feature worktree, verified here rather than assumed. On Codex codex-subagent the worktree binding is a soft prompt-contract (spawn_agent has no cwd parameter), so a worker can start in the main checkout and create feat/<name> there, which breaks Phase 12 autopilot detection. This guard closes that:

WS="<name>"   # your story/change name from the bootstrap prompt — your worktree is
#             .feature-workspaces/<name> on branch feat/<name>, matching the active
#             OpenSpec change (ls openspec/changes/). If WS is still the literal
#             "<name>", resolve it from the prompt/change BEFORE continuing.
TOP=$(git rev-parse --show-toplevel)
BRANCH=$(git branch --show-current)

if [[ "$TOP" != *"/.feature-workspaces/$WS" || "$BRANCH" != "feat/$WS" ]]; then
  echo "GUARD: not in worktree .feature-workspaces/$WS on feat/$WS (top=$TOP branch=$BRANCH)"
  # /aep-launch OWNS worktree creation; the guard only ENTERS the worktree it
  # already made (cd, never `git switch` / `git worktree add -b`).
  ROOT=$(git worktree list --porcelain | sed -n '1s/^worktree //p')   # main worktree root
  if [ -n "$ROOT" ] && [ -d "$ROOT/.feature-workspaces/$WS" ]; then
    cd "$ROOT/.feature-workspaces/$WS"          # launch made it; just enter it
  else
    # No worktree for $WS: escalate (Human-Gate Protocol / needs-human.md)
    # rather than improvising one — "run /aep-launch first, or it misfired."
    echo "ESCALATE: no .feature-workspaces/$WS — run /aep-launch first"; exit 1
  fi
fi
# Re-verify before doing ANY work:
TOP=$(git rev-parse --show-toplevel); BRANCH=$(git branch --show-current)
[[ "$TOP" == *"/.feature-workspaces/$WS" && "$BRANCH" == "feat/$WS" ]] \
  || { echo "STILL NOT IN feat/$WS WORKTREE — STOP and escalate"; exit 1; }

Worktree creation belongs to /aep-launch; a missing one is an escalation, never improvised in the main checkout, because that is unrecoverable rather than merely wrong. Postcondition: guard exits 0 (show-toplevel ends in .feature-workspaces/<name>, branch is feat/<name>).

  1. Discover the OpenSpec change. List openspec/changes/ to find the active change; read proposal.md, design.md, specs/**/*.md, tasks.md. Post: change dir identified, artifacts read.

  2. Create the tracking folder: mkdir -p .dev-workflow. Post: dir exists.

  3. Gitignore it: grep -q '.dev-workflow' .gitignore || echo '\n# Development workflow tracking (per-workspace)\n.dev-workflow/' >> .gitignore. Post: grep -q exits 0 — .dev-workflow/ is gitignored.

  4. Create the progress file from this installed skill's template. Resolve the package from the worktree root, preferring the canonical Codex install: ROOT=$(git rev-parse --show-toplevel); AEP_BUILD_DIR="$ROOT/.agents/skills/aep-build"; [ -f "$AEP_BUILD_DIR/references/progress-template.md" ] || AEP_BUILD_DIR="$ROOT/.claude/skills/aep-build"; [ -f "$AEP_BUILD_DIR/references/progress-template.md" ] || { echo "aep-build progress template not installed" >&2; exit 1; }; cp "$AEP_BUILD_DIR/references/progress-template.md" ".dev-workflow/progress-$(git rev-parse --short HEAD).md". Fill in feature name, base SHA, date, change name, mode; mark design Phases 1–3 pre-completed. Post: file exists.

  5. Read tasks.md. It is the skeleton — you implement its rows linearly in Phase 4, one commit per row, so the commit history mirrors it 1:1. Post: task list known.

  6. Run project setup. If .claude/hooks/workspace-setup.sh exists, bash it (handles deps, dev server, ports, DB/seed, .env); otherwise read the README or ask the user how to set up. The hook writes .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>
    

    Post: .dev-workflow/ports.env exists with those four keys.

  7. Generate sprint contracts to .dev-workflow/contracts.md, one section per task, from specs/*.md + design.md + tasks.md. Format: references/contract-template.md. Post: file exists with one section per task.

  8. Generate the feature verification list .dev-workflow/feature-verification.json (skeleton + generator-ownership rule in references/harness-artifacts.md). The generator writes only commit_sha — verification_steps and passes are the evaluator's. Post: file exists.

  9. Generate the session recovery script .dev-workflow/init.sh (body in references/harness-artifacts.md); chmod +x it. Post: file exists and is executable.

  10. Initialize signals: mkdir -p .dev-workflow/signals, create status.json (skeleton in references/harness-artifacts.md), and read .dev-workflow/signals/feedback.md for main-session input. Full spec: /aep-launch references/signals-spec.md. Post: status.json exists.

  11. Create the lessons file .dev-workflow/lessons.md (template in references/harness-artifacts.md); fill the header from the change. Sections populate in Phase 4 (opt-in) and Phase 9 (summary). Post: file exists.

Update the Phase 0 checkbox in the progress file. Signal: set status.json "phase": 0, "phase_name": "initialized", "completion_pct": 10.

Product-cycle mode: if this change maps to a story in product-context.yaml (change name matches a story's openspec_change), read references/story-status.md for build-time dependency re-verification and the signal-based status protocol. Standalone (no product-context.yaml): skip it.


Phase 4: OpenSpec Apply

Implement each tasks.md task linearly, one task at a time = one commit, then record the commit SHA. For each task: implement (edit files directly), inspect (git status / git diff), run targeted tests, then commit feat(<scope>): <task description> and write the 8-char short SHA into the matching feature-verification.json entry's commit_sha (never touch verification_steps or passes). The commit shape, amend rules, and recovery are the canonical One-Commit-per-Task Pattern — see /aep-git-ref "The One-Commit-per-Task Pattern (Phase 4 of /aep-build)".

Invoke the apply skill for per-task guidance:

/opsx:apply

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.

Lesson capture (opt-in per task): after a task, if you hit something noteworthy — a non-obvious solution, an error that took multiple attempts, missing docs — append it to .dev-workflow/lessons.md under ## Solutions / ## Errors / ## Missing with a ### <title> sub-heading. Only when it is genuinely worth passing on.

Update the progress checkbox per task; mark Phase 4 done when all tasks are committed. Signal: set status.json "phase": 4 at start; keep task_current, task_index, task_total, completion_pct current as tasks progress.


Phase 5: Code Review & Verification

Verify the code before testing. This phase uses the generator/evaluator pattern — full mechanics in /aep-gen-eval references: scoring-framework.md (dimensions, thresholds, presets), agent-contracts.md (role separation, prompt templates), eval-protocol.md (request/response format, verification JSON, convergence rules).

Phase 5 entry — binding tier derivation, before any evaluation round spends the cap. Re-run the verification-tier derivation from the actual diff (git diff --name-only "$BASE"...HEAD) against policy.md's sensitive_paths and the referee-asset rule, and emit .dev-workflow/verification-recipe.json — tier, dimension preset, hard floors, scope_drift/tier_escalated. Derivation rules, floors, and the recipe schema: /aep-gen-eval references/verification-economics.md → The Two-Point Derivation (the generated e2e skill ships a runnable script). Two rules bind here: the tier may only go up from the dispatch brief's provisional tier, and a diff outside the declared files_affected recomputes from the diff with scope_drift: true. Phase 5 refuses to start without the recipe file — with a dispatch brief, derive it now; a standalone build with no brief runs deep defaults (fail-open) and records a recipe saying so. Publish verification_tier and tier_escalated to status.json (/aep-launch references/signals-spec.md owns the schema).

Completeness check (always, generator-side):

  1. Re-read the proposal (including any design-review adjustments).
  2. Walk each task's commit — git show <commit-sha> against its task description.
  3. Check .dev-workflow/contracts.md — verify each task's success criteria are met.
  4. If a task is incomplete, add a follow-up commit (feat(<scope>): complete <task> / fix(<scope>): <issue>) and loop back to Phase 4 — the task history stays append-only.

Quality review — with separate evaluator (full mode): whether an evaluator runs is decided by the binding tier in verification-recipe.json (standard/deep ⇒ evaluator; light ⇒ structured self-review) — the criteria file's presence decides nothing. /aep-launch writes no .dev-workflow/evaluator-criteria.md for a provisional-light story, so when the binding derivation upgrades the tier, assemble the criteria now from the binding recipe (dimension_preset + hard_floors; /aep-gen-eval references/scoring-framework.md → How to use presets) before entering the loop — a missing launch-time file must not downgrade the gate. Under a binding light tier, run the structured self-review instead and publish its durable completion signal to status.json (self_review: {result, sha, report} — signals-spec is the schema owner; this is what the autopilot quality gate reads).

With the criteria in place, the evaluator runs via executor.spawn_evaluator(), mode-dispatched and worktree-bound against this workspace's files + git state; per-mode spawn recipes live in /aep-executor references/backends.md. Spawn authority leaves the generator where an orchestrating layer exists (autopilot nudges the spawn; the main session owns it in interactive runs) — the orchestrator assembles the evaluator's context so the generator cannot curate what its judge sees; only in genuinely standalone builds does the generator invoke the spawn recipe itself. In every mode the evaluator prompt marks eval-request.md as the generator's untrusted claim — data to verify, never framing to adopt (/aep-gen-eval references/agent-contracts.md).

Evaluation loop — for each round N (start 1, max 2 for every tier that spawns an evaluator: light 0 / standard 2 / deep 2; a recipe declaring more is still capped at 2 here). Round 2 is bought only by a blocking finding.

  1. Write eval-request .dev-workflow/signals/eval-request.md per eval-protocol.md (Signal Files).
  2. Compose the evaluator prompt from agent-contracts.md (Evaluator Prompt — Code Quality), customized with the workspace paths (criteria_file, eval_request_file, spec_directory, contracts_file, verification_file, eval_response_file=.dev-workflow/signals/eval-response-<N>.md).
  3. Spawn the evaluator (mode-dispatched; the prompt is the spawn prompt — recipes in backends.md).
  4. Confirm the response exists — native spawns return on completion; legacy polls: while [ ! -f .dev-workflow/signals/eval-response-<N>.md ]; do sleep 15; done.
  5. Read the response (legacy only: tmux kill-pane -t :.1).
  6. On FAIL, run the taxonomy step first — every FAIL finding carries an evaluator-authored Failure-Class (/aep-gen-eval references/verification-economics.md → Failure Taxonomy) and routes by it: environment → ops checklist via the Human-Gate Protocol, zero further rounds; harness-flake → quarantine + harness story; scope → human gate → /aep-reflect re-slicing; unbuilt in-repo dependency → /aep-dispatch re-ordering. Only product-defect findings continue, and only at Impact: blocking — the verdict is derived from the impact labels (eval-protocol.md). A response with no blocking finding reads as PASS or PASS with notes: fix any material findings and attest each fix with its evidence (commit, test, or probe) in an addendum to eval-request.md, carry polish notes into the PR body, and proceed to Phase 6 spending zero further rounds. For a blocking finding after round 1: re-ground (re-read the spec + design + contracts from scratch), fix, add follow-up commits, then loop to step 1 as round 2 — the PR sees fixes as new commits, so history stays append-only.

The evaluator (only) updates .dev-workflow/feature-verification.json pass/fail per eval-protocol.md field ownership. Track eval_round + recovery_rung in status.json, and on every FAIL transcribe the response's per-finding class counts verbatim into status.json.failure_classes (evaluator-authored values — the generator transcribes, never reclassifies; signals-spec owns the schema and the mixed-class routing rule).

Cap exhaustion is defined, not silent: a blocking finding still open after round 2 escalates via the Human-Gate Protocol with type eval_not_converging, whatever the tier — there is no standard → deep escalation (leave tier_escalated: false). The gate record carries the two-round history and proposes the strategy changes the human or the autopilot policy may pick: a fresh generator told "the previous approach failed on X; take a different design path" (inheriting this worktree), or decomposing the story to its smallest viable slice with the split surfaced.

Recovery ladder (generator-side, two rungs) — round 1: the same generator fixes the blocking findings in place; before round 2: re-ground, then fix. The evaluator only scores, so re-ground and both gate options stay generator-side. The ladder is product-defect machinery only — step 6's taxonomy routes every other class off it before any rung is chosen. Skip the ladder and escalate immediately on a hard-failure / security FAIL (auth-model gap, data-exposure risk), a spec contradiction (scope), or a missing external dependency (environment — needs a named refusal tag): these need human judgment. Rationale + the fresh-generator spawn contract: /aep-gen-eval references/recovery-ladder.md.

Quality review — without evaluator (light mode): structured self-review across correctness, security, performance, and conventions, walking the feature-verification.json steps manually. Document findings in .dev-workflow/code-review-<feature>.md and fix issues found.

Update the Phase 5 checkbox. Signal: set status.json "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 — raise a gate:

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

  2. Raise it on your launch mode's transport (/aep-executor references/backends.md → The Human-Gate Protocol). The answer comes back through the main agent (hub-and-spoke), so the human answers where they already are. In the no-push modes (native-bg-subagent, claude-bg, codex-exec, workflow, headless) that means gate-and-park: commit WIP, update status.json, and end your run cleanly — your state is in the worktree + .dev-workflow/, and the orchestrator resumes a worker there with the answer.

  3. Block-in-place modes only: continue anything not blocked on the answer; otherwise wait, re-checking feedback.md.

  4. On answer (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)

This phase is journey-first: Step A authors the journey from the layer's acceptance criteria; Step B executes it. Step A is unconditional (independent of build mode, dogfood_target, journey_timing). Only execution (Step B) varies: skipped in Light mode or when dogfood_target == none, and deferred to the /aep-wrap gate under journey_timing: post-deploy.

Step A — Author the layer's journey (always, pre-merge)

If the project has an e2e-test skill with a journeys/ dir (dogfood_target != none) and no journey covers this layer — or new acceptance criteria lack scenarios — author/extend the journey now in skills/e2e-test/journeys/<NN-slug>.md (path planned in layer_gates[N].journeys; copy the template in journeys/README.md, set front-matter target:, layer:, covers: — the acceptance-criterion ids this journey proves; see /aep-e2e-skill-scaffolding references/bdd-journeys.md):

  • One scenario per acceptance criterion (from stories[].acceptance_criteria), each Then → a concrete Verify (API response / DB row / inspector check), intent-level and tool-agnostic.
  • The journey must map every acceptance criterion to a scenario Verify (or a Tier-1 scripted case for deterministic behavior, or a Tier-3 API check for backend/async state) before execution; a deliberately deferred criterion carries a WAIVER: <reason> line. That coverage is what /aep-wrap checks before flipping the gate to passed.
  • Commit the journey file with the feature now (pre-merge) — even under journey_timing: post-deploy, where only the journey's execution defers (Step B).

dogfood_target == cli (CLI/library): author a target: cli journey — bash drives the built binary, each Then → a Verify on exit code / stdout / stderr / filesystem. Same authoring rules; only the tool track differs. dogfood_target == none (no runnable surface — config/schema/docs, no journeys/ dir): no journey to author — instead map every acceptance criterion to a Tier-1 scripted case / Tier-3 API check, then jump to Step B's coverage note.

Tier applicability is per project type (a CLI/library layer is [1,2]; only a genuine none-target layer is all Tier-1, no journey): /aep-e2e-skill-scaffolding references/three-tier-model.md.

Step B — Execute the authored journey (dogfood)

Light mode: skip Step B (Step A still ran). Outside Light mode, Step B runs with whatever host-aware method is available — an absent agent-browser means degrade to the next method, not skip.

Environment preflight (before any journey spend). Run the probes declared in skills/e2e-test/policy.md (/aep-gen-eval references/verification-economics.md → Environment Preflight Gate):

  • Deploy-independent probes run here on every story, regardless of journey_timing — required secret names present in CI config, expected account fingerprint vs. actual auth identity, env-var names wired in deploy config. They need no live target.
  • Target-bound probes (target reachable, bindings present, fixtures seedable) run here only under journey_timing: pre-merge; under post-deploy they run at the /aep-wrap gate.

An unmet required precondition is a named refusal (REFUSING [dogfood-secret-absent:<NAME>]) — surface it via the Human-Gate Protocol as an ops checklist and stop the journey: zero scenarios run, zero rounds spent, environment by construction, filed as ops work rather than a code story. An absent optional capability (the live half of a non-milestone gate under live_policy: milestone_gates_only) is SKIP, not REFUSED — it blocks nothing.

Run the layer's journey (intent, not a click script), verifying state per each Verify line. The journey's target: (web/mobile/desktop/cli) plus skills/e2e-test/tool-selection.md — the project-local projection of e2e_tool(target_type) — resolve which automation tool to use; the host-aware selection and its degradation order are in /aep-executor references/dogfood-validation.md (e2e_tool(target_type), web wrapper dogfood_method()).

Resolve the target from skills/e2e-test/policy.md (dogfood_target):

  • cli → run the built binary locally via bash (no URL); seed local fixtures with bash skills/e2e-test/scripts/seed.sh if needed.
  • none → skip the journey dogfood (Tier-2 N/A); prove criteria via Tier-1 / Tier-3.
  • 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 journey_timing is post-deploy, execution defers to the /aep-wrap gate (target isn't up pre-merge) — Phase 6 here runs only Tier-1/Tier-3, but the Step-A journey file is still committed now.
source .dev-workflow/ports.env   # local target → $BASE_URL  (deployed target: use the URL from policy.md)

Whatever the method, emit the unified severity/category/repro report format (dogfood-validation.md → Unified report format) so the classifier stays host-agnostic, and write it to .dev-workflow/dogfood-<feature>.md rather than to chat — that path is the ingestion contract /aep-watch's dogfood_report source reads to auto-file findings.

Confirm the coverage matrix. After executing, recompute the layer's coverage matrix: every acceptance criterion maps to the scenario Verify / scripted case / API check that proves it. Because Step A authored a scenario per criterion, this is a confirmation — any uncovered criterion is a Step-A miss: author it now and re-run until coverage.criteria_covered == coverage.criteria_total (deferrals carry a WAIVER: <reason> line).

Signal: set status.json "phase": 6, "phase_name": "dogfood-testing".


Phase 7: Finalize the Journey + Record the Layer Gate

Skip if the project has no e2e-test skill. Light mode: skip this phase.

The journey was already authored in Phase 6 Step A. This phase finalizes that durable BDD regression artifact with reality discovered during execution. In skills/e2e-test/journeys/:

  • Refine the existing journey for this layer — fold in selector/route drift, extra Verify lines, corrected preconditions. Under journey_timing: post-deploy the journey executes later at the /aep-wrap gate, so this pre-merge finalize folds in only what local / Tier-3 checks revealed.
  • Each Then keeps a concrete Verify line (API response / state check). Keep it tool-agnostic; API-level assertions use $BASE_URL / $SERVER_URL from .dev-workflow/ports.env (never hardcoded ports).

Record the layer gate. Write 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 (references/layer-advance.md).


Phase 8: Review Results

Light mode: skip this phase.

  1. Source .dev-workflow/ports.env for correct ports.
  2. Run the project's framework tests (Tier 1), any applicable Tier-3 API drivers, and replay the impacted prior-layer journey set (regression) — selected against the merged diff rather than the declared files_affected: a journey is impacted when its covers: criteria belong to this story, when its optional paths: front-matter globs intersect the diff, or when it declares no paths: at all (fail-open — undeclared journeys stay in the replay set). Plus, always, the walking-skeleton journey as a canary — one execution that catches the "the app no longer starts" class immediately. Full prior-layer replay belongs to the /aep-wrap layer gate and its mid-layer checkpoints, not to every story — with one exception: a deep-tier story replays the full prior-layer set here. Replay this layer's Tier-2 journey here only when journey_timing: pre-merge — under post-deploy its execution defers to the /aep-wrap gate (the deployed:<url> target isn't up pre-merge). Verify everything applicable passes and coverage.criteria_covered == criteria_total (or gaps carry a WAIVER:).
  3. Present (or note in the progress file): the Phase 5 code review, the Phase 6 dogfood report (if run), and the Phase 7 journey result + layer-gate evidence + coverage summary (criteria_covered / criteria_total, if run).
  4. If tests fail, loop back to the appropriate phase.

Signal: set status.json "phase": 8, "phase_name": "review-results".


Phase 9: Cleanup & Publish

Do NOT run /opsx:archive here — archive runs on the integration branch after merge (via /aep-wrap).

  1. Write the lesson summary. Before publishing, write a final ## Summary for Next Agent (1–3 sentences) in .dev-workflow/lessons.md: what would you tell the next agent building in this module? Write it even if there are no other entries ("straightforward implementation, no surprises" is useful signal).
  2. Review the commit history: git log --oneline "$BASE"..HEAD (resolve $BASE per /aep-git-ref "Resolving $BASE"). It should be a clean linear sequence — one commit per tasks.md task, optionally followed by review-fix commits.
  3. Rebase onto the latest integration branch and push (-u on first push): git fetch origin && git rebase origin/"$BASE", resolve conflicts, then git push -u origin feat/<name>. Full recipe, including the abort path: /aep-git-ref "Publishing & PR Conventions".

Phase 10: Create PR / MR

Open the PR/MR with the base passed explicitly — gh pr create --base "$BASE" (GitHub) / glab mr create --target-branch "$BASE" (GitLab), resolving $BASE per /aep-git-ref "Resolving $BASE"; host auto-detect and the full recipe are in /aep-git-ref "Publishing & PR Conventions" → "Open the PR". An unrecognized host means opening it by hand, still with the base passed.

The base is explicit because gh pr create otherwise infers it from local branch state and can land the PR on a stale base or — in two-branch mode — on production main. Postcondition: gh pr view --json baseRefName returns $BASE.

Include in the PR/MR body: summary of changes (from the proposal), test-coverage notes, and a link to the manual test plan (if created).


Phase 11: PR Review & CI Feedback Loop

Monitor for CI and review feedback.

Triage each comment: Fix — correctness, CI failures, convention violations, security. Acknowledge but skip — style preferences, over-engineering, cosmetic. Discuss — architectural suggestions that expand scope, conflicting comments.

Fix loop: (1) triage all comments; (2) write a fix plan at .dev-workflow/pr-fix-plan-<round>.md; (3) reply to skipped/discussed comments; (4) add a follow-up commit per fix (fix(<scope>): address review feedback on <topic>); (5) re-run tests; (6) re-push (git push origin feat/<name>); (7) repeat until CI is green and reviews are resolved. Commit/push conventions: /aep-git-ref "Publishing & PR Conventions".


Phase 11.5: Human Evaluation & Iteration

After PR-review fixes resolve, 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 mismatches), this phase handles the iteration loop.

If no issues found: skip to Phase 12.

Iteration round:

  1. Document findings in .dev-workflow/human-eval-round-<N>.md: what was found (description + repro), severity (minor/moderate), category (UX/logic/edge case/visual).
  2. Add a follow-up commit per fix (fix(<scope>): <human-eval finding>).
  3. Align the OpenSpec change in openspec/changes/<name>/: add completed tasks to tasks.md, update specs/ if behavior changed, update design.md only if approach shifted; keep proposal.md scope as-is.
  4. Re-test — re-run Phase 5 (code review) and Phase 6 (dogfood) on the changed areas.
  5. Push (git push origin feat/<name>).
  6. Repeat if the tester finds more issues.

Signal: create .dev-workflow/signals/ready-for-review.flag when ready for human evaluation; set status.json "phase": 11.5, "phase_name": "human-evaluation". This is a human gate — also follow the Human-Gate Protocol so the orchestrator surfaces it instead of counting you as stuck.


Phase 12: Pre-merge Checks & Merge

  1. Up-to-date with the integration branch (resolve $BASE per /aep-git-ref "Resolving $BASE"): git fetch origin && git rebase origin/"$BASE" && git push --force-with-lease origin feat/<name> — the lease variant pushes only if the remote hasn't advanced since your last fetch (/aep-git-ref "Publishing & PR Conventions").

  2. Read GitHub's own readiness:

    gh pr view <number> --json mergeStateStatus,mergeable --jq '{state: .mergeStateStatus, mergeable}'
    
    • CLEAN → mergeable now (required checks satisfied or none; no required review missing) → proceed (a CLEAN PR with zero required checks is mergeable — absence of checks is not a reason to wait).
    • Anything else (UNKNOWN, UNSTABLE, BLOCKED, DIRTY, BEHIND) → load references/merge-decision-cases.md for the per-state handling before acting.
  3. No unresolved review comments.

  4. E2E tests passed (if applicable).

  5. Tier re-check on post-eval commits: if commits landed after the last eval PASS (Phase 11 review fixes, Phase 11.5 human-eval fixes), re-run the tier derivation on the updated diff. A drift into sensitive_paths upgrades the tier and requires one fresh evaluation round at the upgraded tier before merging — the stale-eval rule, since the code changed since the last evaluation.

  6. Present the final status summary.

  7. Merge decision. The mode marker is the sole authority for detection: the Phase 0 guard relocates every build, interactive included, into a worktree, so cwd cannot distinguish autonomous from interactive.

    MODE=$(cat "$(git rev-parse --show-toplevel)/.dev-workflow/signals/mode" 2>/dev/null)  # anchored — a build phase may have cd'd into a subdir
    
    • mode reads exactly autopilot → autopilot mode: merge immediately when the pre-merge conditions pass (the orchestrator monitors via signals; nothing is waiting to confirm).
    • anything else (absent, empty, other) → interactive mode: ask for confirmation before merging. An ambiguous signal reads as interactive.

    "PR ready" is not a stop condition. In autopilot, stop short of merge only on the 6 legitimate conditions — those, the full per-state handling, and the worked cases are canonical in references/merge-decision-cases.md; anything else merges. After merging, set status.json story_status: "completed" — your job ends there. Wrap runs /opsx:archive on the integration branch, so running /aep-wrap from this worktree corrupts the concurrency protocol; the orchestrator wraps.

Merge:

  • GitHub: gh pr merge <number> --squash --delete-branch
  • GitLab: glab mr merge <number> --squash --remove-source-branch

Guardrails

Cross-cutting rules with no single step home:

  • Only the main session writes product-context.yaml — workspaces report status through .dev-workflow/signals/status.json (the concurrency protocol; /aep-launch references/signals-spec.md).
  • Resume: if returning to an in-progress workflow, run .dev-workflow/init.sh if it exists, then read the progress file.
  • Signals: update status.json at the start and end of every phase, and check .dev-workflow/signals/feedback.md for main-session feedback at phase boundaries.

Next Step

After merge, signal the main session to run:

/aep-wrap