mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: Add unified-execute-parallel, unified-execute-with-file, and worktree-merge workflows
- Implemented unified-execute-parallel for executing tasks in isolated Git worktrees with parallel execution capabilities. - Developed unified-execute-with-file for universal task execution from various planning outputs with progress tracking. - Created worktree-merge to handle merging of completed worktrees back to the main branch, including dependency management and conflict detection. - Enhanced documentation for each workflow, detailing core functionalities, implementation phases, and configuration options.
This commit is contained in:
413
.codex/skills/clean/SKILL.md
Normal file
413
.codex/skills/clean/SKILL.md
Normal file
@@ -0,0 +1,413 @@
|
||||
---
|
||||
name: clean
|
||||
description: Intelligent code cleanup with mainline detection, stale artifact discovery, and safe execution. Supports targeted cleanup and confirmation.
|
||||
argument-hint: "[--dry-run] [--focus=<area>]"
|
||||
---
|
||||
|
||||
# Workflow Clean Command
|
||||
|
||||
## Overview
|
||||
|
||||
Evidence-based intelligent cleanup command. Systematically identifies stale artifacts through mainline analysis, discovers drift, and safely removes unused sessions, documents, and dead code.
|
||||
|
||||
**Core workflow**: Detect Mainline → Discover Drift → Confirm → Stage → Execute
|
||||
|
||||
## Target Cleanup
|
||||
|
||||
**Focus area**: $FOCUS (or entire project if not specified)
|
||||
**Mode**: $ARGUMENTS
|
||||
|
||||
- `--dry-run`: Preview cleanup without executing
|
||||
- `--focus`: Focus area (module or path)
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 0: Initialization
|
||||
├─ Parse arguments (--dry-run, FOCUS)
|
||||
├─ Setup session folder
|
||||
└─ Initialize utility functions
|
||||
|
||||
Phase 1: Mainline Detection
|
||||
├─ Analyze git history (30 days)
|
||||
├─ Identify core modules (high commit frequency)
|
||||
├─ Map active vs stale branches
|
||||
└─ Build mainline profile
|
||||
|
||||
Phase 2: Drift Discovery (Subagent)
|
||||
├─ spawn_agent with cli-explore-agent role
|
||||
├─ Scan workflow sessions for orphaned artifacts
|
||||
├─ Identify documents drifted from mainline
|
||||
├─ Detect dead code and unused exports
|
||||
└─ Generate cleanup manifest
|
||||
|
||||
Phase 3: Confirmation
|
||||
├─ Validate manifest schema
|
||||
├─ Display cleanup summary by category
|
||||
├─ AskUser: Select categories and risk level
|
||||
└─ Dry-run exit if --dry-run
|
||||
|
||||
Phase 4: Execution
|
||||
├─ Validate paths (security check)
|
||||
├─ Stage deletion (move to .trash)
|
||||
├─ Update manifests
|
||||
├─ Permanent deletion
|
||||
└─ Report results
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 0: Initialization
|
||||
|
||||
```javascript
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
// Parse arguments
|
||||
const args = "$ARGUMENTS"
|
||||
const isDryRun = args.includes('--dry-run')
|
||||
const focusMatch = args.match(/FOCUS="([^"]+)"/)
|
||||
const focusArea = focusMatch ? focusMatch[1] : "$FOCUS" !== "$" + "FOCUS" ? "$FOCUS" : null
|
||||
|
||||
// Session setup
|
||||
const dateStr = getUtc8ISOString().substring(0, 10)
|
||||
const sessionId = `clean-${dateStr}`
|
||||
const sessionFolder = `.workflow/.clean/${sessionId}`
|
||||
const trashFolder = `${sessionFolder}/.trash`
|
||||
const projectRoot = process.cwd()
|
||||
|
||||
bash(`mkdir -p ${sessionFolder}`)
|
||||
bash(`mkdir -p ${trashFolder}`)
|
||||
|
||||
// Utility functions
|
||||
function fileExists(p) {
|
||||
try { return bash(`test -f "${p}" && echo "yes"`).includes('yes') } catch { return false }
|
||||
}
|
||||
|
||||
function dirExists(p) {
|
||||
try { return bash(`test -d "${p}" && echo "yes"`).includes('yes') } catch { return false }
|
||||
}
|
||||
|
||||
function validatePath(targetPath) {
|
||||
if (targetPath.includes('..')) return { valid: false, reason: 'Path traversal' }
|
||||
|
||||
const allowed = ['.workflow/', '.claude/rules/tech/', 'src/']
|
||||
const dangerous = [/^\//, /^C:\\Windows/i, /node_modules/, /\.git$/]
|
||||
|
||||
if (!allowed.some(p => targetPath.startsWith(p))) {
|
||||
return { valid: false, reason: 'Outside allowed directories' }
|
||||
}
|
||||
if (dangerous.some(p => p.test(targetPath))) {
|
||||
return { valid: false, reason: 'Dangerous pattern' }
|
||||
}
|
||||
return { valid: true }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Mainline Detection
|
||||
|
||||
```javascript
|
||||
// Check git repository
|
||||
const isGitRepo = bash('git rev-parse --git-dir 2>/dev/null && echo "yes"').includes('yes')
|
||||
|
||||
let mainlineProfile = {
|
||||
coreModules: [],
|
||||
activeFiles: [],
|
||||
activeBranches: [],
|
||||
staleThreshold: { sessions: 7, branches: 30, documents: 14 },
|
||||
isGitRepo,
|
||||
timestamp: getUtc8ISOString()
|
||||
}
|
||||
|
||||
if (isGitRepo) {
|
||||
// Commit frequency by directory (last 30 days)
|
||||
const freq = bash('git log --since="30 days ago" --name-only --pretty=format: | grep -v "^$" | cut -d/ -f1-2 | sort | uniq -c | sort -rn | head -20')
|
||||
|
||||
// Parse core modules (>5 commits)
|
||||
mainlineProfile.coreModules = freq.trim().split('\n')
|
||||
.map(l => l.trim().match(/^(\d+)\s+(.+)$/))
|
||||
.filter(m => m && parseInt(m[1]) >= 5)
|
||||
.map(m => m[2])
|
||||
|
||||
// Recent branches
|
||||
const branches = bash('git for-each-ref --sort=-committerdate refs/heads/ --format="%(refname:short)" | head -10')
|
||||
mainlineProfile.activeBranches = branches.trim().split('\n').filter(Boolean)
|
||||
}
|
||||
|
||||
Write(`${sessionFolder}/mainline-profile.json`, JSON.stringify(mainlineProfile, null, 2))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Drift Discovery
|
||||
|
||||
```javascript
|
||||
let exploreAgent = null
|
||||
|
||||
try {
|
||||
// Launch cli-explore-agent
|
||||
exploreAgent = spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS
|
||||
1. Read: ~/.codex/agents/cli-explore-agent.md
|
||||
2. Read: .workflow/project-tech.json (if exists)
|
||||
|
||||
## Task Objective
|
||||
Discover stale artifacts for cleanup.
|
||||
|
||||
## Context
|
||||
- Session: ${sessionFolder}
|
||||
- Focus: ${focusArea || 'entire project'}
|
||||
|
||||
## Discovery Categories
|
||||
|
||||
### 1. Stale Workflow Sessions
|
||||
Scan: .workflow/active/WFS-*, .workflow/archives/WFS-*, .workflow/.lite-plan/*, .workflow/.debug/DBG-*
|
||||
Criteria: No modification >7 days + no related git commits
|
||||
|
||||
### 2. Drifted Documents
|
||||
Scan: .claude/rules/tech/*, .workflow/.scratchpad/*
|
||||
Criteria: >30% broken references to non-existent files
|
||||
|
||||
### 3. Dead Code
|
||||
Scan: Unused exports, orphan files (not imported anywhere)
|
||||
Criteria: No importers in import graph
|
||||
|
||||
## Output
|
||||
Write to: ${sessionFolder}/cleanup-manifest.json
|
||||
|
||||
Format:
|
||||
{
|
||||
"generated_at": "ISO",
|
||||
"discoveries": {
|
||||
"stale_sessions": [{ "path": "...", "age_days": N, "reason": "...", "risk": "low|medium|high" }],
|
||||
"drifted_documents": [{ "path": "...", "drift_percentage": N, "reason": "...", "risk": "..." }],
|
||||
"dead_code": [{ "path": "...", "type": "orphan_file", "reason": "...", "risk": "..." }]
|
||||
},
|
||||
"summary": { "total_items": N, "by_category": {...}, "by_risk": {...} }
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
// Wait with timeout handling
|
||||
let result = wait({ ids: [exploreAgent], timeout_ms: 600000 })
|
||||
|
||||
if (result.timed_out) {
|
||||
send_input({ id: exploreAgent, message: 'Complete now and write cleanup-manifest.json.' })
|
||||
result = wait({ ids: [exploreAgent], timeout_ms: 300000 })
|
||||
if (result.timed_out) throw new Error('Agent timeout')
|
||||
}
|
||||
|
||||
if (!fileExists(`${sessionFolder}/cleanup-manifest.json`)) {
|
||||
throw new Error('Manifest not generated')
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (exploreAgent) close_agent({ id: exploreAgent })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Confirmation
|
||||
|
||||
```javascript
|
||||
// Load and validate manifest
|
||||
const manifest = JSON.parse(Read(`${sessionFolder}/cleanup-manifest.json`))
|
||||
|
||||
// Display summary
|
||||
console.log(`
|
||||
## Cleanup Discovery Report
|
||||
|
||||
| Category | Count | Risk |
|
||||
|----------|-------|------|
|
||||
| Sessions | ${manifest.summary.by_category.stale_sessions} | ${getRiskSummary('sessions')} |
|
||||
| Documents | ${manifest.summary.by_category.drifted_documents} | ${getRiskSummary('documents')} |
|
||||
| Dead Code | ${manifest.summary.by_category.dead_code} | ${getRiskSummary('code')} |
|
||||
|
||||
**Total**: ${manifest.summary.total_items} items
|
||||
`)
|
||||
|
||||
// Dry-run exit
|
||||
if (isDryRun) {
|
||||
console.log(`
|
||||
**Dry-run mode**: No changes made.
|
||||
Manifest: ${sessionFolder}/cleanup-manifest.json
|
||||
`)
|
||||
return
|
||||
}
|
||||
|
||||
// User confirmation
|
||||
const selection = AskUser({
|
||||
questions: [
|
||||
{
|
||||
question: "Which categories to clean?",
|
||||
header: "Categories",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "Sessions", description: `${manifest.summary.by_category.stale_sessions} stale sessions` },
|
||||
{ label: "Documents", description: `${manifest.summary.by_category.drifted_documents} drifted docs` },
|
||||
{ label: "Dead Code", description: `${manifest.summary.by_category.dead_code} unused files` }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Risk level?",
|
||||
header: "Risk",
|
||||
options: [
|
||||
{ label: "Low only", description: "Safest (Recommended)" },
|
||||
{ label: "Low + Medium", description: "Includes likely unused" },
|
||||
{ label: "All", description: "Aggressive" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Execution
|
||||
|
||||
```javascript
|
||||
const riskFilter = {
|
||||
'Low only': ['low'],
|
||||
'Low + Medium': ['low', 'medium'],
|
||||
'All': ['low', 'medium', 'high']
|
||||
}[selection.risk]
|
||||
|
||||
// Collect items to clean
|
||||
const items = []
|
||||
if (selection.categories.includes('Sessions')) {
|
||||
items.push(...manifest.discoveries.stale_sessions.filter(s => riskFilter.includes(s.risk)))
|
||||
}
|
||||
if (selection.categories.includes('Documents')) {
|
||||
items.push(...manifest.discoveries.drifted_documents.filter(d => riskFilter.includes(d.risk)))
|
||||
}
|
||||
if (selection.categories.includes('Dead Code')) {
|
||||
items.push(...manifest.discoveries.dead_code.filter(c => riskFilter.includes(c.risk)))
|
||||
}
|
||||
|
||||
const results = { staged: [], deleted: [], failed: [], skipped: [] }
|
||||
|
||||
// Validate and stage
|
||||
for (const item of items) {
|
||||
const validation = validatePath(item.path)
|
||||
if (!validation.valid) {
|
||||
results.skipped.push({ path: item.path, reason: validation.reason })
|
||||
continue
|
||||
}
|
||||
|
||||
if (!fileExists(item.path) && !dirExists(item.path)) {
|
||||
results.skipped.push({ path: item.path, reason: 'Not found' })
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const trashTarget = `${trashFolder}/${item.path.replace(/\//g, '_')}`
|
||||
bash(`mv "${item.path}" "${trashTarget}"`)
|
||||
results.staged.push({ path: item.path, trashPath: trashTarget })
|
||||
} catch (e) {
|
||||
results.failed.push({ path: item.path, error: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Permanent deletion
|
||||
for (const staged of results.staged) {
|
||||
try {
|
||||
bash(`rm -rf "${staged.trashPath}"`)
|
||||
results.deleted.push(staged.path)
|
||||
} catch (e) {
|
||||
console.error(`Failed: ${staged.path}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup empty trash
|
||||
bash(`rmdir "${trashFolder}" 2>/dev/null || true`)
|
||||
|
||||
// Report
|
||||
console.log(`
|
||||
## Cleanup Complete
|
||||
|
||||
**Deleted**: ${results.deleted.length}
|
||||
**Failed**: ${results.failed.length}
|
||||
**Skipped**: ${results.skipped.length}
|
||||
|
||||
### Deleted
|
||||
${results.deleted.map(p => `- ${p}`).join('\n') || '(none)'}
|
||||
|
||||
${results.failed.length > 0 ? `### Failed\n${results.failed.map(f => `- ${f.path}: ${f.error}`).join('\n')}` : ''}
|
||||
|
||||
Report: ${sessionFolder}/cleanup-report.json
|
||||
`)
|
||||
|
||||
Write(`${sessionFolder}/cleanup-report.json`, JSON.stringify({
|
||||
timestamp: getUtc8ISOString(),
|
||||
results,
|
||||
summary: {
|
||||
deleted: results.deleted.length,
|
||||
failed: results.failed.length,
|
||||
skipped: results.skipped.length
|
||||
}
|
||||
}, null, 2))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Folder
|
||||
|
||||
```
|
||||
.workflow/.clean/clean-{YYYY-MM-DD}/
|
||||
├── mainline-profile.json # Git history analysis
|
||||
├── cleanup-manifest.json # Discovery results
|
||||
├── cleanup-report.json # Execution results
|
||||
└── .trash/ # Staging area (temporary)
|
||||
```
|
||||
|
||||
## Risk Levels
|
||||
|
||||
| Risk | Description | Examples |
|
||||
|------|-------------|----------|
|
||||
| **Low** | Safe to delete | Empty sessions, scratchpad files |
|
||||
| **Medium** | Likely unused | Orphan files, old archives |
|
||||
| **High** | May have dependencies | Files with some imports |
|
||||
|
||||
## Security Features
|
||||
|
||||
| Feature | Protection |
|
||||
|---------|------------|
|
||||
| Path Validation | Whitelist directories, reject traversal |
|
||||
| Staged Deletion | Move to .trash before permanent delete |
|
||||
| Dangerous Patterns | Block system dirs, node_modules, .git |
|
||||
|
||||
## Iteration Flow
|
||||
|
||||
```
|
||||
First Call (/prompts:clean):
|
||||
├─ Detect mainline from git history
|
||||
├─ Discover stale artifacts via subagent
|
||||
├─ Display summary, await user selection
|
||||
└─ Execute cleanup with staging
|
||||
|
||||
Dry-Run (/prompts:clean --dry-run):
|
||||
├─ All phases except execution
|
||||
└─ Manifest saved for review
|
||||
|
||||
Focused (/prompts:clean FOCUS="auth"):
|
||||
└─ Discovery limited to specified area
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| No git repo | Use file timestamps only |
|
||||
| Agent timeout | Retry once with prompt, then abort |
|
||||
| Path validation fail | Skip item, report reason |
|
||||
| Manifest parse error | Abort with error |
|
||||
| Empty discovery | Report "codebase is clean" |
|
||||
|
||||
---
|
||||
|
||||
**Now execute cleanup workflow** with focus: $FOCUS
|
||||
Reference in New Issue
Block a user