Compare commits
5 Commits
4ae10cd65e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cd4588be6b | |||
| 35d797f8d8 | |||
| 8006695e26 | |||
| a90f5a3e12 | |||
| 131cfb26bb |
92
.claude/commands/finish-up.md
Normal file
92
.claude/commands/finish-up.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
description: Scan projects for uncommitted/unpushed changes, then commit and push
|
||||
---
|
||||
|
||||
You are running the **finish-up** workflow. Your job is to scan git projects for uncommitted changes and unpushed commits, present a report, then autonomously commit and push selected projects using parallel subagents.
|
||||
|
||||
## Phase 1: Scan
|
||||
|
||||
Run the scan script to check all projects:
|
||||
|
||||
```bash
|
||||
bash "C:/Users/aisaacs/Desktop/Projects/project-scripts/git-status-check.sh" "C:/Users/aisaacs/Desktop/Projects"
|
||||
```
|
||||
|
||||
This outputs TAB-separated lines: `project_name \t uncommitted_count \t unpushed_count \t branch`
|
||||
|
||||
If the current working directory is inside a specific git project (not the Projects root), scan only that project instead:
|
||||
|
||||
```bash
|
||||
bash "C:/Users/aisaacs/Desktop/Projects/project-scripts/git-status-check.sh" "$(git rev-parse --show-toplevel)/.."
|
||||
```
|
||||
|
||||
But filter the output to only show the current project.
|
||||
|
||||
## Phase 2: Report
|
||||
|
||||
Parse the scan output and present a summary table. Group projects by status:
|
||||
|
||||
**Projects with uncommitted changes** (show project name, file count, branch):
|
||||
| Project | Uncommitted | Unpushed | Branch |
|
||||
|---------|-------------|----------|--------|
|
||||
|
||||
**Projects with only unpushed commits** (committed but not pushed):
|
||||
| Project | Unpushed | Branch |
|
||||
|---------|----------|--------|
|
||||
|
||||
**Clean projects** (just list names, keep it brief).
|
||||
|
||||
If everything is clean, say "All projects are clean!" and stop.
|
||||
|
||||
## Phase 3: Confirm
|
||||
|
||||
Use AskUserQuestion to ask which projects to process. Options:
|
||||
- "All projects that need attention" (default/recommended)
|
||||
- "Let me pick specific projects"
|
||||
- "Just show the report, don't commit/push"
|
||||
|
||||
If the user wants to pick specific projects, ask which ones with a multiSelect question listing only the projects that need attention.
|
||||
|
||||
## Phase 4: Parallel Commit/Push via Subagents
|
||||
|
||||
For each selected project, dispatch a **Bash subagent** using the Task tool (`subagent_type: "Bash"`). Launch all subagents in parallel in a single message with multiple Task tool calls.
|
||||
|
||||
Each subagent prompt should be:
|
||||
|
||||
```
|
||||
You are committing and pushing changes for the project: [PROJECT_NAME]
|
||||
Working directory: C:/Users/aisaacs/Desktop/Projects/[PROJECT_NAME]
|
||||
|
||||
Steps:
|
||||
1. Run: git status
|
||||
2. Run: git diff --stat
|
||||
3. Run: git diff (to understand the changes)
|
||||
4. Stage all changes: git add -A
|
||||
5. Review what's staged: git diff --cached --stat
|
||||
6. Based on the diff, write a conventional commit message (feat/fix/chore/refactor/docs/etc)
|
||||
- Short summary line that explains WHY, not just WHAT
|
||||
- End with: Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
- Use a HEREDOC to pass the message
|
||||
- NEVER commit files containing secrets (.env, credentials, API keys)
|
||||
- If you find secrets, skip the commit and report the issue
|
||||
7. Commit the changes
|
||||
8. Push to remote: git push
|
||||
- If the push fails on first try, wait 5 seconds and retry (Gitea cold-start)
|
||||
- If it fails again, report the error
|
||||
9. If there are unpushed commits (but no uncommitted changes), just push
|
||||
10. Report what you did: what was committed (summary), push result
|
||||
```
|
||||
|
||||
**IMPORTANT:** Launch ALL subagents in a SINGLE message to maximize parallelism. Do not wait for one to finish before starting the next.
|
||||
|
||||
## Phase 5: Summary
|
||||
|
||||
After all subagents complete, collect their results and present a final summary:
|
||||
|
||||
| Project | Action | Result |
|
||||
|---------|--------|--------|
|
||||
| ProjectA | Committed + Pushed | Success |
|
||||
| ProjectB | Pushed (already committed) | Success |
|
||||
| ProjectC | Committed + Pushed | Push failed: [error] |
|
||||
|
||||
If any pushes failed, suggest retrying those specific projects.
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
def check_git_status(repo_path):
|
||||
"""Check if a git repository has uncommitted changes."""
|
||||
try:
|
||||
# Check if directory is a git repo
|
||||
result = subprocess.run(
|
||||
['git', 'rev-parse', '--git-dir'],
|
||||
cwd=repo_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return None, "Not a git repository"
|
||||
|
||||
# Check for uncommitted changes
|
||||
status_result = subprocess.run(
|
||||
['git', 'status', '--porcelain'],
|
||||
cwd=repo_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if status_result.returncode != 0:
|
||||
return None, "Error checking status"
|
||||
|
||||
changes = status_result.stdout.strip()
|
||||
if changes:
|
||||
# Parse changed files
|
||||
lines = changes.split('\n')
|
||||
files = [line[2:].lstrip() if len(line) > 2 else line for line in lines]
|
||||
return True, (f"{len(lines)} uncommitted change(s)", files)
|
||||
else:
|
||||
return False, ("Clean (no uncommitted changes)", [])
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return None, "Timeout"
|
||||
except Exception as e:
|
||||
return None, f"Error: {str(e)}"
|
||||
|
||||
def main():
|
||||
current_dir = Path.cwd()
|
||||
print(f"Checking projects in: {current_dir}\n")
|
||||
print("=" * 70)
|
||||
|
||||
# Get all subdirectories
|
||||
subdirs = [d for d in current_dir.iterdir() if d.is_dir() and not d.name.startswith('.')]
|
||||
|
||||
if not subdirs:
|
||||
print("No subdirectories found.")
|
||||
return
|
||||
|
||||
projects_with_changes = []
|
||||
clean_projects = []
|
||||
non_git_dirs = []
|
||||
|
||||
for subdir in sorted(subdirs):
|
||||
result = check_git_status(subdir)
|
||||
|
||||
if result[0] is None:
|
||||
# Not a git repo or error
|
||||
has_changes, message = result
|
||||
non_git_dirs.append((subdir.name, message))
|
||||
elif result[0]:
|
||||
# Has changes
|
||||
has_changes, (message, files) = result
|
||||
projects_with_changes.append((subdir.name, message, files))
|
||||
print(f"⚠️ {subdir.name}: {message}")
|
||||
for file in files:
|
||||
print(f" {file}")
|
||||
else:
|
||||
# Clean
|
||||
has_changes, (message, files) = result
|
||||
clean_projects.append(subdir.name)
|
||||
print(f"✓ {subdir.name}: {message}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 70)
|
||||
print("SUMMARY:")
|
||||
print(f" Projects with uncommitted changes: {len(projects_with_changes)}")
|
||||
print(f" Clean projects: {len(clean_projects)}")
|
||||
print(f" Non-git directories: {len(non_git_dirs)}")
|
||||
|
||||
if projects_with_changes:
|
||||
print("\nProjects needing attention:")
|
||||
for name, msg, files in projects_with_changes:
|
||||
print(f" - {name}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
155
docs/plans/2026-02-23-finish-up-implementation.md
Normal file
155
docs/plans/2026-02-23-finish-up-implementation.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# /finish-up Skill Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Create a `/finish-up` Claude Code slash command that scans all projects for uncommitted/unpushed changes, reports findings, then dispatches parallel subagents to commit and push.
|
||||
|
||||
**Architecture:** A bash scan script (`git-status-check.sh`) outputs structured CSV-like results. The skill file (`finish-up.md`) instructs Claude to run the script, parse results, present a report, confirm with the user, then spawn one Bash subagent per project for autonomous commit/push.
|
||||
|
||||
**Tech Stack:** Bash (scan script), Claude Code skill markdown (command file)
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Create the git-status-check.sh scan script
|
||||
|
||||
**Files:**
|
||||
- Create: `C:/Users/aisaacs/Desktop/Projects/project-scripts/git-status-check.sh`
|
||||
|
||||
**Step 1: Write the scan script**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# git-status-check.sh - Scan git repos for uncommitted changes and unpushed commits
|
||||
# Usage: git-status-check.sh [directory]
|
||||
# directory: path to scan (default: parent of script directory)
|
||||
# Output: TAB-separated lines: project_name \t uncommitted_count \t unpushed_count \t branch
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCAN_DIR="${1:-$(dirname "$(dirname "$(readlink -f "$0")")")}"
|
||||
|
||||
for dir in "$SCAN_DIR"/*/; do
|
||||
[ -d "$dir/.git" ] || continue
|
||||
|
||||
name=$(basename "$dir")
|
||||
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
|
||||
# Count uncommitted changes
|
||||
uncommitted=$(git -C "$dir" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
# Count unpushed commits (0 if no upstream)
|
||||
unpushed=$(git -C "$dir" log @{u}..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ' || echo "0")
|
||||
|
||||
echo -e "${name}\t${uncommitted}\t${unpushed}\t${branch}"
|
||||
done
|
||||
```
|
||||
|
||||
**Step 2: Make it executable and test**
|
||||
|
||||
Run: `chmod +x C:/Users/aisaacs/Desktop/Projects/project-scripts/git-status-check.sh`
|
||||
Run: `bash C:/Users/aisaacs/Desktop/Projects/project-scripts/git-status-check.sh C:/Users/aisaacs/Desktop/Projects`
|
||||
|
||||
Expected: TAB-separated output with one line per git repo, showing project name, uncommitted count, unpushed count, and branch.
|
||||
|
||||
**Step 3: Verify edge cases**
|
||||
|
||||
Run the script and confirm:
|
||||
- Non-git directories (like loose `.py`/`.cs` files) are skipped
|
||||
- Repos with no upstream tracking show `0` for unpushed (not an error)
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd C:/Users/aisaacs/Desktop/Projects/project-scripts
|
||||
git add git-status-check.sh
|
||||
git commit -m "feat: add git-status-check.sh scan script
|
||||
|
||||
Scans all git repos in a directory for uncommitted changes and
|
||||
unpushed commits. Outputs structured TAB-separated results.
|
||||
Replaces check_uncommitted.py with additional unpushed detection.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create the /finish-up skill file
|
||||
|
||||
**Files:**
|
||||
- Create: `C:/Users/aisaacs/.claude/commands/finish-up.md`
|
||||
|
||||
**Step 1: Write the skill file**
|
||||
|
||||
The skill markdown instructs Claude on how to execute the /finish-up workflow:
|
||||
|
||||
1. Detect context (all projects vs single project)
|
||||
2. Run `git-status-check.sh` and parse output
|
||||
3. Present report table
|
||||
4. Ask user which projects to process
|
||||
5. Spawn parallel Bash subagents for commit/push
|
||||
6. Collect results and present summary
|
||||
|
||||
Key instructions for the skill:
|
||||
- Use `Task` tool with `subagent_type="Bash"` for parallel work
|
||||
- Each subagent: `git add -A && git diff --cached --stat` to review, craft commit message, commit, push with retry
|
||||
- Handle Gitea cold-start (retry push after 5s on first failure)
|
||||
- Never commit `.env`, `credentials`, `secrets` files
|
||||
- Use conventional commit format
|
||||
- Include `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
|
||||
|
||||
**Step 2: Test the skill**
|
||||
|
||||
Run `/finish-up` in a Claude Code session from `C:\Users\aisaacs\Desktop\Projects` and verify:
|
||||
- Script runs and output is parsed correctly
|
||||
- Report is presented clearly
|
||||
- Subagents are dispatched for selected projects
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd C:/Users/aisaacs/Desktop/Projects/project-scripts
|
||||
git add C:/Users/aisaacs/.claude/commands/finish-up.md
|
||||
git commit -m "feat: add /finish-up Claude Code slash command
|
||||
|
||||
Scans projects for uncommitted/unpushed changes, reports findings,
|
||||
then dispatches parallel subagents to commit and push.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Clean up old check_uncommitted.py
|
||||
|
||||
**Files:**
|
||||
- Delete: `C:/Users/aisaacs/Desktop/Projects/check_uncommitted.py` (loose copy in Projects root)
|
||||
- Delete: `C:/Users/aisaacs/Desktop/Projects/project-scripts/check_uncommitted.py`
|
||||
|
||||
**Step 1: Remove old scripts**
|
||||
|
||||
```bash
|
||||
rm C:/Users/aisaacs/Desktop/Projects/check_uncommitted.py
|
||||
cd C:/Users/aisaacs/Desktop/Projects/project-scripts
|
||||
git rm check_uncommitted.py
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "chore: remove check_uncommitted.py (replaced by git-status-check.sh)
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Push project-scripts to remote
|
||||
|
||||
**Step 1: Push**
|
||||
|
||||
```bash
|
||||
cd C:/Users/aisaacs/Desktop/Projects/project-scripts
|
||||
git push
|
||||
```
|
||||
|
||||
If push fails (Gitea cold-start), wait 5 seconds and retry.
|
||||
61
docs/plans/2026-02-23-finish-up-skill-design.md
Normal file
61
docs/plans/2026-02-23-finish-up-skill-design.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# /finish-up Skill Design
|
||||
|
||||
**Date:** 2026-02-23
|
||||
|
||||
## Purpose
|
||||
|
||||
A Claude Code slash command (`/finish-up`) that scans git projects for uncommitted changes and unpushed commits, reports findings, then autonomously commits and pushes selected projects via parallel subagents.
|
||||
|
||||
## Behavior
|
||||
|
||||
### Context Detection
|
||||
|
||||
- If CWD is `C:\Users\aisaacs\Desktop\Projects` (or not inside a git repo), scan all subdirectories
|
||||
- If CWD is inside a specific project, scan just that project
|
||||
|
||||
### Phase 1 - Scan
|
||||
|
||||
Run a `git-status-check.sh` script from the Projects directory that:
|
||||
- Checks each git repo for uncommitted changes (`git status --porcelain`)
|
||||
- Checks for unpushed commits (`git log @{u}..HEAD --oneline`)
|
||||
- Outputs structured results (project name, has uncommitted, has unpushed, file count, commit count)
|
||||
|
||||
This replaces the existing `check_uncommitted.py` with a bash script that also covers unpushed commits.
|
||||
|
||||
### Phase 2 - Report
|
||||
|
||||
Present findings as a summary:
|
||||
- Projects with uncommitted changes (file counts)
|
||||
- Projects with unpushed commits (commit counts)
|
||||
- Clean projects (brief list)
|
||||
- If everything clean, say so and exit
|
||||
|
||||
### Phase 3 - Confirm
|
||||
|
||||
Ask which projects to process (default: all that need attention).
|
||||
|
||||
### Phase 4 - Parallel Subagents
|
||||
|
||||
Spawn a Bash subagent per selected project. Each autonomously:
|
||||
1. Stages all changes (`git add -A`)
|
||||
2. Reviews the diff (`git diff --cached`)
|
||||
3. Crafts a conventional commit message based on the changes
|
||||
4. Commits
|
||||
5. Pushes to remote (with retry for Gitea cold-start)
|
||||
|
||||
### Phase 5 - Summary
|
||||
|
||||
Report what was committed and pushed across all projects.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `~/.claude/commands/finish-up.md` | Claude Code skill file |
|
||||
| `~/Desktop/Projects/project-scripts/git-status-check.sh` | Scan script (replaces check_uncommitted.py) |
|
||||
|
||||
## Commit Message Style
|
||||
|
||||
Subagents craft conventional commit messages (feat/fix/chore/refactor/docs/etc.) with:
|
||||
- Short summary line
|
||||
- Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
28
git-status-check.sh
Normal file
28
git-status-check.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# git-status-check.sh - Scan git repos for uncommitted changes and unpushed commits
|
||||
# Usage: git-status-check.sh [directory]
|
||||
# directory: path to scan (default: parent of script directory)
|
||||
# Output: TAB-separated lines: project_name \t uncommitted_count \t unpushed_count \t branch
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCAN_DIR="${1:-$(dirname "$(dirname "$(readlink -f "$0")")")}"
|
||||
|
||||
for dir in "$SCAN_DIR"/*/; do
|
||||
[ -d "$dir/.git" ] || continue
|
||||
|
||||
name=$(basename "$dir")
|
||||
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
|
||||
# Count uncommitted changes
|
||||
uncommitted=$(git -C "$dir" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
# Count unpushed commits (0 if no upstream)
|
||||
if git -C "$dir" rev-parse --verify '@{u}' &>/dev/null; then
|
||||
unpushed=$(git -C "$dir" log '@{u}..HEAD' --oneline 2>/dev/null | wc -l | tr -d ' ')
|
||||
else
|
||||
unpushed=0
|
||||
fi
|
||||
|
||||
printf '%s\t%s\t%s\t%s\n' "$name" "$uncommitted" "$unpushed" "$branch"
|
||||
done
|
||||
Reference in New Issue
Block a user