Finish Feature
Complete epic or feature with verification, archiving, and cleanup (<2 minutes).
Purpose
Ensures clean completion of epics/features: Verify work done, archive docs, cache to Serena, update Beads status.
Philosophy: Automated cleanup + Knowledge preservation + Clean slate
When to Use This Skill
Auto-activated when:
- User says "I'm done with this epic"
- User says "finish the feature"
- User says "close this work"
- User says "archive this epic"
Trigger phrases:
- "I'm done with bd-xyz"
- "finish this feature"
- "close this epic"
- "archive the work"
Workflow
1. Set Beads Context
mcp__plugin_beads_beads__set_context(
workspace_root="/Users/fengning/prime-radiant-ai"
)
2. Get Current Issue
# From branch name
currentBranch=$(git branch --show-current)
# Extract Feature-Key (e.g., feature-bd-xyz → bd-xyz)
# Or from user argument
issueId="bd-xyz" # User specifies
Fetch full issue:
issue = mcp__plugin_beads_beads__show(issue_id=issueId)
3. Verify Completion Criteria
Check prerequisites:
# 1. All children closed?
children=$(bd show $issueId --json | jq -r '.dependents[] | select(.type == "parent-child" or .type == "discovered-from") | .id')
openChildren=()
for child in $children; do
status=$(bd show $child --json | jq -r '.status')
if [ "$status" != "closed" ]; then
openChildren+=("$child")
fi
done
if [ ${#openChildren[@]} -gt 0 ]; then
echo "⚠️ Warning: ${#openChildren[@]} child issue(s) still open:"
for child in "${openChildren[@]}"; do
echo " - $child"
done
echo ""
echo "Options:"
echo " [c] Continue anyway (force close)"
echo " [a] Abort (finish children first)"
read choice
if [ "$choice" = "a" ]; then
echo "Aborted. Finish child issues first:"
for child in "${openChildren[@]}"; do
echo " bd close $child --reason 'Completed'"
done
exit 0
fi
fi
# 2. All commits merged?
git log --oneline --grep="Feature-Key: $issueId" origin/master..HEAD
if [ $? -eq 0 ]; then
echo "⚠️ Warning: Commits exist on feature branch but not merged to master"
echo " Consider creating PR first: say 'create PR'"
echo ""
echo "Continue anyway? [y/n]"
read choice
if [ "$choice" != "y" ]; then
echo "Aborted. Merge work first."
exit 0
fi
fi
4. Offer Doc Archiving
If docs exist, offer archiving:
DOC_DIR="docs/$issueId"
if [ -d "$DOC_DIR" ]; then
echo "📄 Archive documentation?"
echo " [y] Yes - Move to docs/archive/$(date +%Y)-Q$(($(date +%-m)/3+1))/"
echo " [n] No - Keep in docs/$issueId/ (for reference)"
echo " (Recommended: Yes for completed work, No for ongoing reference)"
echo ""
read choice
if [ "$choice" = "y" ]; then
# Create archive directory
ARCHIVE_DIR="docs/archive/$(date +%Y)-Q$(($(date +%-m)/3+1))"
mkdir -p "$ARCHIVE_DIR"
# Move docs
mv "$DOC_DIR" "$ARCHIVE_DIR/"
# Update .beads-meta
echo "Archived: $(date +%Y-%m-%d)" >> "$ARCHIVE_DIR/$issueId/.beads-meta"
# Stage for commit
git add docs/archive/ docs/$issueId/
echo "📦 Archived to: $ARCHIVE_DIR/$issueId/"
else
echo "📁 Kept at: $DOC_DIR/ (not archived)"
fi
fi
Why optional:
- User controls archiving timing
- Some epics serve as ongoing reference (keep in docs/)
- Others are completed and should archive (move to archive/)
5. Cache to Serena
Auto-cache docs to Serena for searchability:
DOC_DIR="docs/$issueId"
ARCHIVE_DIR="docs/archive/$(date +%Y)-Q$(($(date +%-m)/3+1))/$issueId"
# Check which location exists
if [ -d "$ARCHIVE_DIR" ]; then
SOURCE_DIR="$ARCHIVE_DIR"
elif [ -d "$DOC_DIR" ]; then
SOURCE_DIR="$DOC_DIR"
else
echo "ℹ️ No docs to cache (Beads only)"
SOURCE_DIR=""
fi
if [ -n "$SOURCE_DIR" ]; then
# Combine all markdown files into cache
CACHE_CONTENT="# $issueId: ${issue.title}
**Status:** closed
**Type:** ${issue.type}
**Archived:** $(date +%Y-%m-%d)
**Source:** $SOURCE_DIR/
**[CACHE]** This is a searchable cache. Source of truth: git ($SOURCE_DIR/)
---
"
# Append all markdown files
find "$SOURCE_DIR" -name "*.md" -type f | while read file; do
echo "## $(basename $file)" >> cache.tmp
echo "" >> cache.tmp
cat "$file" >> cache.tmp
echo "" >> cache.tmp
echo "---" >> cache.tmp
echo "" >> cache.tmp
done
CACHE_CONTENT+=$(cat cache.tmp)
rm cache.tmp
# Write to Serena
mcp__serena__write_memory(
memory_file_name="${issueId}_archive",
content="$CACHE_CONTENT"
)
echo "💾 Cached to Serena: ${issueId}_archive"
echo " Search with: /search '@serena ${issueId}'"
fi
Why cache:
- Docs become searchable via Serena tools
- Clearly marked as [CACHE] (not source of truth)
- Enables fast lookup without reading git files
- Useful for cross-epic references
6. Close or Verify Beads Issue
Handle issue closure based on type:
ISSUE_TYPE=$(bd show $issueId --json | jq -r '.type')
ISSUE_STATUS=$(bd show $issueId --json | jq -r '.status')
if [ "$ISSUE_TYPE" = "epic" ]; then
# Epics: Close if all children closed (special case)
if [ "$ISSUE_STATUS" != "closed" ]; then
echo "Closing epic: $issueId"
mcp__plugin_beads_beads__close(
issue_id=issueId,
reason="Epic completed - all phases done, archived and cached"
)
echo "✅ Closed: $issueId (epic)"
else
echo "✅ Already closed: $issueId (epic)"
fi
else
# Features/Tasks/Bugs: Verify already closed (should be closed at PR creation)
if [ "$ISSUE_STATUS" != "closed" ]; then
echo "❌ Error: Non-epic issue must already be closed"
echo ""
echo "Current status: $ISSUE_STATUS"
echo "Expected: closed (from PR creation)"
echo "Issue type: $ISSUE_TYPE"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Why this matters:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Non-epic issues should be closed at PR creation time, not during finish."
echo "This ensures JSONL merges atomically with code (no post-merge mutations)."
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Likely cause:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "PR was created before our workflow update (when merge-pr closed issues)."
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Recovery: Close retroactively (one-time fix)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Since work is already merged, close retroactively:"
echo " bd close $issueId --reason 'Retroactive closure - work merged in PR'"
echo " bd sync # Export to JSONL"
echo ""
echo "Then retry: Say 'finish this feature'"
echo ""
echo "Future prevention:"
echo " When creating PRs, create-pull-request skill asks:"
echo " 'Is work complete?' → YES closes issue BEFORE creating PR"
echo ""
exit 1
fi
echo "✅ Already closed: $issueId ($ISSUE_TYPE)"
fi
Why different behavior:
- Epics: Special case - close only when ALL children closed (epic completion)
- Features/Tasks/Bugs: Must be closed at PR creation time (atomic merge)
- Prevents post-merge mutations: All non-epic closures on feature branch
- Epics are long-lived: Often remain open across multiple PRs
7. Archive External Docs Skill (if exists)
Check for epic-specific external docs skill:
DOCS_SKILL=".claude/skills/docs-$issueId"
if [ -d "$DOCS_SKILL" ]; then
echo "📚 Archive external docs skill?"
echo " [y] Yes - Move to .claude/skills/archive/docs-$issueId/"
echo " [n] No - Keep active (for ongoing reference)"
echo " (Recommended: Yes for completed work, No for ongoing reference)"
echo ""
read choice
if [ "$choice" = "y" ]; then
# Create archive directories
mkdir -p .claude/skills/archive
mkdir -p .serena/memories/archive
# Move skill
mv "$DOCS_SKILL" .claude/skills/archive/
# Move cached docs
DOCS_CACHE=".serena/memories/external_$issueId"
if [ -d "$DOCS_CACHE" ]; then
mv "$DOCS_CACHE" .serena/memories/archive/
echo "📦 Archived skill and cache:"
echo " - .claude/skills/archive/docs-$issueId/"
echo " - .serena/memories/archive/external_$issueId/"
echo " To restore: mv .claude/skills/archive/docs-$issueId .claude/skills/ && mv .serena/memories/archive/external_$issueId .serena/memories/"
else
echo "📦 Archived skill: .claude/skills/archive/docs-$issueId/"
fi
# Stage for commit
git add .claude/skills/archive/ .serena/memories/archive/ .claude/skills/ .serena/memories/
else
echo "📁 Kept active: $DOCS_SKILL/ (not archived)"
fi
fi
Why optional:
- User controls when to archive external docs
- Some epics have docs useful across multiple features (keep active)
- Completed epics can archive docs to clean up skill list
- Easy restoration from archive if needed later
8. Commit Cleanup
If archiving happened, commit the changes:
if [ -n "$(git status --porcelain)" ]; then
git add -A
git commit -m "docs: archive $issueId on completion
Moved docs to archive, cached to Serena, closed issue.
Feature-Key: $issueId
Agent: claude-code
Role: cleanup"
echo "📦 Committed cleanup"
fi
9. Branch Cleanup (Post-Merge)
If on feature branch, offer deletion after PR merged:
CURRENT_BRANCH=$(git branch --show-current)
# Only cleanup feature branches
if [[ "$CURRENT_BRANCH" == feature-* ]]; then
# Extract Feature-Key from branch name
FEATURE_KEY=$(echo "$CURRENT_BRANCH" | sed 's/^feature-//')
# Check if PR exists and is merged
PR_STATE=$(gh pr view --json state -q '.state' 2>/dev/null || echo "NOT_FOUND")
if [ "$PR_STATE" = "MERGED" ]; then
echo ""
echo "🧹 Branch cleanup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "PR merged successfully! Clean up feature branch?"
echo ""
echo "Options:"
echo " [b] Delete both local and remote (recommended)"
echo " [l] Delete local branch only"
echo " [r] Delete remote branch only"
echo " [n] Keep branches (manual cleanup later)"
echo ""
read -p "Choice: " cleanup_choice
case "$cleanup_choice" in
b|B)
# Delete both
echo ""
echo "Switching to master..."
git checkout master
git pull origin master
echo "Deleting local branch: $CURRENT_BRANCH"
git branch -d "$CURRENT_BRANCH"
echo "Deleting remote branch: origin/$CURRENT_BRANCH"
git push origin --delete "$CURRENT_BRANCH"
echo ""
echo "✅ Deleted local and remote branches"
;;
l|L)
# Delete local only
echo ""
echo "Switching to master..."
git checkout master
git pull origin master
echo "Deleting local branch: $CURRENT_BRANCH"
git branch -d "$CURRENT_BRANCH"
echo ""
echo "✅ Deleted local branch (remote still exists)"
;;
r|R)
# Delete remote only
echo "Deleting remote branch: origin/$CURRENT_BRANCH"
git push origin --delete "$CURRENT_BRANCH"
echo ""
echo "✅ Deleted remote branch (local still exists)"
echo " To delete local: git checkout master && git branch -d $CURRENT_BRANCH"
;;
n|N)
echo ""
echo "ℹ️ Branches kept. Manual cleanup:"
echo " git checkout master"
echo " git branch -d $CURRENT_BRANCH"
echo " git push origin --delete $CURRENT_BRANCH"
;;
*)
echo ""
echo "⚠️ Invalid choice. Skipping branch cleanup."
echo " Manual cleanup: git checkout master && git branch -d $CURRENT_BRANCH"
;;
esac
elif [ "$PR_STATE" = "OPEN" ]; then
echo ""
echo "ℹ️ PR still open (not merged yet)"
echo " Merge first, then run cleanup"
echo " Current branch: $CURRENT_BRANCH"
elif [ "$PR_STATE" = "NOT_FOUND" ]; then
echo ""
echo "⚠️ No PR found for branch: $CURRENT_BRANCH"
echo " Create PR or manually clean up:"
echo " git checkout master"
echo " git branch -d $CURRENT_BRANCH"
fi
else
# On master or other branch
echo ""
echo "ℹ️ Not on feature branch (current: $CURRENT_BRANCH)"
echo " No branch cleanup needed"
fi
Why after PR merged:
- Safe deletion (work is in master)
- Automated workflow (no manual git commands)
- User controls timing (can defer cleanup)
- Convention enforcement (feature-* branches only)
10. Confirm and Suggest Next
Show completion summary:
✅ Finished: $issueId ($issue.type)
📋 Status: closed
📄 Docs: ${archived ? "Archived to $ARCHIVE_DIR/" : "Kept at $DOC_DIR/"}
💾 Cache: ${cached ? "Serena (.serena/memories/${issueId}_archive.md)" : "None"}
🧹 Cleanup: ${committed ? "Committed" : "No changes"}
🌿 Branch: ${deleted ? "Deleted (local + remote)" : "Kept (manual cleanup)"}
Next steps:
- Find new work: bd ready
- View stats: bd stats
- Start new feature: Say "create <feature>"
If parent epic exists:
Parent epic: ${parent_id}
- Progress: ${parent.dependents.filter(closed).length}/${parent.dependents.length} children closed
- Status: ${parent.status}
- Next: ${nextSibling ? nextSibling.id : 'All phases complete!'}
Best Practices
Do
✅ Always verify children closed before closing parent ✅ Archive completed work (frees up docs/ for active work) ✅ Cache to Serena for searchability ✅ Check commits merged before closing ✅ Commit archiving changes with Feature-Key ✅ Show parent epic progress for context ✅ Understand closure timing: Epics close here, features close at PR creation
Don't
❌ Force close with open children (unless justified) ❌ Archive ongoing reference docs (keep in docs/ for access) ❌ Skip Serena caching (loses searchability) ❌ Forget to commit cleanup changes ❌ Try to close non-epic issues (should already be closed from PR creation)
Integration with Other Skills
Workflow progression:
- issue-first → Create tracking issue
- Implementation → Code the feature
- sync-feature-branch → Commit with Feature-Key
- create-pull-request → Open PR
- fix-pr-feedback → Address CI/review issues (if needed)
- merge-pr → Merge to master
- finish-feature → Close and cleanup (THIS SKILL)
Related skills:
- issue-first: Creates the issue this skill closes
- sync-feature-branch: Commits work with Feature-Key
- create-pull-request: Creates PR for review
- merge-pr: Merges work to master
What This Skill Does
✅ Verifies completion criteria (children closed, commits merged) ✅ Offers doc archiving (docs/ → docs/archive/YYYY-QQ/) ✅ Caches docs to Serena for searchability ✅ For epics: Closes epic if all children closed ✅ For features/tasks/bugs: Verifies already closed (from PR creation) ✅ Commits cleanup changes with Feature-Key ✅ Offers branch deletion after PR merged (local + remote) ✅ Automatically switches to master after cleanup ✅ Shows parent epic progress (if applicable) ✅ Suggests next work (bd ready)
What This Skill DOESN'T Do
❌ Force closure (user can abort if children open) ❌ Delete docs (archives for reference) ❌ Auto-merge PRs (merge-pr skill handles that) ❌ Create new issues (issue-first handles that) ❌ Cascade close children (user closes children first) ❌ Close non-epic issues (already closed at PR creation time by create-pull-request skill)
Examples
Example 1: Finish Feature with Archiving
User: "I'm done with bd-xyz"
finish-feature activates:
1. Verify: All children closed ✓
2. Verify: Commits merged ✓
3. Offer archiving: User chooses [y] Yes
4. Archive: docs/bd-xyz/ → docs/archive/2025-Q1/bd-xyz/
5. Cache: Serena memory created (bd-xyz_archive.md)
6. Close: bd close bd-xyz --reason "Completed - archived and cached"
7. Commit: git commit -m "docs: archive bd-xyz on completion"
✅ Finished: bd-xyz (feature)
📄 Docs: Archived to docs/archive/2025-Q1/bd-xyz/
💾 Cache: Serena (.serena/memories/bd-xyz_archive.md)
🧹 Cleanup: Committed
Next: bd ready (find new work)
Example 2: Finish Epic with Open Children (Abort)
User: "finish bd-abc"
finish-feature:
1. Verify children:
⚠️ Warning: 2 child issues still open:
- bd-abc.2
- bd-abc.3
Options:
[c] Continue anyway (force close)
[a] Abort (finish children first)
User chooses: [a] Abort
Aborted. Finish child issues first:
bd close bd-abc.2 --reason 'Completed'
bd close bd-abc.3 --reason 'Completed'
Skill exits.
Example 3: Finish with No Archiving
User: "finish bd-docs" (external docs tracking epic)
finish-feature:
1. Verify: All children closed ✓
2. Verify: Commits merged ✓
3. Offer archiving: User chooses [n] No (ongoing reference)
4. Skip archiving
5. Cache: Serena memory created (bd-docs_archive.md)
6. Close: bd close bd-docs --reason "Completed - cached"
✅ Finished: bd-docs (epic)
📄 Docs: Kept at docs/bd-docs/ (not archived)
💾 Cache: Serena (.serena/memories/bd-docs_archive.md)
🧹 Cleanup: No changes
Next: bd ready
Troubleshooting
Children Still Open
Symptom: Skill warns about open children
Solution:
- Check which children:
bd show bd-xyz --json | jq '.dependents' - Close each child:
bd close bd-xyz.1 --reason 'Completed' - Re-run finish-feature
Commits Not Merged
Symptom: Skill warns commits exist on branch but not master
Solution:
- Create PR: Say "create PR"
- Merge PR: Say "merge it" (after CI passes)
- Re-run finish-feature
Docs Not Found
Symptom: "No docs to cache (Beads only)"
Why: Issue tracked in Beads only, no docs/ created Solution: Normal for tasks/bugs, nothing to do
Related Skills
- issue-first: Creates tracking issue (start of lifecycle)
- sync-feature-branch: Commits work with Feature-Key (during work)
- create-pull-request: Opens PR for review (before merge)
- merge-pr: Merges to master (before finish)
- finish-feature: Closes and cleans up (THIS SKILL, end of lifecycle)
Resources
Beads reference:
- Official AGENTS.md: https://github.com/steveyegge/beads/blob/main/AGENTS.md
- Close command:
bd close <id> --reason <reason> - Dependencies: parent-child, discovered-from, blocks
Serena reference:
- Write memory:
mcp__serena__write_memory - Search memory:
/search '@serena <term>' - Cache convention: [CACHE] marker in content
Last Updated: 2025-01-14 Skill Type: Workflow Average Duration: <2 minutes Related Docs:
- https://github.com/steveyegge/beads/blob/main/AGENTS.md
- .claude/skills/issue-first/SKILL.md
- .claude/skills/sync-feature-branch/SKILL.md
- .claude/skills/create-pull-request/SKILL.md
- .claude/skills/merge-pr/SKILL.md