Use when you have a written implementation plan to execute in a separate session with review checkpoints
Instal·la
npx skillscat add daniel-ospina/agent-infra/executing-plans Instal·la des del registre de SkillsCat.
⛔ This skill MUST be read in full — not skimmed. Formal review gates depend on its workflow.
Skipping steps silently bypasses quality checks. Missing gates = undetected breakages.
Human approval gate: presents output for user review. Pipeline advances after approval.
Verifier gate: dispatches AI reviewers. Pipeline auto-advances when clean.
Human approval gate: presents output for user review. Pipeline advances after approval.
Verifier gate: dispatches AI reviewers. Pipeline auto-advances when clean.
Human approval gate: presents output for user review. Pipeline advances after approval.
Verifier gate: dispatches AI reviewers. Pipeline auto-advances when clean.
Source: Canonical copy at `skills/executing-plans/SKILL.md``.
v5.0.0 — stripped NVIDIA/MCP tool calls. Restored batched execution flow. Added dirty-state guard and research context injection. All sub-agents use Pi task tool with isolated context.
Executing Plans
Overview
Ontology:
tortoise/docs/ONTOLOGY.md(v3.1, canonical) — fetch:gh api repos/daniel-ospina/tortoise/contents/docs/ONTOLOGY.md --jq .content | base64 -d(§5 = controlled vocabulary).
Load plan, review critically, execute tasks in batches, report progress between batches (informational — not blocking), pause only when taxonomy-matching decisions arise. Verification runs after each batch via isolated sub-agents. Fall back to the controller on failures.
Core principle: Batch execution with informational progress reports. Blocking pauses only for genuine decisions.
Announce at start: "I'm using the executing-plans skill to implement this plan."
Between-Phase UI Verification (agent-browser)
When a phase touches UI files (*.tsx components or pages), run a lightweight agent-browser check before proceeding to the next phase:
# Verify the affected route renders in dev
agent-browser open http://localhost:3000/<affected-route> --screenshot=/tmp/verify-phase.png
agent-browser errors 2>&1
# Mobile viewport check
agent-browser open http://localhost:3000/<affected-route> --viewport=375x812 --screenshot=/tmp/verify-phase-mobile.pngGate: If the page crashes (white screen, 500, unhandled error), BLOCK the next phase — fix before continuing. If warnings (layout issues, missing elements), WARN and proceed.
Skip: Backend-only phases with no .tsx changes.
CTA Click-Through (between phases): After completing a phase that touches UI, verify the CTA remains functional:
# Navigate to the affected page and verify CTA buttons work
agent-browser open http://localhost:3000/<affected-route> --screenshot=/tmp/verify-phase-cta.png
# Check for CTA elements
agent-browser evaluate "document.querySelectorAll('a[href], button').length" 2>&1 > /tmp/verify-cta-count.txt && cat /tmp/verify-cta-count.txt
# Verify no broken links on the page
agent-browser errors 2>&1 > /tmp/verify-phase-errors.txt && cat /tmp/verify-phase-errors.txtGate: CTA elements must be present and functional. Broken CTAs or missing buttons block the next phase.
Quality Gates (WARN only): After completing a phase that touches .ts/.tsx files, run quality checks:
npm run check:coverage-pruning # flag untested new code
npm run check:arch:changed # catch layer violationsSurface any gaps before proceeding. These are informational — they do not block.
Human Input Taxonomy (inlined from human-input-framework v2.0.0)
Research-Before-Ask Protocol (MANDATORY — runs BEFORE any pause)
When a taxonomy match occurs, do NOT pause immediately. Run this protocol first:
- Internal Research — Search codebase for existing patterns, conventions, or prior decisions
- External Research — Perplexity queries for best practices, how others solve this
- Decision:
80% confidence → Apply decision, note with ponytail comment. DO NOT PAUSE.
- 50-80% confidence → Apply best-supported option, note rationale. DO NOT PAUSE.
- <50% confidence → Pause with structured question + research findings.
- P0 consequence (data loss, security, irreversible, cost >$10/mo, legal/compliance) → Pause regardless.
Taxonomy Categories
Pause and present structured questions when a decision involves ANY of:
- Ontology changes — new tables, columns, relationships, semantic meaning changes
- UX changes — visible user-facing behavior/layout/flow changes not explicitly requested
- One-way doors — destructive operations, data migrations, schema drops, force pushes
- Third-party dependencies — new API integrations, service subscriptions
- Cost impact — changes increasing recurring costs by >$1-3/month
- Scope expansion — implementing beyond what was requested
Tie-breaking rule: When uncertain whether a decision matches the taxonomy, RESEARCH FIRST. Pause only if research is inconclusive OR P0 (data loss, security, irreversible, cost >$10/mo, legal/compliance).
Everything else → Agent decides autonomously with a brief note, open to iteration.
Third-Party Dependency Detection
When Step 1.5 runs, "unfamiliar" means: a third-party npm package imported in files the plan will touch, that is NOT covered by the plan's ### Pattern Research section.
Detection algorithm:
Collect target files: Parse the plan's task list for
**Files:**entries. Extract allCreate:andModify:paths. Also scan task step code blocks for import statements in new-file code.Extract imports: For
Modify:files, read the file and extract import statements with a non-relative, non-alias package specifier. ForCreate:files (don't exist yet), extract imports from the plan's code blocks for those files. Skip files that can't be read (treat as empty — no imports).Regex pattern to match third-party imports:
/(?:import\s+.*?\s+from\s+['"]|require\s*\(\s*['"])([^.@/][^'"]*)['")]/Exclude:
- Relative imports (
./,../) - Alias imports (
@/— project internal) - Node.js built-ins (
fs,path,crypto,stream,http,https,url,os,events,util,buffer,child_process,tls,net,dns,readline,zlib,querystring,assert,cluster,dgram,domain,punycode,string_decoder,tty,vm,worker_threads) - Type-only imports (
import type { ... } from '...')
- Relative imports (
Parse Pattern Research: Read the plan's
### Pattern Researchsection using an anchored, line-level exact heading match (^### Pattern Research$) — a substring match would let epic-brief headings such as### UX Pattern Research/### Tech Stack Researchfalsely satisfy coverage. Those epic-brief headings are PRIOR_RESEARCH context and never coverage evidence (issue #231 D5). Extract library/SDK/package names that appear in section headings or findings text. Also note> Gate skipped,> Bucket [name] skipped, and> Research skipped: no demonstrated gapjustifications.Cross-reference: For each extracted package name, check if it (or its parent scope, e.g.
@supabase/supabase-jsmatchessupabase) is mentioned in Pattern Research. A package is "covered" if:- Its name appears in a Pattern Research bucket heading or finding
- The Pattern Research explicitly mentions skipping it with a valid reason (e.g., "uses in-repo wrapper exclusively") — a documented skip takes precedence over findings-date absence (issue #231 H5)
- The Pattern Research says "plan touches zero third-party deps" AND no third-party imports were found
Determine unfamiliar: Any package NOT covered by Pattern Research is "unfamiliar". Also treat as unfamiliar if:
- Pattern Research is absent from the plan entirely
- Pattern Research exists, contains actual findings, but carries no
> **Findings date:** YYYY-MM-DDstamp — findings-date ABSENT → unfamiliar directly (fail-safe re-verify; NO plan-date fallback for the coverage question — legacy unstamped plans get re-verified, issue #231 D4). Carve-out (issue #231 H5): a documented whole-section skip is NOT "findings-date absent" — a block carrying> Gate skipped: <justification>/> Bucket [name] skipped: <justification>/> Research skipped: no demonstrated gaptakes precedence over the stamp rule (per Step 4); only blocks containing actual findings are subject to the stamp requirement. - The stamped findings are older than 6 months by findings date (staleness threshold unchanged; plan date plays no role in the new algorithm)
- The plan references a different major version than what's imported (detected via package.json or import style)
Result: A list of
[package_name, reason_unfamiliar]tuples.
The Process
Step 0: Workspace Setup
Before isolating — collision pre-flight (#3061, FAIL-CLOSED) — the FIRST action of Step 0. The gate runs BEFORE any worktree or branch is created (before using-git-worktrees, before hub-worktree.sh, before git checkout -b), because the tool matches issue numbers boundary-exact against branch and worktree paths (number_present: feat/4027-fix-callers → hit, .worktrees/4027-preflight → hit). Run it after isolation and the agent's own feat/<N>-… branch and .worktrees/<N>-… path read as a COLLISION on the issue it was authorised to start — the gate blocks its own caller. Run it first and that cannot happen.
The gate is provided by tortoise (the only repo carrying tools/collision_preflight.py) and applies to any issue's repo through --repo. It checks every in-flight surface (open + closed PRs, local + remote branches, worktrees, assignee/claim comments) and fails loudly in both directions: a hit, and a surface that could not be queried.
# 1. The tool lives ONLY in tortoise. Resolve that checkout EXPLICITLY — unset, `"$TORTOISE"/tools/…`
# collapses to `/tools/…` and PYTHON exits 2, which the exit table would report as INCOMPLETE
# ("a surface could not be queried"): a caller misconfiguration wearing a real verdict's face.
# $TORTOISE if set; else the cwd itself (when it IS tortoise), the tortoise sibling of the cwd's
# MAIN checkout (so it still resolves from a linked worktree), then the standard GITHUB root.
if [ -z "${TORTOISE:-}" ]; then
TOPLEVEL="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null)"
COMMON="$(git -C "$PWD" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
for CAND in "$TOPLEVEL" "$(dirname "${COMMON%/.git}")/tortoise" "${HOME:-/nonexistent}/Documents/GitHub/tortoise"; do
if [ -n "$CAND" ] && [ -f "$CAND/tools/collision_preflight.py" ]; then
TORTOISE="$(cd "$CAND" && pwd -P)"; break
fi
done
fi
if [ ! -f "${TORTOISE:-}/tools/collision_preflight.py" ]; then
echo "❌ collision pre-flight: no tortoise checkout resolved (TORTOISE='${TORTOISE:-}') — #3061 pre-flight NOT run."
echo " Record this in the dispatch log; do NOT silently skip. Set TORTOISE=<a tortoise worktree> and re-run."
exit 1 # do not start work
fi
# 2. --repo is MANDATORY. Omitted it means "cwd", and the tool then silently resolved the WRONG
# repository's issue — a tortoise worktree asked for an agent-infra #NNNN and returned CLEAN (#4027).
ISSUE_NUMBER="${ISSUE_NUMBER:-<N>}" # the issue being gated
REPO="${REPO:-<owner/name>}" # the issue's repo, e.g. daniel-ospina/agent-infra
case "$REPO" in ''|*'<'*|*'>'*) echo "❌ REPO is unset or still the literal placeholder ('$REPO') — set it to the issue's repo owner/name; #3061 pre-flight NOT run."; exit 1 ;; esac
# The `owner/name` form needs tortoise #3978. Until it lands, tortoise main rejects a slug with
# exit 3 (`--repo not a directory`), so probe the tool's own usage and pass the form it accepts.
if python3 "$TORTOISE"/tools/collision_preflight.py --help 2>&1 | grep -q 'owner/name'; then
REPO_ARG="$REPO" # slug form
else
# Pre-#3978 only a PATH is accepted, and it must be a checkout of $REPO. Falling back to `.`
# would check the CWD's repo and return CLEAN — the ONE code that authorizes dispatch, i.e.
# the #4027 wrong-repo false CLEAN. Resolve a path whose origin slug really is $REPO, or stop.
SLOT="$(git -C "$PWD" rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed -e 's#/\.git$##')"
REPO_ARG=""
for CAND in "${ISSUE_REPO_PATH:-}" "$PWD" "$TORTOISE" "$(dirname "$SLOT")/$(basename "$REPO")"; do
[ -n "$CAND" ] || continue # `git -C ""` silently uses the CWD — an empty candidate would "match" anything
SLUG="$(git -C "$CAND" config --get remote.origin.url 2>/dev/null | sed -e 's#.*github\.com[:/]##' -e 's#\.git$##')"
[ -n "$SLUG" ] && [ "$SLUG" = "$REPO" ] && { REPO_ARG="$CAND"; break; }
done
[ -n "$REPO_ARG" ] || { echo "❌ pre-#3978 tool: no local checkout of $REPO — '.' would check the WRONG repo (#4027). Set ISSUE_REPO_PATH=<a checkout of $REPO>; pre-flight NOT run."; exit 1; }
fi
python3 "$TORTOISE"/tools/collision_preflight.py "$ISSUE_NUMBER" --repo "$REPO_ARG"| Exit | Verdict | Action |
|---|---|---|
0 |
CLEAN | every surface queried, no in-flight work — the only outcome that authorizes starting work |
1 |
COLLISION | a worktree/branch/PR/claim already covers #N — do NOT start work; report the named surface |
2 |
INCOMPLETE | a surface could not be queried — NOT clean; fix gh auth/network, re-run |
3 |
usage/internal error | stop |
ANY non-zero exit stops the work. There is no "warn and proceed" and no graceful degradation: if gh is unavailable the tool returns INCOMPLETE (2) by construction — a stop, not a fallback path. A pre-flight that cannot tell "no collision" from "could not check" is exactly the bug this gate exists to prevent.
Distinguish the two exit-2 causes. A VERDICT: INCOMPLETE line on stdout is the tool's INCOMPLETE. An argparse usage: line with no VERDICT: line is a wrong invocation (unsubstituted or non-integer <N>) — fix the argument, not gh.
Pre-run precondition — do NOT run this from the issue's own worktree. This gate is the creator's gate: it runs before isolation, so feat/<N>-… and .worktrees/<N>-… do not exist yet. The tool has no self-exclusion: a branch/worktree this checkout already owns is reported as a strong hit under [local branches] / [local worktrees], so a run from inside feat/<N>-… / .worktrees/<N>-… collides with the artifact of the very work being gated. That is a property of the tool, not a licence to excuse a non-zero exit: "ANY non-zero exit stops the work" is unaffected, and a surface reported INCOMPLETE is never excused either. Run the gate from a checkout that does not carry <N> (the dispatcher's checkout, or a separate clone of $REPO). If none exists — a child already placed inside <N>'s worktree — record in the dispatch log that the pre-flight could not be run untainted and defer to the dispatcher's pre-dispatch gate (issue-workflow), which runs before any worktree exists.
Proportional isolation (inlined from proportional-gates v1.0.0):
| Risk | Isolation |
|---|---|
| Low (docs, config, 1-2 files) | Plain branch inside a worktree; in a hub, create the worktree first (#626). |
| Medium (3+ files, shared infrastructure) | Worktree recommended. Plain branch OK for single-file. |
| High (multi-system, migrations, auth) | Worktree required. Hub dirty → bash scripts/checkout-hygiene/hub-worktree.sh salvage feat/issue-{N}-{slug} (in-hub git stash push is M4-blocked); inside a worktree, do not stash — refs/stash is shared by every worktree of the repo (#1264) — make a WIP commit on your own branch instead. |
Never start on main/master regardless of risk.
If worktree is needed: Invoke using-git-worktrees skill once, in the controller session. If already inside an existing worktree, skip.
If plain branch is acceptable:
⛔ Guard note (#626): in-hub
git checkout -bis BLOCKED in every repo — the shared
main checkout must never be flipped (agent-infra's #99 exemption was removed in #615;
the M3 create-new carve-out was removed in #626). The "plain branch" shortcut applies
only when you are ALREADY inside a worktree; in a hub, create an isolated worktree first
(using-git-worktrees skill; agent-infra:bash scripts/checkout-hygiene/hub-worktree.sh feat/issue-<N>-<slug>).
- Run
git status --porcelainto check for uncommitted changes - If the HUB is dirty (uncommitted changes on main):
git stash pushis BLOCKED there — the M4 hub-disorder gate sanctions only recovery verbs, andstashis not one. Use the dirty-hub salvage path instead:bash scripts/checkout-hygiene/hub-worktree.sh salvage feat/issue-{N}-{slug}(README #435). Inside a worktree there is no "changes on main" to stash — and a worktree stash is not lane-scoped either:refs/stashis shared by every worktree of the repo, so a baregit stash popcan take a peer lane's work (#1264 — see using-git-worktrees → "The stash register is SHARED by every worktree"). Use a WIP commit on your own branch. - Create the branch INSIDE a worktree — invoke
using-git-worktrees(agent-infra:bash scripts/checkout-hygiene/hub-worktree.sh feat/issue-<N>-<slug>). Do NOT rungit checkout -bin a hub — it is BLOCKED (#626). - Dirty-state guard (worktree only): Check for uncommitted changes that look like partial implementation. If found, surface options but default to reset-and-re-execute. Do NOT silently overwrite partial state.
Pre-warming typecheck (proportional): Run npx tsc --noEmit in background when changes touch .ts/.tsx files. Skip for non-code or config-only changes. When run, start before Step 1 to eliminate cold-start latency — by the time the plan is reviewed, typecheck is already complete or failing fast.
Never create a worktree from inside a worktree.
Proportional Gating Summary
This skill uses proportional gates (see proportional-gates v1.0.0). Instead of "always run X," match verification depth to change risk and novelty. A reviewer sub-agent validates gate-skip decisions. The gating decisions scale with: code impact, surface risk, novelty, and reversibility.
Step 0.5 — Complexity Ratings Extraction (P0-3)
Before executing any tasks, extract domain complexity ratings from the scoping plan comment. These ratings drive proportional verification depth.
ISSUE_NUMBER=<from plan doc or branch name>
SCOPING_PLAN=$(gh issue view $ISSUE_NUMBER --json comments --jq '.comments[] | select(.body | contains("<!-- issue-scoping:")) | .body' | tail -1)
if [ -n "$SCOPING_PLAN" ]; then
# Table rows are parsed with -F'|' + whitespace strip, matching
# commit-workflow/workflow/03-code-review.md — the default FS makes $3
# the literal '|' for a '| Domain | Rating |' row, so the rating is
# unreachable and the complexity-axis checks silently never fire.
UX_RATING=$(echo "$SCOPING_PLAN" | awk -F'|' '/^\| UX \|/ {gsub(/ /,""); print $3}')
ARCH_RATING=$(echo "$SCOPING_PLAN" | awk -F'|' '/^\| Architecture \|/ {gsub(/ /,""); print $3}')
ONTOLOGY_RATING=$(echo "$SCOPING_PLAN" | awk -F'|' '/^\| Ontology \|/ {gsub(/ /,""); print $3}')
ACCESSIBILITY_RATING=$(echo "$SCOPING_PLAN" | awk -F'|' '/^\| Accessibility \|/ {gsub(/ /,""); print $3}')
else
echo "No scoping plan found — skipping complexity-axis checks"
UX_RATING=""
ARCH_RATING=""
ONTOLOGY_RATING=""
ACCESSIBILITY_RATING=""
fi
# Normalize: lowercase, trim
for var in UX_RATING ARCH_RATING ONTOLOGY_RATING ACCESSIBILITY_RATING; do
eval "$var=$(echo \"${!$var}\" | tr '[:upper:]' '[:lower:]' | xargs)"
# Empty or invalid → empty (skip checks)
case "${!$var}" in
low|medium|high) ;;
*) eval "$var=" ;;
esac
donePass ratings to Step 3 verifier as context. When all ratings are empty, verifier runs only typecheck + tests (existing behavior).
Step 1: Load and Review Plan
- Read plan file
- Review critically — identify any questions or concerns about the plan
- If concerns: Raise them with your human partner before starting
- If no concerns: Create TodoWrite and proceed
Step 1.5: Third-Party Dependency Verification (proportional)
Proportional dependency verification (inlined from proportional-gates v1.0.0):
| Situation | Action |
|---|---|
| Dep already used elsewhere in codebase | Skip verification — pattern is known |
| Dep is well-known stdlib-adjacent | Skip — common knowledge |
| Dep is new to codebase AND not in plan Pattern Research | Verify: 1-2 Perplexity calls |
| Dep is novel, unfamiliar, AND no Pattern Research | Verify: 2-3 calls. Pause if unavailable. |
Perplexity unavailable: If dep is well-known, proceed with note. Only pause for genuinely novel deps.
This catches deps missed at plan-writing time and prevents hallucinated API usage.
Sub-step 1.5a — Detect Unfamiliar Dependencies
Run the detection algorithm from Third-Party Dependency Detection above. Produces a list of unfamiliar packages.
If zero unfamiliar deps: Skip to Step 2. Document the skip with a brief note (e.g., "Step 1.5: all third-party deps covered by plan Pattern Research — skipped").
If unfamiliar deps found: Proceed to Sub-step 1.5b.
Sub-step 1.5b — Perplexity Verification
For each unfamiliar dep, fire 2 separate web_search calls. Keep at most 2 web_search calls in flight across ALL deps — that is the rung-2 concurrency bound from the research skill's Step 3 (Perplexity returns HTTP 429 to a wider fan-out and the batch is silently truncated). Stagger the deps instead of firing 2-per-dep at once, retry a 429'd call serially with a short backoff, and report attempted / returned if any call is lost:
- API verification call — "What is the current API for [package]? How do you import and use it? Show the most common usage patterns for the latest stable version."
- Pitfalls call — "What are common pitfalls, breaking changes, deprecations, or migration notes for [package]? What should developers watch out for?"
Record findings: For each dep, synthesize the two responses into a brief summary: correct import syntax, key API surface, version-specific gotchas. Append as context before proceeding to Step 2.
Sub-step 1.5c — Perplexity Unavailable
Proportional response: If the dep is well-known and used in 2+ other contexts, proceed with a note. Only pause if the dep is genuinely novel AND Perplexity is the only way to verify its API surface. When pausing, surface:
⚠️ Perplexity unavailable. The plan touches unfamiliar third-party deps that were not verified at plan-writing:
- [dep1] — [why unfamiliar]
- [dep2] — [why unfamiliar]
Options:
(a) Resolve perplexity (API key / credits) and say "continue"
(b) Proceed without verification — I accept the risk of hallucinated API usage
(c) Skip only [specific dep] — it's an internal dependency I know wellWait for explicit user choice. Default to (a). Never auto-select.
For 5xx errors / network failures: Retry 3× with exponential backoff (1s, 2s, 4s) + jitter ±200ms. On final failure, surface same pause message.
For 200 with empty content: Surface: "Perplexity returned no usable content for [dep]. Cannot verify API surface."
Sub-step 1.5d — Proceed
After all unfamiliar deps verified, append findings as context for the implementation session. If verification surfaces a major API change (deprecated method, breaking change since plan was written), flag it explicitly before proceeding — the implementer may need to adjust the plan. Proceed to Step 2 (Execute Batch).
Label Management — Execution Start
Before executing any tasks, manage workflow-state labels:
OWN_ING_LABEL="implementing"
**Status check:** Verify issue status is `planned` or `implementing` before starting. Warn if `dropped` or `failed` — do not auto-resume without human confirmation. Document `doc_status` transitions alongside label changes.
# 1. Self-cleanup: remove own stale implementing label from a prior crashed run
if gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name' | grep -q "^${OWN_ING_LABEL}$"; then
echo "🧹 Removing stale '${OWN_ING_LABEL}' label (from a prior incomplete run)"
gh issue edit $ISSUE_NUMBER --remove-label "$OWN_ING_LABEL" || {
echo "⚠️ Failed to remove stale label — continuing anyway"
}
fi
# 2. Warn-and-confirm: check for OTHER in-progress labels (exclude own)
OTHER_LABELS=$(gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name' \
| grep -E '^(scoping|planning|implementing)$' \
| grep -v "^${OWN_ING_LABEL}$" || true)
if [ -n "$OTHER_LABELS" ]; then
echo "⚠️ Issue #$ISSUE_NUMBER already has in-progress label(s): $OTHER_LABELS"
echo " Another agent may be working on this issue."
# Present the warning to the user. Ask: "Continue anyway?"
fi
# 3. Apply own implementing label (idempotent)
gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name' | grep -q '^implementing$' \
|| gh issue edit $ISSUE_NUMBER --add-label implementingKnown residual race: Between self-cleanup and label-apply, another agent could apply a conflicting label. This is acceptable — GitHub labels are advisory, not locking primitives.
Canary-fix detection: If the issue has the ci-failure label and "typecheck-canary" in its title, the fix may only address a type error without fixing the runtime gap. As part of implementation, ensure the fix wires the field through all data-fetching layers — not just the type. Identify every path that constructs or fetches the affected type (.select() calls, view queries, mapping functions, API handlers) and verify the plan covers each one. The commit-workflow preflight §Wiring-Gap Check will gate this again before merge.
Step 2: Execute Batch
Default: First 3 tasks
For each task:
- Mark as in_progress
- Follow each step (plan has bite-sized steps). Use judgment — if a step is clearly unnecessary given what has already been built, note the skip and why.
- For test-writing steps (steps containing "Write the failing test", "Write tests", or referencing a test file): invoke the
test-writingskill to guide test creation. The skill enforces Red-Green-Refactor and a 7-point quality self-check.
- For test-writing steps (steps containing "Write the failing test", "Write tests", or referencing a test file): invoke the
- Run verifications as specified
- If a step requires a taxonomy-matching decision: run the Research-Before-Ask Protocol. If research is inconclusive or P0, STOP, present structured question with findings, wait for answer
- Mark as completed
Step 2.5: Plan Fidelity Gate
Purpose: Mechanically verify that files changed on disk match what the plan said to change. This catches plan-implementation drift before verification runs — silent scope creep, missed steps, and unplanned files that slip through when the implementer takes a "better" path without updating the plan.
Blocking rule: This gate MUST pass before Step 3 verification. If the plan has no task list (pre-refactoring plan doc), skip this gate with a note.
2.5a — Extract Planned Files
Parse every task in the plan doc's ### Task N: blocks. Extract all paths from each task's **Files:** block — Create:, Modify:, and Test: lines. Produce a set of planned paths (normalized — relative to repo root, no ./ prefix).
Edge cases:
- Tasks with glob patterns (e.g.,
Modify: src/components/*/index.tsx) → expand vialsbefore comparing - Tasks with no
**Files:**block → flag as "task missing Files block" (P2 — implementer should add one, but don't block) - Task references a file that the plan says to "create if missing" → treat as both Create and Modify
2.5b — Extract Actual Changes
git diff --name-only HEADThis gives the set of files actually modified or created in the working tree (unstaged changes). Produce a set of actual paths.
2.5c — Mechanical Comparison
Compare the planned set against the actual set. Classify every divergence into one of three categories:
| Classification | Detection | Action |
|---|---|---|
| Missed | File in planned set but NOT in actual set | BLOCK. The plan says to change this file but it wasn't touched. Either (a) the implementer skipped a step, or (b) the step was unnecessary. If (b), the implementer MUST add a comment explaining the skip before retrying the gate. |
| Extra | File in actual set but NOT in planned set | WARN. Check if the file is an expected side-effect (config regeneration, type generation, lockfile churn). If not — it's unplanned scope. Surface with the task's Intent/Acceptance for a decision. If the file serves the same Intent as a planned task and its Acceptance is satisfied, accept with a note. Otherwise flag. |
| Different approach | Same file in both sets, but the implementation takes a fundamentally different path than the plan described | Gate on Intent. Read the task's **Intent:** and **Acceptance:** fields. If the implementation still satisfies both → accept + note divergence for plan update. If Acceptance is violated or Intent is unmet → flag. |
2.5d — Fidelity Report
Emit a structured report before proceeding:
📋 Plan Fidelity Gate Report
Planned files: N
Actual files changed: M
✅ Matched: X files
⚠️ Missed: Y files
- path/to/missed.ts — [task reference / reason]
🔶 Extra: Z files
- path/to/extra.ts — [expected side-effect? | maps to which task Intent?]
🔀 Different approach: W files
- path/to/changed.ts — Intent: [satisfied?] | Acceptance: [satisfied?]
Verdict: PASS | BLOCK (reason)When BLOCK: Surface missed files to the implementer. They must either implement the missing changes or annotate the plan doc with a skip justification. Re-run the gate after. Max 2 retries — on 3rd failure, surface to user.
When PASS: Proceed to Step 3 verification.
Skip entirely when: The plan doc has no ### Task N: blocks (pre-refactoring plan). Document the skip: "Step 2.5: No task blocks found in plan — fidelity gate skipped."
Step 3: Verification (per batch)
Verification sub-agent dispatch must return PASS before proceeding to the next batch. The verification-gate extension blocks git operations (git commit, git push, gh pr create, gh pr merge) until the verifier sub-agent returns PASS with matching file hashes for all changed files.
Mandatory: After each batch completes, dispatch a verification sub-agent via the subagent tool:
subagent(agent='verifier', task='verify files: <space-separated list>. Classification: <UI|backend|both>. Project root: <PROJECT_ROOT>.', cwd=<PROJECT_ROOT>)The verifier reads the ### Verification Plan from the plan doc (if present) and runs applicable commands. Falls back to existing behavior if no verification plan exists.
Verifier commands:
| Layer | Command | When |
|---|---|---|
| typecheck | npx tsc --noEmit |
Always |
| unit | npm test |
Always |
| integration | npm run test:integration |
DB/API surfaces in verification plan |
| e2e smoke | npm run test:e2e:smoke |
Critical paths, depth=smoke |
| e2e full | npm run test:e2e:critical |
Multi-page flows, depth=full |
Backward compatible: If no verification plan exists in the plan doc (pre-refactoring plan docs), fall back to existing: typecheck + npm test.
ISSUE block format:
ISSUE:
check_type: <type>
severity: P0|P1|P2
location: <file>:<line> or [section name]
description: <what's wrong>
suggestion: <how to fix>A verification with zero issues = CLEAN.
Complexity-Axis Checks (P0-2 — lightweight, pre-commit)
When: Domain ratings available from Step 0.5 extraction. Skip when all ratings are empty.
# UX checks — component catalog + design tokens
if [ "$UX_RATING" = "medium" ] || [ "$UX_RATING" = "high" ]; then
echo "=== UX Complexity-Axis Checks ==="
# Check for hardcoded colors (design token bypass)
if git diff --cached --name-only | grep -qE '\.(tsx|css)$'; then
git diff --cached | grep -E 'bg-amber-|bg-yellow-|bg-gray-|bg-red-|bg-blue-|bg-green-' && echo "ISSUE: check_type: design-token | severity: P2 | location: <grep output> | description: hardcoded Tailwind color used instead of design token | suggestion: use semantic token (bg-primary, bg-card) or hex token (bg-[#FFD147])" || true
git diff --cached | grep -E 'text-gray-|text-slate-|text-zinc-' && echo "ISSUE: check_type: design-token | severity: P2 | location: <grep output> | description: hardcoded text color used instead of design token | suggestion: use text-primary or text-secondary" || true
fi
fi
# Architecture checks — wrapper compliance
if [ "$ARCH_RATING" = "medium" ] || [ "$ARCH_RATING" = "high" ]; then
echo "=== Architecture Complexity-Axis Checks ==="
# Check for direct external service imports bypassing wrappers
if git diff --cached | grep -qE "from ['"]twilio['"]|from ['"]@sendgrid|from ['"]resend"; then
echo "ISSUE: check_type: wrapper-compliance | severity: P0 | description: direct external service import bypasses in-repo wrapper | suggestion: use in-repo wrapper (sms.ts, email.ts, dispatcher.ts)"
fi
fi
# Ontology checks — migration safety
if [ "$ONTOLOGY_RATING" = "medium" ] || [ "$ONTOLOGY_RATING" = "high" ]; then
echo "=== Ontology Complexity-Axis Checks ==="
# Check migration timestamp ordering
if git diff --cached --name-only | grep -q 'supabase/migrations/'; then
npm run check:migrations 2>&1 || echo "ISSUE: check_type: migration-safety | severity: P0 | description: migration timestamp check failed | suggestion: run npm run check:migrations and fix timestamp ordering"
# Check types regenerated
if [ -f src/integrations/supabase/types.ts ]; then
git diff --cached --name-only | grep -q 'src/integrations/supabase/types.ts' || echo "ISSUE: check_type: type-alignment | severity: P1 | description: migration added without regenerating types | suggestion: run npm run generate:types and commit types.ts"
fi
fi
fiPlaywright Functional Verification (CPI-9 — UI batches only)
When: The batch touches UI files AND the plan doc has a journey map.
Pipeline: Planner agent explores localhost from journey map → Generator produces tests → Execute → Healer auto-repairs locators. Human reviews patches.
Gate: Tests fail AND Healer cannot fix = BLOCK. Planner/Generator cannot produce = WARN.
Flakiness prevention: networkidle before screenshots, animations disabled, dynamic content masked.
Skip when: No journey map in plan doc, OR batch has no UI file changes.
The JSON result with file hashes is produced as an optional machine-readable sidecar (/tmp/verify-<batch>.json).
Gate: If verifier returns issues, fix and re-dispatch (max 2 retries). Do NOT proceed to the next batch until zero issues remain. On 3rd failure: surface to user.
Use the same model as the current session — omit the model parameter or pass the current session's model. Do NOT specify a different model for sub-agents.
Inputs to the sub-agent:
plan_text: the plan tasks covered by this batchfiles_written: list of files modified/createdacceptance_criteria: from the planresearch_context: if a research brief exists for this plan, pre-read and inject as## Verified Research Contextheader + content
The sub-agent should:
- Read the implemented files
- Verify each acceptance criterion is met
- Run any specified tests
- Return ISSUE blocks (zero issues = CLEAN) with unmet criteria + failing tests, plus optional JSON sidecar with file hashes
On issues found: Fix them, then re-dispatch verification (max 2 retries). On 3rd failure → the orchestrator (you, the main session) takes over:
- Read the unmet criteria and failing tests
- Fix the issues directly (you have full context the verification sub-agent lacked)
- Re-run verification
- If THIS also fails → halt and surface to user with options to (a) provide guidance, (b) skip and proceed, (c) abort
The orchestrator escalation gives one more recovery attempt before waking the human.
Step 4: Progress Report (Informational — NEVER STOP)
When batch complete and verified:
- Show what was implemented (files written, shell commands run)
- Show verification output (PASS / FAIL details)
- ▶ CONTINUE IMMEDIATELY to next batch. Do NOT pause. Do NOT ask "shall I continue?"
- The user can interrupt at any time if they want to provide feedback
This is an informational checkpoint, not a gate. Never stop after a progress report.
Step 5: Continue
- Execute next batch, repeat Steps 2-4 until complete
- If user provides feedback: apply changes before next batch
Step 6: Complete Development
After all tasks complete and verified:
Verification gate extension fires at completion. Do NOT claim "done" without verification evidence.
The verification-gate extension (loaded in this session) blocks git commit, git push, gh pr create, and gh pr merge until the verifier sub-agent returns PASS with matching file hashes. All files must be verified before git operations succeed.
Required evidence before claiming "done":
- Verifier sub-agent returned
{"status": "PASS"}with verified file hashes - Typecheck passes (0 errors)
- Tests pass (0 failures)
- For UI changes: browser screenshot shows page renders correctly
- No regressions in unaffected code
If verification fails: fix and re-verify. Do NOT claim "done" with failing checks. Do NOT bypass the gate.
- Run verification gate: Invoke
verification-before-completionto prove the work.- Typecheck must pass. Tests must pass. No regressions.
- For features with UI: render the affected pages, verify they load correctly.
- If verification fails: fix the issue and re-verify. Do NOT claim "done" with failing checks.
- Verification clean → proceed:
Label Transition:
# Remove implementing, add implemented
gh issue edit $ISSUE_NUMBER --remove-label implementing || true
gh issue view $ISSUE_NUMBER --json labels --jq '.labels[].name' | grep -q '^implemented$' \
|| gh issue edit $ISSUE_NUMBER --add-label implemented- Announce: "I'm using the commit-workflow skill to land this work."
Step 6.5 — Worktree Teardown
After verification passes and before handoff to commit-workflow, clean up the worktree if one was created in Step 0:
TOPLEVEL=$(git rev-parse --show-toplevel)
GIT_COMMON=$(git rev-parse --git-common-dir)
if [ "$GIT_COMMON" != ".git" ] && [ "$GIT_COMMON" != "$TOPLEVEL/.git" ]; then
WORKTREE_PATH="$TOPLEVEL"
BRANCH=$(git branch --show-current)
MAIN_REPO=$(cd "$GIT_COMMON/.." && pwd)
cd "$MAIN_REPO"
git worktree remove --force "$WORKTREE_PATH" 2>/dev/null || echo "Warning: worktree cleanup failed"
git branch -D "$BRANCH" 2>/dev/null || true
fiIf not in a worktree: skip silently.
- REQUIRED SUB-SKILL: Use
commit-workflow - Follow that skill's full sequence: commit → draft PR → code-review gate → auto-merge → doc update
Human Gates
The commit step (Step 6 handoff) is a human_approval gate in this skill's frontmatter, and the checkpoint stops below ("When to Stop and Ask") are its checkpoint gates — every stop surfaces a request for human input.
Approval Routing (inlined from human-input-framework v2.1.1)
Canonical:
skills/human-input-framework/SKILL.md→ "Approval Routing — Canonical".
Inlined operational excerpt — cross-session resilience: this skill must run in a fresh session
without loading the framework skill first. Only the operational core is inlined; the status table,
store/transport contract, and Slack enablement live canonically (restating them is how the original
six copies drifted apart).
When a gate fires — including this skill's commit handoff gate and the "When to Stop and Ask"
checkpoints — the agent MUST invoke the approval router to surface the request:
# Portable invocation (works from ANY repo checkout — swarm #1402 rollout):
python3 -c "
import os, sys
sys.path.insert(0, os.environ.get('SWARM_ROOT', os.path.expanduser('~/swarm')))
from operations.coordination.approval import request_approval
request_approval('product-implementer', artifact='<plan-doc>.md', context='<checkpoint> approval for issue <N>', requires_human=False)
print('Approval request created')
"⚠️ This excerpt passes requires_human=False, so under the default config
(APPROVAL_AUTO_APPROVE unset, which means 1) the request auto-approves to policy:auto and
touches no human — it does not route to the escalation chain (reports_to). Escalation-chain
routing happens only with APPROVAL_AUTO_APPROVE=0; a genuine human checkpoint needs requires_human=True.
⛔ No dialog pops — do not wait for one. A pending request fires a macOS notification banner
(osascript … display notification), which has no buttons and no answer path; it is best-effort and
silently no-ops on non-macOS/CI/SSH. Reaching a human on Slack needs more than SLACK_BOT_TOKEN +SLACK_APPROVAL_CHANNEL: the bridge derives a different store slug than the router, soSLACK_APPROVAL_FILE must be pinned to the router's store (agent-infra #956) or the request just
waits in ~/.swarm/approvals/<slug>.json.
Detect the answer — read the record, never infer from a shrinking list:
SWARM="${SWARM_ROOT:-$HOME/swarm}/operations/coordination/approval.py"
python3 "$SWARM" --pending --role human # still open
python3 "$SWARM" --status <req_id> # this record's status + reviewerA Slack thread reply sets changes_requested, which leaves --pending without approving;is_approved(..., requires_human=True) also returns False after a Slack button approval (the
bridge overwrites reviewer with the clicking user's id — agent-infra #959). Trust status == 'approved'.
Role-based escalation: with APPROVAL_AUTO_APPROVE=0, omitting requires_human=True pends for
the requester's reports_to role — e.g. product-strategist for product-implementer; onlychain[1] is used, so nothing walks the chain further. Under the default (APPROVAL_AUTO_APPROVE
unset, which means 1) such a request auto-approves and never reaches a human — unless an
escalation keyword (deploy/delete/destroy/migrate/release) appears in the artifact/context,
which pends for a human regardless. requires_human=True
is for genuine human gates (epics, P0) — those are never auto-approved. Never silently proceed
past a denied gate, and never spam: a new request per revision is correct. Dedupe collapses
identical pending requests and identical policy:auto re-fires (so respawns do not grow the
store); human-reviewed records never dedupe.
⚠️ The former 4-step Slack conversation protocol was removed from this skill: it calledapproval_feedback() and request_approval(..., parent=…), neither of which exists in the current
router (SWARM_ROOT) — approval_feedback is not importable there (an from … import raisesImportError) and parent= raises TypeError. See the canonical block and agent-infra #958.
When to Stop and Ask
STOP executing ONLY when:
- Research-Before-Ask protocol is inconclusive on a taxonomy-matching decision
- A P0 gate is triggered (data loss, security, irreversible, cost >$10/mo, legal/compliance)
- Hit a blocker mid-batch (missing dependency, test fails >2 attempts, instruction unclear)
- Plan has critical gaps preventing starting
- You don't understand an instruction AND research doesn't help
ALWAYS research before stopping. Never stop with a bare question — include research findings and why the decision still needs input.
Research before asking for clarification. Run internal + external research first. Ask only when research is inconclusive.
When to Revisit Earlier Steps
Return to Review (Step 1) when:
- Partner updates the plan based on feedback
- Fundamental approach needs rethinking
Don't force through blockers — stop and ask.
Remember
- Review plan critically first
- Follow plan steps exactly
- Run Step 1.5 third-party dependency verification before implementing (Standard + Complex only)
- Don't skip verifications — Step 3 is mandatory after every batch
- Reference skills when plan says to
- Between batches: report and continue (don't gate)
- Stop when blocked or when taxonomy-matching decision arises
- Never start implementation on main/master — every implementation runs in an isolated worktree (#615/#626); the hub stays main + clean.
Label Cleanup
If this skill exits early (error, user abort, or any non-completion path) before Step 6, remove the implementing label:
gh issue edit <ISSUE_NUMBER> --remove-label implementing || trueDo not leave implementing on issues where work is not actively progressing.
Integration
Required workflow skills:
- using-git-worktrees — MANDATORY: always invoked in Step 0; NEVER
git stashin the hub (the M4 dirty-hub gate blocks it — use the#435salvage path), and never a baregit stash pop|applyin a linked worktree either (refs/stashis shared by every worktree of the repo — a peer lane's work can be taken and their ref dropped, #1264; use a WIP commit on your own branch). - writing-plans — Creates the plan this skill executes
- commit-workflow — Complete development after all tasks (Step 6)
Continue following the workflow as mandated by this skill. Do not skip steps.