Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 14 additions & 67 deletions .claude/commands/finish.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

This skill handles the end of a development workflow:
1. Reads the full contents of PLAN.md
2. Adds the complete plan details to the PR description (or as a PR comment if the description is already long)
2. Posts the complete plan details as a PR comment
3. Removes ephemeral planning documents
4. Creates a cleanup commit
5. Optionally merges the PR
Expand All @@ -23,14 +23,14 @@

- `--merge`: After cleanup, merge the PR using `gh pr merge --squash`
- `--dry-run`: Preview actions without executing them
- `--no-pr-update`: Skip updating PR description
- `--no-comment`: Skip posting plan details as PR comment

## Steps

### 1. Verify Prerequisites

Check that we're ready to finish:

Check notice on line 32 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'we're'.
- Confirm we're in a git worktree (not main branch)

Check notice on line 33 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'we're'.
- Check for PLAN.md existence
- Verify no uncommitted changes (or warn)
- Check for open PR on current branch
Expand All @@ -50,7 +50,7 @@

Read the entire PLAN.md file and preserve all sections:
- Source metadata
- Objective

Check warning on line 53 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.TooWordy

'Objective' is too wordy.
- All tasks (completed and incomplete)
- Notes, decisions, and research

Expand All @@ -59,42 +59,12 @@
PLAN_CONTENTS="$(cat PLAN.md)"
```

### 3. Add Plan Details to PR
### 3. Post Plan Details as PR Comment

Add the **complete** PLAN.md contents to the PR. Do not summarize or abbreviate
the plan -- include all details so they are preserved after the file is deleted.

**Strategy:**
- If the PR description is short (under ~2000 characters), append the full plan
to the PR description.
- If the PR description is already long, post the full plan as a PR comment
instead, to keep the description readable.

**Appending to PR description:**

```bash
# Get current PR body
CURRENT_BODY="$(gh pr view --json body -q '.body')"

# Update PR with full plan details appended
gh pr edit --body "$(cat <<EOF
${CURRENT_BODY}

---

## Development Plan

<details>
<summary>Full plan details (from PLAN.md)</summary>

${PLAN_CONTENTS}

</details>
EOF
)"
```

**Posting as PR comment (if description is already long):**
Post the **complete** PLAN.md contents as a PR comment. Do not summarize or
abbreviate the plan -- include all details so they are preserved after the file

Check notice on line 65 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'are'.

Check warning on line 65 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.Passive

'are preserved' may be passive voice. Use active voice if you can.
is deleted. Always use a comment (not the PR description) to keep the

Check notice on line 66 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'is'.

Check warning on line 66 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.Passive

'is deleted' may be passive voice. Use active voice if you can.
description clean and editable.

```bash
gh pr comment --body "$(cat <<EOF
Expand All @@ -111,7 +81,7 @@

### 4. Remove Ephemeral Files

Delete planning documents that shouldn't be merged:

Check warning on line 84 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.Passive

'be merged' may be passive voice. Use active voice if you can.

Check notice on line 84 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'be'.

```bash
# Remove PLAN.md and any other ephemeral docs
Expand Down Expand Up @@ -182,7 +152,7 @@

### Actions:
1. Read full PLAN.md contents (42 lines, all sections preserved)
2. Updated PR #6789 description with complete plan details
2. Posted full plan details as comment on PR #6789
3. Removed PLAN.md
4. Created commit: "chore: remove planning docs"
5. Pushed to origin
Expand All @@ -205,29 +175,7 @@
Done! PR merged successfully.
```

### Example 3: Plan added as comment (long PR description)
```
User: /finish

Agent:
## Finishing development work

### Prerequisites
- Branch: docs-v2-issue6763
- PLAN.md: Found
- PR description: 3200 characters (long)

### Actions:
1. Read full PLAN.md contents
2. PR description already long -- posted full plan as PR comment on #6789
3. Removed PLAN.md
4. Created commit: "chore: remove planning docs"
5. Pushed to origin

Ready for merge!
```

### Example 4: Incomplete tasks warning
### Example 3: Incomplete tasks warning
```
User: /finish

Expand All @@ -246,7 +194,7 @@
[continues with normal flow]
```

### Example 5: Dry run
### Example 4: Dry run
```
User: /finish --dry-run

Expand All @@ -255,7 +203,7 @@

Would perform the following actions:
1. Read full PLAN.md contents
2. Add complete plan details to PR #6789 description (or comment)
2. Post full plan details as comment on PR #6789
3. Run: git rm PLAN.md
4. Run: git commit -m "chore: remove planning docs"
5. Run: git push
Expand All @@ -266,10 +214,9 @@
## Notes

- Always reads full PLAN.md contents before deleting it
- Preserves complete plan details (not just a summary) in the PR for future reference
- If the PR description is short, appends plan details in a collapsible `<details>` block
- If the PR description is already long, posts the full plan as a PR comment instead
- Preserves complete plan details (not just a summary) as a PR comment for future reference
- Always uses a PR comment, keeping the PR description clean and editable
- Squash merge is recommended to keep main branch clean

Check warning on line 219 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.Passive

'is recommended' may be passive voice. Use active voice if you can.

Check notice on line 219 in .claude/commands/finish.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'is'.
- The deleted PLAN.md remains in branch history (recoverable if needed)
- Works with GitHub Actions cleanup as a fallback safety net
- Use `--no-pr-update` if you want to write the PR description manually
- Use `--no-comment` to skip posting plan details to the PR
103 changes: 69 additions & 34 deletions .github/workflows/cleanup-ephemeral-docs.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# Cleanup Ephemeral Planning Documents
#
# This workflow automatically removes PLAN.md and other ephemeral planning
# documents when a PR is merged. This serves as a safety net in case the
# documents from the main branch. It serves as a safety net in case the
# /finish skill wasn't used before merging.
#
# Triggers:
# 1. pull_request_target (closed+merged) - catches files merged via PR
# 2. push to main branches - catches files pushed directly or via squash
# merge (path-filtered to only run when ephemeral files are present)
#
# Strategy: Creates a cleanup PR instead of pushing directly to the
# protected main branch, since branch protection rules block direct pushes.
#
# Ephemeral documents are temporary files used during development that
# shouldn't persist on the main branch:
# - PLAN.md: Planning and task tracking
Expand All @@ -12,16 +20,14 @@
# These files are tracked on feature branches but should be deleted before
# merge. If they slip through, this action cleans them up.
#
# The "Preserve plan details" step only runs for PR merges (not direct
# pushes) since there's no source PR to comment on for direct pushes.
#
# Security Note: Uses pull_request_target to ensure write permissions for
# PRs from forks. This is safe because:
# 1. The workflow only checks out the base branch (not PR code)
# 2. File paths are validated (hardcoded list of allowed files)
# 3. No PR-controlled code is executed
#
# To use in your repo:
# 1. Copy this file to .github/workflows/cleanup-ephemeral-docs.yml
# 2. Update the 'branches' list to match your main branch (master or main)
# 3. Commit and push

name: Cleanup ephemeral docs

Expand All @@ -31,11 +37,20 @@ on:
branches:
- master
- main
push:
branches:
- master
- main
paths:
- 'PLAN.md'
- 'HANDOVER.md'

jobs:
cleanup:
# Only run when PR is actually merged (not just closed)
if: github.event.pull_request.merged == true
# Run when PR is merged OR when ephemeral files are pushed directly
if: >-
(github.event_name == 'push') ||
(github.event_name == 'pull_request_target' && github.event.pull_request.merged == true)
runs-on: ubuntu-latest

permissions:
Expand All @@ -46,21 +61,24 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
ref: ${{ github.event.pull_request.base.ref || github.ref }}
fetch-depth: 0

- name: Check for ephemeral files
id: check
run: |
FILES_TO_REMOVE=""
FILE_LIST=""

if [ -f "PLAN.md" ]; then
FILES_TO_REMOVE="$FILES_TO_REMOVE PLAN.md"
FILE_LIST="${FILE_LIST:+$FILE_LIST, }PLAN.md"
echo "Found: PLAN.md"
fi

if [ -f "HANDOVER.md" ]; then
FILES_TO_REMOVE="$FILES_TO_REMOVE HANDOVER.md"
FILE_LIST="${FILE_LIST:+$FILE_LIST, }HANDOVER.md"
echo "Found: HANDOVER.md"
fi

Expand All @@ -71,67 +89,84 @@ jobs:
echo "Files to remove:$FILES_TO_REMOVE"
echo "has_files=true" >> $GITHUB_OUTPUT
echo "files=$FILES_TO_REMOVE" >> $GITHUB_OUTPUT
echo "file_list=$FILE_LIST" >> $GITHUB_OUTPUT
fi

- name: Preserve plan details on PR
if: steps.check.outputs.has_files == 'true'
- name: Preserve plan details on source PR
if: >-
steps.check.outputs.has_files == 'true' &&
github.event_name == 'pull_request_target'
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}

# Post full PLAN.md contents as a PR comment before removing
if [ -f "PLAN.md" ]; then
PLAN_BODY="## Development Plan (preserved by cleanup workflow)
gh pr comment "$PR_NUMBER" --body "$(cat <<'EOFCOMMENT'
## Development Plan (preserved by cleanup workflow)

Full plan details from \`PLAN.md\`, preserved before automated cleanup:
Full plan details from `PLAN.md`, preserved before automated cleanup:

---

$(cat PLAN.md)"
gh pr comment "$PR_NUMBER" --body "$PLAN_BODY"
EOFCOMMENT
cat PLAN.md)"
Comment on lines 110 to +113
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The heredoc used to build the PR comment body is indented, so the EOFCOMMENT terminator won’t be recognized by bash (heredoc delimiters must start at column 1 unless using <<- with tabs). This will cause the step to hang or fail and prevent plan preservation. Consider left-aligning the terminator line (no leading whitespace) or avoid heredocs here (e.g., use printf/cat without heredoc).

Copilot uses AI. Check for mistakes.
echo "Posted PLAN.md contents to PR #$PR_NUMBER"
fi

if [ -f "HANDOVER.md" ]; then
HANDOVER_BODY="## Handover Notes (preserved by cleanup workflow)
gh pr comment "$PR_NUMBER" --body "$(cat <<'EOFCOMMENT'
## Handover Notes (preserved by cleanup workflow)

Full contents from \`HANDOVER.md\`, preserved before automated cleanup:
Full contents from `HANDOVER.md`, preserved before automated cleanup:

---

$(cat HANDOVER.md)"
gh pr comment "$PR_NUMBER" --body "$HANDOVER_BODY"
EOFCOMMENT
cat HANDOVER.md)"
Comment on lines 123 to +126
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same heredoc indentation issue as above: the EOFCOMMENT terminator is indented inside the run: | block, so bash won’t terminate the heredoc correctly. This can break posting HANDOVER.md contents to the source PR.

Copilot uses AI. Check for mistakes.
echo "Posted HANDOVER.md contents to PR #$PR_NUMBER"
fi

- name: Remove ephemeral files
- name: Create cleanup PR
if: steps.check.outputs.has_files == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
TARGET_BRANCH="${{ github.event.pull_request.base.ref || github.ref_name }}"
CLEANUP_BRANCH="chore/cleanup-ephemeral-docs-$(date +%Y%m%d-%H%M%S)"
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLEANUP_BRANCH is only timestamp-based to the second. Two runs starting within the same second (e.g., simultaneous merges) can collide on the same branch name and cause git push/PR creation failures. Consider incorporating ${{ github.run_id }} / ${{ github.run_attempt }} (or github.sha) into the branch name to guarantee uniqueness.

Suggested change
CLEANUP_BRANCH="chore/cleanup-ephemeral-docs-$(date +%Y%m%d-%H%M%S)"
CLEANUP_BRANCH="chore/cleanup-ephemeral-docs-${{ github.run_id }}-${{ github.run_attempt }}-$(date +%Y%m%d-%H%M%S)"

Copilot uses AI. Check for mistakes.

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Pull latest changes to avoid non-fast-forward errors
# If rebase fails due to conflicts, fall back to regular merge
if ! git pull --rebase origin ${{ github.event.pull_request.base.ref }}; then
echo "Rebase failed, falling back to merge"
git rebase --abort
git pull --no-rebase --no-edit origin ${{ github.event.pull_request.base.ref }}
fi
# Create cleanup branch from current HEAD
git checkout -b "$CLEANUP_BRANCH"

# Remove files
# Remove ephemeral files
git rm -f ${{ steps.check.outputs.files }}

# Commit with skip-ci to avoid triggering other workflows
git commit -m "chore: remove ephemeral planning docs [skip ci]

Automated cleanup of development planning documents.
These files are used during development but shouldn't
persist on the main branch.
Plan details preserved as PR comment.

Files removed:${{ steps.check.outputs.files }}"
Files removed: ${{ steps.check.outputs.file_list }}"

git push -u origin "$CLEANUP_BRANCH"

# Create PR and auto-merge
PR_URL=$(gh pr create \
--base "$TARGET_BRANCH" \
--head "$CLEANUP_BRANCH" \
--title "chore: remove ephemeral planning docs" \
--body "Automated cleanup of development planning documents (${{ steps.check.outputs.file_list }}).

These files are used during development but shouldn't persist on the main branch.
Created by the cleanup-ephemeral-docs workflow.")

echo "Created cleanup PR: $PR_URL"

git push
# Enable auto-merge so it merges once checks pass
gh pr merge "$PR_URL" --auto --squash --delete-branch || echo "Auto-merge not available, PR requires manual merge"

echo "✓ Removed ephemeral files and pushed cleanup commit"
echo "Cleanup PR created and auto-merge enabled"
Loading