| name | wrapup |
| description | Close out a completed feature — update AIF artifacts, log knowledge, and summarize |
| argument-hint | REQ-xxx ID to wrap up |
/wrapup — Feature Completion Wrap-Up
You are closing out a completed feature after it has been merged. This skill ensures AIF artifacts are finalized, knowledge is captured, and the team has a clear summary of what shipped.
Ethos
!sh .aif/partials/ethos-include.sh 2>/dev/null || sh ~/.claude/skills/partials/ethos-include.sh
Context
- Active specs: !
grep -rl 'status: approved\|status: in-progress\|status: complete' .aif/specs/*/requirement.md 2>/dev/null | tail -20 || echo "No specs found"
- Knowledge directory: !
ls .aif/knowledge/ 2>/dev/null || echo "No knowledge directory"
- Current branch: !
git branch --show-current 2>/dev/null || echo "Not a git repo"
- Recent merges: !
git log --oneline --merges -10 2>/dev/null || echo "No merge history"
Input
Target: $ARGUMENTS
Prerequisites
Before proceeding, verify that .aif/context/architecture.md and .aif/context/conventions.md exist. If any of these files are missing, stop and tell the user: "The .aif/ structure hasn't been initialized. Run /init first to set up the project context."
Instructions
Step 1: Identify the Feature
- If given a REQ ID, locate all artifacts under
.aif/specs/REQ-xxx-*/
- If no REQ ID given, infer from the current branch name or recent merge commits
- Read the requirement spec, architecture doc, and all task files
- Detect repository mode — read
.aif/config.yml in the primary repo. If it declares more than one entry under repos:, this is cross-repo mode; otherwise single-repo mode. In cross-repo mode also read pipeline-state.json from the spec directory — it holds the per-repo branch/worktree/PR/merge state.
Step 2: Commit, Push, and Merge
Determine the repo set to operate on:
- Single-repo mode: operate on the current repo only. Skip to the single-repo steps below.
- Cross-repo mode from
/proceed: pipeline-state.json already lists touched repos; each repos[<id>].merged reflects whether /proceed Phase 8 already merged that PR. Walk mergeOrder and for each repo either confirm it's merged (no-op) or run the single-repo merge sequence inside that repo's worktree.
- Cross-repo mode standalone: no
pipeline-state.json — fall back to detecting touched repos from the config and checking for feature branches/open PRs in each. Proceed with the single-repo merge sequence in each repo that has pending work, in merge_order from the config.
Single-repo merge sequence — run this block inside each target repo's worktree (same mechanics as before):
- Branch check FIRST — never commit on
main. Run git -C <worktree> branch --show-current. If it reports main (or master), stop: create a feature branch (e.g., agent/REQ-xxx-slug or feat/REQ-xxx-slug) and switch to it with git checkout -b <branch> BEFORE touching any files. If you're already on a worktree branch from /proceed Phase 0, continue.
- Check
git -C <worktree> status and git -C <worktree> diff for any uncommitted changes related to the feature
- If there are uncommitted changes:
- Stage all relevant files (avoid secrets,
.env, credentials)
- Create a commit with message:
feat(REQ-xxx): <summary of changes>
- Include
Co-Authored-By: Claude <noreply@anthropic.com>
- Push the branch to remote with
git -C <worktree> push -u origin <branch>
- If no PR exists for this branch, create one using
aif_forge_pr_create (source partials/forge.sh in the same fence; from inside the worktree, or with -R <owner/repo>) with a summary of what shipped — PR ops route through the forge adapter, never direct gh (REQ-520 BR-1)
- If CI checks exist, monitor the pipeline with
gh run watch and report the result
- Rebase onto current main before merging — in a sprint or long-running pipeline, upstream
main may have advanced since the branch was cut. Run git -C <worktree> fetch origin main and check whether the branch is behind: git -C <worktree> merge-base --is-ancestor origin/main HEAD. If that command fails (exit 1), the branch is behind main and must be updated:
git -C <worktree> rebase origin/main
- If there are conflicts, STOP and surface them to the user — do not try to resolve semantic conflicts blindly
- On clean rebase, force-push with lease:
git -C <worktree> push --force-with-lease
- Re-run
gh pr checks <prUrl> and wait for CI to re-pass before merging
- Verify PR status is mergeable:
aif_forge_pr_view <prUrl> --json mergeable,mergeStateStatus should report MERGEABLE and a clean merge state (on GitHub; ADO normalizes via ). If not, stop and surface the reason.
Cross-repo aggregate log: after walking every touched repo, emit a one-line summary per repo: <repo-id>: merged <prUrl>, worktree cleaned or <repo-id>: already merged (from /proceed Phase 8).
Step 3: Update AIF Artifact Statuses
- Set the requirement's frontmatter status to
complete
- Set all task statuses to
complete
- Update the
updated date on all modified artifacts to today's date
- If any tasks were deferred or descoped, note them in the requirement file under a "Deferred" section
- If
pipeline-state.json exists in the spec directory, update it: set "completed": true and add a final entry to phaseHistory
Step 4: Capture Knowledge
Evaluate whether any decisions, patterns, or lessons should be persisted:
Architectural Decisions
- Were any new patterns introduced? If so, propose an update to
.aif/context/architecture.md
- Were any existing patterns modified or deprecated?
Assumptions Validated or Invalidated
Lessons Learned
Before the gate check, create a skill-invocation flag and capture the start time for telemetry (REQ-424 ghost-skip detection):
. .aif/partials/delegate-tools-path.sh 2>/dev/null || . ~/.claude/skills/partials/delegate-tools-path.sh
flag=$("$DELEGATE_TOOLS"/skill-flag.sh create)
trap '"$DELEGATE_TOOLS"/skill-flag.sh clear "$flag" 2>/dev/null || true' EXIT
"$DELEGATE_TOOLS"/skill-flag.sh mark "$flag" start_s "$(date -u +%s)"
The telemetry state (start_s, invoked, exit, reason) is persisted to the
flag-file sidecar via skill-flag.sh mark, NOT to shell variables, because
SKILL.md fenced blocks do not share shell state (single-fence-safe telemetry,
REQ-522 BR-4). The resolution block reads it back with skill-flag.sh read.
Decide drafting strategy via the shared predicate (REQ-416 ADR-2 — see partials/delegate-gate.md), then proceed down the appropriate branch:
. .aif/partials/delegate-gate.sh 2>/dev/null || . ~/.claude/skills/partials/delegate-gate.sh
. .aif/partials/delegate-tools-path.sh 2>/dev/null || . ~/.claude/skills/partials/delegate-tools-path.sh
aif_delegate_gate_check; gate=$?
"$DELEGATE_TOOLS"/skill-flag.sh mark "$flag" reason "$AIF_DELEGATE_GATE_REASON"
case $gate in
0) ;;
1) ;;
2) ;;
esac
Delegated drafting (gate passes — aif-read is on PATH and AIF_DISABLE_DELEGATE is not 1):
MANDATORY — no agent discretion. When the gate passes, invoking aif-read to draft the lessons is required, not optional. The only acceptable non-delegated outcome on the gate-pass path is: aif-read was actually invoked and exited non-zero (→ api-error fallback). Drafting the lessons-learned yourself from the transcript instead of calling aif-read — for ANY reason, including "short session", "few lessons", or "faster to just write them" — is a compliance violation, NOT a fallback. emit-telemetry.sh mechanically rewrites any gate-pass fallback record whose reason is not api-error into a ghost-skip, so a hand-written reason cannot disguise a skipped call — the skip surfaces in check-delegation.sh counts regardless of how the emit is labeled.
- Locate the Claude Code session JSONL whose recent content mentions the active REQ — content-anchored discovery (REQ-423). The prior heuristic ("newest JSONL under the repo-root-encoded path") silently picked the wrong transcript when a session was opened at a parent directory and later navigated into the repo. The fix walks the encoded-path tree from the repo root up to (and including)
$HOME, collects candidate JSONLs at each level, and picks the one whose last 200 lines contain a word-boundary match for the active REQ id. Falls back to newest overall (with a stderr warning) if no candidate mentions the active REQ; falls through to direct drafting if no candidates exist at all. Emits exactly one stderr line per invocation stating which JSONL was chosen and why.
ROOT=$(git rev-parse --show-toplevel 2>/dev/null | sed 's|/\.worktrees/.*$||')
HOME_NORM="${HOME%/}"
CANDIDATES=()
DIR="$ROOT"
case "$DIR/" in
"$HOME_NORM"/|"$HOME_NORM"/*) ;;
*) DIR="" ;;
esac
if [ -n "$DIR" ] && [ -n "$HOME_NORM" ]; then
while [ -n "$DIR" ] && [ != ];
ENCODED=$( | sed )
BASENAME=
ENC_DIR=
*..*) ;;
*) | grep -qE && [ -d ];
IFS= -r f;
[ -n ] && CANDIDATES+=()
< <( -t 2>/dev/null | grep )
;;
[ = ] &&
DIR=$( )
JSONL=
[ -eq 0 ];
>&2
[ -n ];
c ;
-n 200 2>/dev/null | grep -qwF ;
JSONL=
>&2
[ -z ];
JSONL=
[ -n ];
>&2
>&2
. .aif/partials/delegate-tools-path.sh 2>/dev/null || . ~/.claude/skills/partials/delegate-tools-path.sh
/skill-flag.sh mark jsonl
(Claude Code prefixes encoded project paths with under ; the strips the leading before substitution to avoid a double-prefix. The walk terminates at per BR-6 — see REQ-423 architecture ADR-2.)
Fallback drafting (gate fails — aif-read not on PATH, or AIF_DISABLE_DELEGATE=1, or not opted in):
-
Emit /wrapup: aif-read unavailable — Claude drafting lesson directly to stderr (or /wrapup: aif-read disabled via AIF_DISABLE_DELEGATE — Claude drafting lesson directly when the gate failed specifically because AIF_DISABLE_DELEGATE=1). Skip this emit when arriving here from a delegation-failure fall-through above — those branches emit their own combined single line (BR-4: one line per invocation).
-
Claude drafts the lesson directly from in-context conversation memory. Consider:
- Any surprises during implementation?
- Approaches that didn't work and why?
- Things that worked particularly well?
-
Log notable lessons to .aif/knowledge/lessons/ if they'd help future work
-
Use the lesson template (check .aif/templates/lesson-template.md first, fall back to ~/.claude/skills/templates/lesson-template.md)
-
Filename format is LESSON-xxx-slug.md (e.g., LESSON-041-signed-url-ttl-mismatch.md). This is the ONLY permitted naming scheme — do not use date-prefixed names (2026-MM-DD-…md) or bare numeric prefixes (034-…md). Slugs are lowercase kebab-case, ≤6 words.
-
Allocate the next ID atomically via the global ~/.claude/.global-next-lesson counter (shared across all repos for unique IDs, mirroring the REQ/BUG counters — see LESSON-004; directory scans also race against concurrent /sprint pipelines — LESSON-110). The counter is now a cache, not the authority — the remote is the source of truth (REQ-518): allocation derives the remote high-water, takes max(remote, local) + 1, and fast-forwards the local counter, all inside the shared mkdir-lock with its LESSON-014 symlink pre-check. The lock path ~/.claude/.global-next-lesson.lock.d is shared with /bugfix so concurrent /wrapup and /bugfix runs mutually exclude. Allocate via the shared partials/id-alloc.sh helper (BR-5 — the lock block + its rationale live in the partial). Source it and call aif_alloc_id in the same fenced block (the cross-fence-fn rule — see conventions.md "Bash in skills"):
. .aif/partials/id-alloc.sh 2>/dev/null || . ~/.claude/skills/partials/id-alloc.sh
LESSON_NUM=$(aif_alloc_id lesson)
[ -n ] || { >&2; 1; }
Resolve telemetry mode and emit (REQ-424). After the delegated OR fallback drafting path completes, before continuing to Convention Updates. Emit telemetry ONLY by sourcing and calling the shared resolver in the SAME fenced block — it derives mode/reason/gate_result/duration_ms from the flag-file sidecar the steps above marked, so no shell variable crosses a fence boundary (REQ-522 BR-4). Never hand-construct a telemetry line:
. .aif/partials/emit-step-telemetry.sh 2>/dev/null || . ~/.claude/skills/partials/emit-step-telemetry.sh
_aif_emit_step_telemetry wrapup Step-4-Lessons-Learned
Convention Updates
- Were any new conventions established? Propose updates to
.aif/context/conventions.md
- Were any existing conventions found to be problematic?
Step 5: Generate Ship Summary
Create a concise summary suitable for sharing with the team. In cross-repo mode, list each repo/PR under a Repos section.
Single-repo template:
## REQ-xxx: Feature Title
**Status**: Shipped
**Branch**: agent/REQ-xxx-slug
**PR**: #nn
**Merged**: YYYY-MM-DD
### What shipped
- Bullet points of user-facing or developer-facing changes
### Key decisions
- Notable architectural or design decisions made during implementation
### Metrics
- Files changed: N
- Lines added/removed: +N / -N
- Tests added: N
- Coverage impact: X% -> Y% (if measurable)
### Deferred items
- Any work explicitly postponed for future
### Follow-up needed
- Any remaining work, monitoring, or verification required
Cross-repo template (replace the single PR/Branch lines with a Repos table):
## REQ-xxx: Feature Title
**Status**: Shipped
**Merged**: YYYY-MM-DD
### Repos
| Repo | Branch | PR | Files | +/- |
|------|--------|----|-------|-----|
| api | feat/REQ-xxx-... | #12 | 7 | +320 / -15 |
| web | feat/REQ-xxx-... | #45 | 3 | +88 / -2 |
| mobile | feat/REQ-xxx-... | #31 | 5 | +210 / -40 |
### What shipped
- Bullet points (call out cross-repo changes like new API contracts explicitly)
### Key decisions
### Metrics (aggregate across repos)
### Deferred items
### Follow-up needed
Step 6: Deploy
Walk the touched repos and deploy each deployable component. Read .aif/config.yml for stack and deploy config — every step below is conditional on what the project actually declares.
- Determine which components were changed by examining each touched repo's PR/commits. Deploy decisions per repo:
- Backend services declared under
services:: If the project's CI/CD already deploys on merge (typical for cloud-run + github-actions), confirm the deploy succeeded for each touched service — gcloud run services describe <service> --project=<gcp.production_project> --region=<services[<id>].region or gcp.default_region>. If the project doesn't deploy on merge, run the appropriate manual deploy.
- iOS (when
stack.frontends includes ios AND the iOS repo was touched): read ios.deploy_targets, ios.derived_data_clean, and ios.deploy_command from the primary's .aif/config.yml. If derived_data_clean is true, run rm -rf ~/Library/Developer/Xcode/DerivedData/* first. Then cd <ios-repo-worktree-or-checkout> and run <ios.deploy_command>, deploying to every device in deploy_targets. Don't skip a device.
- Web frontend: Confirm CI/CD deploy succeeded.
- Infrastructure changes: Note that the IaC apply (Terraform/Pulumi/etc.) is needed and confirm with user.
- If no touched repo has deployable changes (e.g., only AIF docs changed), skip this step.
- In cross-repo mode, emit a one-line deploy status per touched repo in the ship summary.
Step 7: Clean Up
- Check for any temporary files, debug logging, or feature flags that should be removed
- Verify CLAUDE.md or other docs don't need updates based on what shipped
Step 8: Recommend Next Steps
- If deferred items exist: "Consider creating
/spec for deferred items: [list]"
- If follow-up monitoring is needed: "Monitor [what] for [how long]"
- If conventions were updated: "Review
.aif/context/conventions.md changes"
- Otherwise: "Feature complete. No follow-up needed."