clean

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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)
聚焦区域:$FOCUS(未指定则为整个项目) 模式:$ARGUMENTS
  • --dry-run
    :预览清理操作,不实际执行
  • --focus
    :指定聚焦区域(模块或路径)

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
   ├─ ASK_USER: 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
Phase 0: 初始化
   ├─ 解析参数(--dry-run, FOCUS)
   ├─ 设置会话文件夹
   └─ 初始化工具函数

Phase 1: 主线检测
   ├─ 分析git历史(最近30天)
   ├─ 识别核心模块(高提交频率)
   ├─ 映射活跃分支与过期分支
   └─ 构建主线配置文件

Phase 2: 漂移发现(子Agent)
   ├─ 以cli-explore-agent角色启动spawn_agent
   ├─ 扫描工作流会话中的孤立工件
   ├─ 识别与主线偏离的文档
   ├─ 检测死代码和未使用的导出
   └─ 生成清理清单

Phase 3: 确认环节
   ├─ 验证清单格式
   ├─ 按类别展示清理摘要
   ├─ 询问用户:选择清理类别和风险等级
   └─ 若为--dry-run模式则退出

Phase 4: 执行清理
   ├─ 路径验证(安全检查)
   ├─ 暂存待删除项(移动到.trash)
   ├─ 更新清单
   ├─ 永久删除
   └─ 生成结果报告

Implementation

实现细节

Phase 0: Initialization

Phase 0: 初始化

Step 0: Determine Project Root
Step 0: 确定项目根目录
检测项目根目录,确保
.workflow/
产物位置正确:
bash
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
优先通过 git 获取仓库根目录;非 git 项目回退到
pwd
取当前绝对路径。 存储为
{projectRoot}
,后续所有
.workflow/
路径必须以此为前缀。
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 = `${projectRoot}/.workflow/.clean/${sessionId}`
const trashFolder = `${sessionFolder}/.trash`
const projectRoot = bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()

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 }
}

检测项目根目录,确保
.workflow/
产物位置正确:
bash
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
优先通过 git 获取仓库根目录;非 git 项目回退到
pwd
取当前绝对路径。 存储为
{projectRoot}
,后续所有
.workflow/
路径必须以此为前缀。
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 = `${projectRoot}/.workflow/.clean/${sessionId}`
const trashFolder = `${sessionFolder}/.trash`
const projectRoot = bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()

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

Phase 1: 主线检测

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))

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

Phase 2: 漂移发现

javascript
let exploreAgent = null

try {
  // Launch cli-explore-agent
  exploreAgent = spawn_agent({
    message: `
javascript
let exploreAgent = null

try {
  // Launch cli-explore-agent
  exploreAgent = spawn_agent({
    message: `

TASK ASSIGNMENT

TASK ASSIGNMENT

MANDATORY FIRST STEPS

MANDATORY FIRST STEPS

  1. Read: ~/.codex/agents/cli-explore-agent.md
  2. Read: ${projectRoot}/.workflow/project-tech.json (if exists)
  1. Read: ~/.codex/agents/cli-explore-agent.md
  2. Read: ${projectRoot}/.workflow/project-tech.json (if exists)

Task Objective

Task Objective

Discover stale artifacts for cleanup.
Discover stale artifacts for cleanup.

Context

Context

  • Session: ${sessionFolder}
  • Focus: ${focusArea || 'entire project'}
  • Session: ${sessionFolder}
  • Focus: ${focusArea || 'entire project'}

Discovery Categories

Discovery Categories

1. Stale Workflow Sessions

1. Stale Workflow Sessions

Scan: ${projectRoot}/.workflow/active/WFS-, ${projectRoot}/.workflow/archives/WFS-, ${projectRoot}/.workflow/.lite-plan/, ${projectRoot}/.workflow/.debug/DBG- Criteria: No modification >7 days + no related git commits
Scan: ${projectRoot}/.workflow/active/WFS-, ${projectRoot}/.workflow/archives/WFS-, ${projectRoot}/.workflow/.lite-plan/, ${projectRoot}/.workflow/.debug/DBG- Criteria: No modification >7 days + no related git commits

2. Drifted Documents

2. Drifted Documents

Scan: .claude/rules/tech/, ${projectRoot}/.workflow/.scratchpad/ Criteria: >30% broken references to non-existent files
Scan: .claude/rules/tech/, ${projectRoot}/.workflow/.scratchpad/ Criteria: >30% broken references to non-existent files

3. Dead Code

3. Dead Code

Scan: Unused exports, orphan files (not imported anywhere) Criteria: No importers in import graph
Scan: Unused exports, orphan files (not imported anywhere) Criteria: No importers in import graph

Output

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 }) }

---
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

Phase 3: 确认环节

javascript
// Load and validate manifest
const manifest = JSON.parse(Read(`${sessionFolder}/cleanup-manifest.json`))

// Display summary
console.log(`
javascript
// Load and validate manifest
const manifest = JSON.parse(Read(`${sessionFolder}/cleanup-manifest.json`))

// Display summary
console.log(`

Cleanup Discovery Report

Cleanup Discovery Report

CategoryCountRisk
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 = ASK_USER([ { id: "categories", type: "multi-select", prompt: "Which categories to clean?", 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
} ] }, { id: "risk", type: "select", prompt: "Risk level?", options: [ { label: "Low only", description: "Safest (Recommended)" }, { label: "Low + Medium", description: "Includes likely unused" }, { label: "All", description: "Aggressive" } ] } ]) // BLOCKS (wait for user response)

---
CategoryCountRisk
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 = ASK_USER([ { id: "categories", type: "multi-select", prompt: "Which categories to clean?", 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
} ] }, { id: "risk", type: "select", prompt: "Risk level?", options: [ { label: "Low only", description: "Safest (Recommended)" }, { label: "Low + Medium", description: "Includes likely unused" }, { label: "All", description: "Aggressive" } ] } ]) // BLOCKS (wait for user response)

---

Phase 4: Execution

Phase 4: 执行清理

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(`
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

Cleanup Complete

Deleted: ${results.deleted.length} Failed: ${results.failed.length} Skipped: ${results.skipped.length}
Deleted: ${results.deleted.length} Failed: ${results.failed.length} Skipped: ${results.skipped.length}

Deleted

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))

---
${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

会话文件夹结构

{projectRoot}/.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)
{projectRoot}/.workflow/.clean/clean-{YYYY-MM-DD}/
├── mainline-profile.json     # Git历史分析结果
├── cleanup-manifest.json     # 工件发现结果
├── cleanup-report.json       # 执行结果报告
└── .trash/                   # 暂存区(临时目录)

Risk Levels

风险等级

RiskDescriptionExamples
LowSafe to deleteEmpty sessions, scratchpad files
MediumLikely unusedOrphan files, old archives
HighMay have dependenciesFiles with some imports
风险等级描述示例
低风险可安全删除空会话、临时草稿文件
中风险大概率已无用孤立文件、旧归档
高风险可能存在依赖仍有部分引用的文件

Security Features

安全特性

FeatureProtection
Path ValidationWhitelist directories, reject traversal
Staged DeletionMove to .trash before permanent delete
Dangerous PatternsBlock system dirs, node_modules, .git
特性保护机制
路径验证白名单目录,拒绝路径遍历
暂存删除永久删除前先移动到.trash目录
危险模式拦截阻止系统目录、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
首次调用(/prompts:clean):
   ├─ 从Git历史中检测主线
   ├─ 通过子Agent发现过期工件
   ├─ 展示摘要,等待用户选择
   └─ 执行带暂存的清理操作

模拟运行(/prompts:clean --dry-run):
   ├─ 执行除实际删除外的所有阶段
   └─ 保存清单供用户查看

定向清理(/prompts:clean FOCUS="auth"):
   └─ 仅在指定区域内执行工件发现

Error Handling

错误处理

SituationAction
No git repoUse file timestamps only
Agent timeoutRetry once with prompt, then abort
Path validation failSkip item, report reason
Manifest parse errorAbort with error
Empty discoveryReport "codebase is clean"

Now execute cleanup workflow with focus: $FOCUS
场景处理方式
非Git仓库仅使用文件时间戳判断
Agent超时重新提示一次后终止
路径验证失败跳过该工件并报告原因
清单解析错误终止操作并抛出错误
未发现过期工件报告“代码库已清理完毕”

现在执行清理工作流,聚焦区域:$FOCUS