diff --git a/.claude/skills/team-tech-debt/SKILL.md b/.claude/skills/team-tech-debt/SKILL.md index a621ce61..2a91fd5d 100644 --- a/.claude/skills/team-tech-debt/SKILL.md +++ b/.claude/skills/team-tech-debt/SKILL.md @@ -155,6 +155,11 @@ const TEAM_CONFIG = { name: "tech-debt", sessionDir: ".workflow/.team/TD-{slug}-{date}/", sharedMemory: "shared-memory.json", + worktree: { + basePath: ".worktrees", + branchPrefix: "tech-debt/TD-", + autoCleanup: true // Remove worktree after PR creation + }, debtDimensions: ["code", "architecture", "testing", "dependency", "documentation"], priorityMatrix: { highImpact_lowCost: "立即修复 (Quick Win)", @@ -194,13 +199,44 @@ const TEAM_CONFIG = { ``` Scan Mode (仅扫描评估): - TDSCAN-001(多维度扫描) → TDEVAL-001(量化评估) → 报告 + TDSCAN-001(并行多维度扫描+多视角Gemini分析) → TDEVAL-001(量化评估) → 报告 Remediate Mode (完整闭环): - TDSCAN-001(扫描) → TDEVAL-001(评估) → TDPLAN-001(规划) → TDFIX-001(修复) → TDVAL-001(验证) + TDSCAN-001(并行扫描) → TDEVAL-001(评估) → TDPLAN-001(规划) → [Plan Approval] → [Create Worktree] → TDFIX-001(修复,worktree) → TDVAL-001(验证,worktree) → [Commit+PR] → 报告 Targeted Mode (定向修复): - TDPLAN-001(规划) → TDFIX-001(修复) → TDVAL-001(验证) + TDPLAN-001(规划) → [Plan Approval] → [Create Worktree] → TDFIX-001(修复,worktree) → TDVAL-001(验证,worktree) → [Commit+PR] → 报告 +``` + +### TDSCAN Parallel Fan-out Architecture + +``` +TDSCAN-001 内部并行架构: + + ┌────────────────────────────────────────────────────┐ + │ Scanner Worker │ + │ │ + │ Fan-out A: Subagent Exploration (并行 cli-explore) │ + │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ + │ │structure │ │patterns │ │deps │ │ + │ │角度 │ │角度 │ │角度 │ │ + │ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ + │ └────────────┼────────────┘ │ + │ ↓ merge │ + │ Fan-out B: CLI Dimension Analysis (并行 gemini) │ + │ ┌──────┐┌────────┐┌───────┐┌──────┐┌─────┐ │ + │ │code ││arch ││testing││deps ││docs │ │ + │ └──┬───┘└───┬────┘└──┬────┘└──┬───┘└──┬──┘ │ + │ └────────┼────────┼────────┘ │ │ + │ ↓ merge │ │ + │ Fan-out C: Multi-Perspective Gemini (并行) │ + │ ┌────────┐┌──────────┐┌───────┐┌─────────┐ │ + │ │security││perform. ││quality││architect│ │ + │ └───┬────┘└────┬─────┘└──┬────┘└────┬────┘ │ + │ └──────────┼─────────┘ │ │ + │ ↓ Fan-in aggregate │ + │ debt-inventory.json │ + └────────────────────────────────────────────────────┘ ``` ### Mode Auto-Detection @@ -228,7 +264,7 @@ TDFIX → TDVAL → (if regression or quality drop) → TDFIX-fix → TDVAL-2 ``` .workflow/.team/TD-{slug}-{YYYY-MM-DD}/ ├── team-session.json -├── shared-memory.json # 债务清单 / 评估矩阵 / 治理方案 / 修复结果 / 验证结果 +├── shared-memory.json # 债务清单 / 评估矩阵 / 治理方案 / 修复结果 / 验证结果 / worktree 信息 ├── scan/ # Scanner output │ └── debt-inventory.json ├── assessment/ # Assessor output @@ -239,6 +275,15 @@ TDFIX → TDVAL → (if regression or quality drop) → TDFIX-fix → TDVAL-2 │ └── fix-log.json └── validation/ # Validator output └── validation-report.json + +# shared-memory.json worktree 字段(TDFIX 前由 coordinator 写入): +# { +# ... +# "worktree": { +# "path": ".worktrees/TD-{slug}-{date}", +# "branch": "tech-debt/TD-{slug}-{date}" +# } +# } ``` ## Coordinator Spawn Template diff --git a/.claude/skills/team-tech-debt/roles/coordinator/commands/monitor.md b/.claude/skills/team-tech-debt/roles/coordinator/commands/monitor.md index dfa9da40..f2a4055f 100644 --- a/.claude/skills/team-tech-debt/roles/coordinator/commands/monitor.md +++ b/.claude/skills/team-tech-debt/roles/coordinator/commands/monitor.md @@ -51,6 +51,7 @@ const sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) let fixVerifyIteration = 0 const MAX_FIX_VERIFY_ITERATIONS = 3 +let worktreeCreated = false // 获取 pipeline 阶段列表(按创建顺序 = 依赖顺序) const allTasks = TaskList() @@ -113,7 +114,90 @@ for (const stageTask of pipelineTasks) { }) } - // 5. 阶段间质量检查(仅 TDVAL 阶段) + // 5. Plan Approval Gate(TDPLAN 完成后,进入 TDFIX 前) + if (stagePrefix === 'TDPLAN' && taskState.status === 'completed') { + // 读取治理方案 + let planContent = '' + try { planContent = Read(`${sessionFolder}/plan/remediation-plan.md`) } catch {} + if (!planContent) { + try { planContent = JSON.stringify(JSON.parse(Read(`${sessionFolder}/plan/remediation-plan.json`)), null, 2) } catch {} + } + + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "plan_approval", + summary: `[coordinator] 治理方案已生成,等待审批` + }) + + if (!autoYes) { + // 输出方案摘要供用户审阅 + // 注意: 方案内容通过 AskUserQuestion 的描述呈现 + const approval = AskUserQuestion({ + questions: [{ + question: `治理方案已生成,请审阅后决定:\n\n${planContent ? planContent.slice(0, 2000) : '(方案文件未找到,请查看 ' + sessionFolder + '/plan/)'}${planContent && planContent.length > 2000 ? '\n\n... (已截断,完整方案见 ' + sessionFolder + '/plan/)' : ''}`, + header: "Plan Review", + multiSelect: false, + options: [ + { label: "批准执行", description: "按此方案创建 worktree 并执行修复" }, + { label: "修订方案", description: "重新规划(重新 spawn planner)" }, + { label: "终止", description: "停止流水线,不执行修复" } + ] + }] + }) + + const planDecision = approval["Plan Review"] + if (planDecision === "修订方案") { + // 重新创建 TDPLAN 任务并 spawn planner + const revisedTask = TaskCreate({ + subject: `TDPLAN-revised: 修订治理方案`, + description: `session: ${sessionFolder}\n需求: ${taskDescription}\n用户要求修订方案`, + activeForm: "Revising remediation plan" + }) + TaskUpdate({ taskId: revisedTask.id, owner: 'planner', status: 'pending' }) + // 将修订任务插入到当前位置之后重新执行 + pipelineTasks.splice(pipelineTasks.indexOf(stageTask) + 1, 0, { + id: revisedTask.id, + subject: `TDPLAN-revised`, + description: revisedTask.description + }) + continue // 跳到下一阶段(即刚插入的修订任务) + } else if (planDecision === "终止") { + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "shutdown", + summary: `[coordinator] 用户终止流水线(方案审批阶段)` + }) + break // 退出 pipeline 循环 + } + // "批准执行" → 继续 + } + } + + // 6. Worktree Creation(TDFIX 之前,方案已批准) + if (stagePrefix === 'TDFIX' && !worktreeCreated) { + const branchName = `tech-debt/TD-${sessionSlug}-${sessionDate}` + const worktreePath = `.worktrees/TD-${sessionSlug}-${sessionDate}` + + // 创建 worktree 和新分支 + Bash(`git worktree add -b "${branchName}" "${worktreePath}"`) + + // 安装依赖(如有 package.json) + Bash(`cd "${worktreePath}" && npm install --ignore-scripts 2>/dev/null || true`) + + // 存入 shared memory + sharedMemory.worktree = { path: worktreePath, branch: branchName } + Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2)) + + worktreeCreated = true + + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "worktree_created", + summary: `[coordinator] Worktree 已创建: ${worktreePath} (branch: ${branchName})` + }) + } + + // 7. 阶段间质量检查(仅 TDVAL 阶段) if (stagePrefix === 'TDVAL') { const needsFixVerify = evaluateValidationResult(sessionFolder) if (needsFixVerify && fixVerifyIteration < MAX_FIX_VERIFY_ITERATIONS) { @@ -130,6 +214,20 @@ for (const stageTask of pipelineTasks) { ```javascript function buildWorkerPrompt(stageTask, workerConfig, sessionFolder, taskDescription) { + const stagePrefix = stageTask.subject.match(/^(TD\w+)-/)?.[1] || 'TD' + + // Worktree 注入(TDFIX 和 TDVAL 阶段) + let worktreeSection = '' + if (sharedMemory.worktree && (stagePrefix === 'TDFIX' || stagePrefix === 'TDVAL')) { + worktreeSection = ` +## Worktree(强制) +- Worktree 路径: ${sharedMemory.worktree.path} +- 分支: ${sharedMemory.worktree.branch} +- **所有文件读取、修改、命令执行必须在 worktree 路径下进行** +- 使用 \`cd "${sharedMemory.worktree.path}" && ...\` 前缀执行所有 Bash 命令 +- 禁止在主工作树中修改任何文件` + } + return `你是 team "${teamName}" 的 ${workerConfig.role.toUpperCase()}。 ## ⚠️ 首要指令(MUST) @@ -142,9 +240,9 @@ Skill(skill="team-tech-debt", args="${workerConfig.skillArgs}") - 任务: ${stageTask.subject} - 描述: ${stageTask.description || taskDescription} - Session: ${sessionFolder} - +${worktreeSection} ## 角色准则(强制) -- 你只能处理 ${stageTask.subject.match(/^(TD\w+)-/)?.[1] || 'TD'}-* 前缀的任务 +- 你只能处理 ${stagePrefix}-* 前缀的任务 - 所有输出必须带 [${workerConfig.role}] 标识前缀 - 仅与 coordinator 通信,不得直接联系其他 worker @@ -238,13 +336,66 @@ function evaluateValidationResult(sessionFolder) { } ``` -### Step 3: Result Processing +### Step 3: Result Processing + PR Creation ```javascript // 汇总所有结果 const finalSharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) const allFinalTasks = TaskList() const workerTasks = allFinalTasks.filter(t => t.owner && t.owner !== 'coordinator') + +// PR 创建(worktree 执行模式下,验证通过后) +if (finalSharedMemory.worktree && finalSharedMemory.validation_results?.passed) { + const { path: wtPath, branch } = finalSharedMemory.worktree + + // Commit all changes in worktree + Bash(`cd "${wtPath}" && git add -A && git commit -m "$(cat <<'EOF' +tech-debt: ${taskDescription} + +Automated tech debt cleanup via team-tech-debt pipeline. +Mode: ${pipelineMode} +Items fixed: ${finalSharedMemory.fix_results?.items_fixed || 0} +Debt score: ${finalSharedMemory.debt_score_before} → ${finalSharedMemory.debt_score_after} +EOF +)"`) + + // Push + Create PR + Bash(`cd "${wtPath}" && git push -u origin "${branch}"`) + + const prTitle = `Tech Debt: ${taskDescription.slice(0, 50)}` + Bash(`cd "${wtPath}" && gh pr create --title "${prTitle}" --body "$(cat <<'EOF' +## Tech Debt Cleanup + +**Mode**: ${pipelineMode} +**Items fixed**: ${finalSharedMemory.fix_results?.items_fixed || 0} +**Debt score**: ${finalSharedMemory.debt_score_before} → ${finalSharedMemory.debt_score_after} + +### Validation +- Tests: ${finalSharedMemory.validation_results?.checks?.test_suite?.status || 'N/A'} +- Types: ${finalSharedMemory.validation_results?.checks?.type_check?.status || 'N/A'} +- Lint: ${finalSharedMemory.validation_results?.checks?.lint_check?.status || 'N/A'} + +### Session +${sessionFolder} +EOF +)"`) + + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "pr_created", + summary: `[coordinator] PR 已创建: branch ${branch}` + }) + + // Cleanup worktree + Bash(`git worktree remove "${wtPath}" 2>/dev/null || true`) +} else if (finalSharedMemory.worktree && !finalSharedMemory.validation_results?.passed) { + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "quality_gate", + summary: `[coordinator] 验证未通过,worktree 保留于 ${finalSharedMemory.worktree.path},请手动检查` + }) +} + const summary = { total_tasks: workerTasks.length, completed_tasks: workerTasks.filter(t => t.status === 'completed').length, diff --git a/.claude/skills/team-tech-debt/roles/coordinator/role.md b/.claude/skills/team-tech-debt/roles/coordinator/role.md index a3d0b23d..203d3c55 100644 --- a/.claude/skills/team-tech-debt/roles/coordinator/role.md +++ b/.claude/skills/team-tech-debt/roles/coordinator/role.md @@ -34,6 +34,9 @@ | Type | Direction | Trigger | Description | |------|-----------|---------|-------------| | `mode_selected` | coordinator → all | 模式确定 | scan/remediate/targeted | +| `plan_approval` | coordinator → user | TDPLAN 完成 | 呈现治理方案供审批(批准/修订/终止) | +| `worktree_created` | coordinator → user | TDFIX 前 | Worktree 和分支已创建 | +| `pr_created` | coordinator → user | TDVAL 通过 | PR 已创建,worktree 已清理 | | `quality_gate` | coordinator → user | 质量评估 | 通过/不通过/有条件通过 | | `task_unblocked` | coordinator → worker | 依赖解除 | 任务可执行 | | `error` | coordinator → user | 协调错误 | 阻塞性问题 | @@ -190,9 +193,9 @@ Read("commands/monitor.md") |----------|--------|--------| | TDSCAN-001 | scanner | → 启动 TDEVAL | | TDEVAL-001 | assessor | → 启动 TDPLAN | -| TDPLAN-001 | planner | → 启动 TDFIX | -| TDFIX-001 | executor | → 启动 TDVAL | -| TDVAL-001 | validator | → 评估质量门控 | +| TDPLAN-001 | planner | → [Plan Approval Gate] → [Create Worktree] → 启动 TDFIX | +| TDFIX-001 | executor (worktree) | → 启动 TDVAL | +| TDVAL-001 | validator (worktree) | → 评估质量门控 → [Commit+PR] | **Fix-Verify 循环**(TDVAL 阶段发现回归时): ```javascript @@ -209,12 +212,64 @@ if (regressionFound && fixVerifyIteration < 3) { } ``` -### Phase 5: Report + Debt Reduction Metrics +### Phase 5: Report + Debt Reduction Metrics + PR ```javascript // 读取 shared memory 汇总结果 const memory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) +// PR 创建(worktree 执行模式下,验证通过后) +if (memory.worktree && memory.validation_results?.passed) { + const { path: wtPath, branch } = memory.worktree + + // Commit all changes in worktree + Bash(`cd "${wtPath}" && git add -A && git commit -m "$(cat <<'EOF' +tech-debt: ${taskDescription} + +Automated tech debt cleanup via team-tech-debt pipeline. +Mode: ${pipelineMode} +EOF +)"`) + + // Push + Create PR + Bash(`cd "${wtPath}" && git push -u origin "${branch}"`) + + const prBody = `## Tech Debt Cleanup + +**Mode**: ${pipelineMode} +**Items fixed**: ${memory.fix_results?.items_fixed || 0} +**Debt score**: ${memory.debt_score_before} → ${memory.debt_score_after} + +### Validation +- Tests: ${memory.validation_results?.checks?.test_suite?.status || 'N/A'} +- Types: ${memory.validation_results?.checks?.type_check?.status || 'N/A'} +- Lint: ${memory.validation_results?.checks?.lint_check?.status || 'N/A'} + +### Session +${sessionFolder}` + + Bash(`cd "${wtPath}" && gh pr create --title "Tech Debt: ${taskDescription.slice(0, 50)}" --body "$(cat <<'EOF' +${prBody} +EOF +)"`) + + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "pr_created", + summary: `[coordinator] PR 已创建: branch ${branch}` + }) + + // Cleanup worktree + Bash(`git worktree remove "${wtPath}" 2>/dev/null || true`) +} else if (memory.worktree && !memory.validation_results?.passed) { + // 验证未通过,保留 worktree 供手动检查 + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "quality_gate", + summary: `[coordinator] 验证未通过,worktree 保留于 ${memory.worktree.path},请手动检查` + }) +} + const report = { mode: pipelineMode, debt_items_found: memory.debt_inventory?.length || 0, diff --git a/.claude/skills/team-tech-debt/roles/executor/commands/remediate.md b/.claude/skills/team-tech-debt/roles/executor/commands/remediate.md index f8a6dad8..a9e2a07b 100644 --- a/.claude/skills/team-tech-debt/roles/executor/commands/remediate.md +++ b/.claude/skills/team-tech-debt/roles/executor/commands/remediate.md @@ -49,6 +49,10 @@ function sortBatches(batches) { // 按类型分组并排序 const sortedBatches = sortBatches(batches) +// Worktree 路径(从 shared memory 加载) +const worktreePath = sharedMemory.worktree?.path || null +const cmdPrefix = worktreePath ? `cd "${worktreePath}" && ` : '' + // 每批最大 items 数 const MAX_ITEMS_PER_BATCH = 10 @@ -97,7 +101,7 @@ for (const [batchName, actions] of Object.entries(finalBatches)) { description: `Tech debt cleanup: ${batchName} (${actions.length} items)`, prompt: `## Goal ${prompt} - +${worktreePath ? `\n## Worktree(强制)\n- 工作目录: ${worktreePath}\n- **所有文件操作必须在 ${worktreePath} 下进行**\n- 读文件: Read("${worktreePath}/path/to/file")\n- Bash 命令: cd "${worktreePath}" && ...\n- 禁止修改主工作树\n` : ''} ## Items to Fix ${actions.map(a => `### ${a.debt_id}: ${a.action} - File: ${a.file || 'N/A'} @@ -123,9 +127,9 @@ ${fileList.map(f => `- ${f}`).join('\n')}` status: 'completed' } - // 检查文件是否被修改 + // 检查文件是否被修改(在 worktree 中执行) for (const file of fileList) { - const modified = Bash(`git diff --name-only -- "${file}" 2>/dev/null`).trim() + const modified = Bash(`${cmdPrefix}git diff --name-only -- "${file}" 2>/dev/null`).trim() if (modified) { fixResults.files_modified.push(file) } diff --git a/.claude/skills/team-tech-debt/roles/executor/role.md b/.claude/skills/team-tech-debt/roles/executor/role.md index 7793f9cb..9ed8d43e 100644 --- a/.claude/skills/team-tech-debt/roles/executor/role.md +++ b/.claude/skills/team-tech-debt/roles/executor/role.md @@ -102,6 +102,10 @@ const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() || let sharedMemory = {} try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {} +// 加载 worktree 路径(由 coordinator 写入 shared memory) +const worktreePath = sharedMemory.worktree?.path || null +const worktreeBranch = sharedMemory.worktree?.branch || null + // 加载治理方案 let plan = {} try { @@ -137,7 +141,7 @@ function groupActionsByType(actions) { Read("commands/remediate.md") ``` -**核心策略**: 分批委派 code-developer 执行修复 +**核心策略**: 分批委派 code-developer 执行修复(在 worktree 中操作) ```javascript const fixResults = { @@ -157,7 +161,7 @@ for (const [batchType, actions] of Object.entries(batches)) { description: `Fix tech debt batch: ${batchType} (${actions.length} items)`, prompt: `## Goal Execute tech debt cleanup for ${batchType} items. - +${worktreePath ? `\n## Worktree(强制)\n- 工作目录: ${worktreePath}\n- **所有文件读取和修改必须在 ${worktreePath} 下进行**\n- 读文件时使用 ${worktreePath}/path/to/file\n- Bash 命令使用 cd "${worktreePath}" && ... 前缀\n` : ''} ## Actions ${actions.map(a => `- [${a.debt_id}] ${a.action} (file: ${a.file})`).join('\n')} @@ -183,12 +187,13 @@ ${actions.map(a => `- [${a.debt_id}] ${a.action} (file: ${a.file})`).join('\n')} ### Phase 4: Self-Validation ```javascript -// 基本语法检查 -const syntaxResult = Bash(`npx tsc --noEmit 2>&1 || python -m py_compile *.py 2>&1 || echo "skip"`) +// 基本语法检查(在 worktree 中执行) +const cmdPrefix = worktreePath ? `cd "${worktreePath}" && ` : '' +const syntaxResult = Bash(`${cmdPrefix}npx tsc --noEmit 2>&1 || python -m py_compile *.py 2>&1 || echo "skip"`) const hasSyntaxErrors = /error/i.test(syntaxResult) && !/skip/.test(syntaxResult) // 基本 lint 检查 -const lintResult = Bash(`npx eslint --no-error-on-unmatched-pattern src/ 2>&1 || echo "skip"`) +const lintResult = Bash(`${cmdPrefix}npx eslint --no-error-on-unmatched-pattern src/ 2>&1 || echo "skip"`) const hasLintErrors = /error/i.test(lintResult) && !/skip/.test(lintResult) // 更新修复统计 diff --git a/.claude/skills/team-tech-debt/roles/scanner/commands/scan-debt.md b/.claude/skills/team-tech-debt/roles/scanner/commands/scan-debt.md index 89770fa1..b05190c3 100644 --- a/.claude/skills/team-tech-debt/roles/scanner/commands/scan-debt.md +++ b/.claude/skills/team-tech-debt/roles/scanner/commands/scan-debt.md @@ -1,12 +1,12 @@ # Command: scan-debt -> 多维度 CLI Fan-out 技术债务扫描。从代码质量、架构、测试、依赖、文档 5 个维度并行分析代码,发现技术债务。 +> 三层并行 Fan-out 技术债务扫描。Subagent 结构探索 + CLI 维度分析 + 多视角 Gemini 深度分析,三层并行执行后 Fan-in 聚合。 ## When to Use - Phase 3 of Scanner - 需要对代码库进行多维度技术债务扫描 -- 复杂度为 Medium 或 High 时使用 CLI Fan-out +- 复杂度为 Medium 或 High 时使用并行 Fan-out **Trigger conditions**: - TDSCAN-* 任务进入 Phase 3 @@ -17,10 +17,14 @@ ### Delegation Mode -**Mode**: CLI Fan-out +**Mode**: Triple Fan-out + Fan-in +**Subagent**: `cli-explore-agent`(并行结构探索) **CLI Tool**: `gemini` (primary) **CLI Mode**: `analysis` -**Parallel Dimensions**: 3-5(根据复杂度) +**Parallel Layers**: +- Fan-out A: 2-3 并行 subagent(结构探索) +- Fan-out B: 3-5 并行 CLI(维度分析) +- Fan-out C: 2-4 并行 CLI(多视角 Gemini) ### Decision Logic @@ -30,13 +34,15 @@ if (complexity === 'Low') { // ACE 搜索 + Grep 内联分析(不使用 CLI) mode = 'inline' } else if (complexity === 'Medium') { - // CLI Fan-out: 3 个核心维度 - mode = 'cli-fanout' + // 双层 Fan-out: subagent 探索 + CLI 3 维度 + mode = 'dual-fanout' activeDimensions = ['code', 'testing', 'dependency'] + exploreAngles = ['structure', 'patterns'] } else { - // CLI Fan-out: 所有 5 个维度 - mode = 'cli-fanout' - activeDimensions = dimensions + // 三层 Fan-out: subagent 探索 + CLI 5 维度 + 多视角 Gemini + mode = 'triple-fanout' + activeDimensions = dimensions // all 5 + exploreAngles = ['structure', 'patterns', 'dependencies'] } ``` @@ -57,20 +63,84 @@ const changedFiles = Bash(`git diff --name-only HEAD~10 2>/dev/null || echo ""`) const fileContext = changedFiles.length > 0 ? changedFiles.map(f => `@${f}`).join(' ') : `@${scanScope}` + +// 多视角检测(从 role.md Phase 2 传入) +// perspectives = detectPerspectives(task.description) ``` ### Step 2: Execute Strategy ```javascript if (mode === 'inline') { - // 快速内联扫描 + // 快速内联扫描(Low 复杂度) const aceResults = mcp__ace-tool__search_context({ project_root_path: projectRoot, query: "code smells, TODO/FIXME, deprecated APIs, complex functions, dead code, missing tests, circular imports" }) // 解析 ACE 结果并分类到维度 } else { - // CLI Fan-out: 每个维度一个 CLI 调用 + // === 三层并行 Fan-out === + // A、B、C 三层同时启动,互不依赖 + + // ─── Fan-out A: Subagent 并行探索(codebase 结构理解)─── + executeExploreAngles(exploreAngles) + + // ─── Fan-out B: CLI 维度分析(并行 gemini)─── + executeDimensionAnalysis(activeDimensions) + + // ─── Fan-out C: 多视角 Gemini 深度分析(并行)─── + if (mode === 'triple-fanout') { + executePerspectiveAnalysis(perspectives) + } + + // 等待所有 Fan-out 完成(hook 回调通知) +} +``` + +### Step 2a: Fan-out A — Subagent Exploration + +> 并行启动 cli-explore-agent 探索代码库结构,为后续分析提供上下文。 +> 每个角度独立执行,不互相依赖。 + +```javascript +function executeExploreAngles(angles) { + const explorePrompts = { + 'structure': `Explore the codebase structure and module organization. +Focus on: directory layout, module boundaries, entry points, build configuration. +Project root: ${projectRoot} +Report: module map, key entry files, build system type, framework detection.`, + + 'patterns': `Explore coding patterns and conventions used in this codebase. +Focus on: naming conventions, import patterns, error handling patterns, state management, design patterns. +Project root: ${projectRoot} +Report: dominant patterns, anti-patterns found, consistency assessment.`, + + 'dependencies': `Explore dependency graph and inter-module relationships. +Focus on: import/require chains, circular dependencies, external dependency usage, shared utilities. +Project root: ${projectRoot} +Report: dependency hotspots, tightly-coupled modules, dependency depth analysis.` + } + + // 并行启动所有探索角度(每个 cli-explore-agent 独立执行) + for (const angle of angles) { + Task({ + subagent_type: "cli-explore-agent", + run_in_background: false, + description: `Explore: ${angle}`, + prompt: explorePrompts[angle] || `Explore from ${angle} perspective. Project: ${projectRoot}` + }) + } + + // 所有 subagent 返回后,探索结果已可用 +} +``` + +### Step 2b: Fan-out B — CLI Dimension Analysis + +> 每个维度独立的 gemini CLI 分析,全部并行启动。 + +```javascript +function executeDimensionAnalysis(activeDimensions) { const dimensionPrompts = { 'code': `PURPOSE: Identify code quality debt - complexity, duplication, code smells TASK: • Find functions with cyclomatic complexity > 10 • Detect code duplication (>20 lines) • Identify code smells (God class, long method, feature envy) • Find TODO/FIXME/HACK comments • Detect dead code and unused exports @@ -108,6 +178,7 @@ EXPECTED: Documentation debt with severity, file:line, type (missing/stale/incom CONSTRAINTS: Focus on public interfaces and critical paths` } + // 并行启动所有维度分析 for (const dimension of activeDimensions) { const prompt = dimensionPrompts[dimension] if (!prompt) continue @@ -116,47 +187,153 @@ CONSTRAINTS: Focus on public interfaces and critical paths` run_in_background: true }) } - - // 等待所有 CLI 完成(hook 回调通知) } ``` -### Step 3: Result Processing +### Step 2c: Fan-out C — Multi-Perspective Gemini Analysis + +> 多视角深度分析,每个视角关注不同质量维度。 +> 视角由 `detectPerspectives()` 自动检测,或在 High 复杂度下全量启用。 +> 与 Fan-out B(维度分析)的区别:维度分析按"代码/测试/依赖"横切,视角分析按"安全/性能/质量/架构"纵切,交叉覆盖。 ```javascript -// 聚合所有维度的结果 -const allFindings = [] +function executePerspectiveAnalysis(perspectives) { + const perspectivePrompts = { + 'security': `PURPOSE: Security-focused analysis of codebase to identify vulnerability debt +TASK: • Find injection vulnerabilities (SQL, command, XSS, LDAP) • Check authentication/authorization weaknesses • Identify hardcoded secrets or credentials • Detect insecure data handling (sensitive data exposure) • Find missing input validation on trust boundaries • Check for outdated crypto or insecure hash functions +MODE: analysis +CONTEXT: ${fileContext} +EXPECTED: Security findings with: severity (critical/high/medium/low), CWE/OWASP reference, file:line, remediation suggestion +CONSTRAINTS: Focus on exploitable vulnerabilities, not theoretical risks`, -// 从 CLI 输出解析结果 + 'performance': `PURPOSE: Performance-focused analysis to identify performance debt +TASK: • Find N+1 query patterns in database calls • Detect unnecessary re-renders or recomputations • Identify missing caching opportunities • Find synchronous blocking in async contexts • Detect memory leak patterns (event listener accumulation, unclosed resources) • Check for unoptimized loops or O(n²) algorithms on large datasets +MODE: analysis +CONTEXT: ${fileContext} +EXPECTED: Performance findings with: severity, impact estimate (latency/memory/CPU), file:line, optimization suggestion +CONSTRAINTS: Focus on measurable impact, not micro-optimizations`, + + 'code-quality': `PURPOSE: Code quality deep analysis beyond surface-level linting +TASK: • Identify functions violating single responsibility principle • Find overly complex conditional chains (>3 nesting levels) • Detect hidden temporal coupling between functions • Find magic numbers and unexplained constants • Identify error handling anti-patterns (empty catch, swallowed errors) • Detect feature envy (methods that access other classes more than their own) +MODE: analysis +CONTEXT: ${fileContext} +EXPECTED: Quality findings with: severity, code smell category, file:line, refactoring suggestion with pattern name +CONSTRAINTS: Focus on maintainability impact, skip style-only issues`, + + 'architecture': `PURPOSE: Architecture-level analysis of system design debt +TASK: • Identify layering violations (skip-layer calls, reverse dependencies) • Find God modules/classes with >5 distinct responsibilities • Detect missing domain boundaries (business logic in UI/API layer) • Check for abstraction leaks (implementation details in interfaces) • Identify duplicated business logic across modules • Find tightly coupled modules that should be independent +MODE: analysis +CONTEXT: ${fileContext} +EXPECTED: Architecture findings with: severity, affected modules, coupling metric, suggested restructuring +CONSTRAINTS: Focus on structural issues affecting scalability and team autonomy` + } + + // 并行启动所有视角分析 + for (const perspective of perspectives) { + const prompt = perspectivePrompts[perspective] + if (!prompt) continue + + Bash(`ccw cli -p "${prompt}" --tool gemini --mode analysis --rule analysis-review-architecture`, { + run_in_background: true + }) + } +} +``` + +### Step 3: Fan-in Result Processing + +> 三层 Fan-out 结果聚合:探索结果提供上下文,维度分析 + 视角分析交叉去重。 + +```javascript +// ─── 3a: 聚合探索结果(来自 Fan-out A)─── +const exploreContext = { + structure: exploreResults['structure'] || {}, + patterns: exploreResults['patterns'] || {}, + dependencies: exploreResults['dependencies'] || {} +} + +// ─── 3b: 聚合维度分析结果(来自 Fan-out B)─── +const dimensionFindings = [] for (const dimension of activeDimensions) { const findings = parseCliOutput(cliResults[dimension]) for (const finding of findings) { finding.dimension = dimension - allFindings.push(finding) + finding.source = 'dimension-analysis' + dimensionFindings.push(finding) } } -// 去重:相同 file:line 的发现合并 -function deduplicateFindings(findings) { - const seen = new Set() - const unique = [] - for (const f of findings) { - const key = `${f.file}:${f.line}:${f.dimension}` - if (!seen.has(key)) { - seen.add(key) - unique.push(f) +// ─── 3c: 聚合视角分析结果(来自 Fan-out C)─── +const perspectiveFindings = [] +if (mode === 'triple-fanout') { + for (const perspective of perspectives) { + const findings = parseCliOutput(cliResults[perspective]) + for (const finding of findings) { + finding.perspective = perspective + finding.source = 'perspective-analysis' + // 映射视角到最近维度(用于统一归类) + finding.dimension = finding.dimension || mapPerspectiveToDimension(perspective) + perspectiveFindings.push(finding) } } - return unique +} + +// ─── 3d: 合并 + 交叉去重 ─── +const allFindings = [...dimensionFindings, ...perspectiveFindings] + +function deduplicateFindings(findings) { + const seen = new Map() // key → finding (保留严重性更高的) + for (const f of findings) { + const key = `${f.file}:${f.line}` + const existing = seen.get(key) + if (!existing) { + seen.set(key, f) + } else { + // 同一位置多角度发现 → 合并,提升严重性 + const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 } + if ((severityOrder[f.severity] || 3) < (severityOrder[existing.severity] || 3)) { + existing.severity = f.severity + } + // 记录交叉引用(被多个视角/维度发现的条目更可信) + existing.crossRefs = existing.crossRefs || [] + existing.crossRefs.push({ source: f.source, perspective: f.perspective, dimension: f.dimension }) + } + } + return [...seen.values()] +} + +// 视角 → 维度映射 +function mapPerspectiveToDimension(perspective) { + const map = { + 'security': 'code', + 'performance': 'code', + 'code-quality': 'code', + 'architecture': 'architecture' + } + return map[perspective] || 'code' } const deduped = deduplicateFindings(allFindings) -// 按严重性排序 +// ─── 3e: 按严重性排序(交叉引用的条目优先)─── deduped.sort((a, b) => { + // 被多角度发现的条目 → 优先级提升 + const aBoost = (a.crossRefs?.length || 0) > 0 ? -0.5 : 0 + const bBoost = (b.crossRefs?.length || 0) > 0 ? -0.5 : 0 const order = { critical: 0, high: 1, medium: 2, low: 3 } - return (order[a.severity] || 3) - (order[b.severity] || 3) + return ((order[a.severity] || 3) + aBoost) - ((order[b.severity] || 3) + bBoost) }) + +// ─── 3f: 用探索上下文增强发现(可选)─── +// 利用 Fan-out A 的结构探索结果标注模块归属 +for (const finding of deduped) { + if (finding.file && exploreContext.structure?.modules) { + const module = exploreContext.structure.modules.find(m => + finding.file.startsWith(m.path) + ) + if (module) finding.module = module.name + } +} ``` ## Output Format @@ -164,12 +341,13 @@ deduped.sort((a, b) => { ``` ## Debt Scan Results -### Dimensions Scanned: [list] +### Scan Mode: [inline|dual-fanout|triple-fanout] ### Complexity: [Low|Medium|High] +### Perspectives: [security, performance, code-quality, architecture] ### Findings by Dimension #### Code Quality ([count]) -- [file:line] [severity] - [description] +- [file:line] [severity] - [description] [crossRefs: N perspectives] #### Architecture ([count]) - [module] [severity] - [description] @@ -183,6 +361,16 @@ deduped.sort((a, b) => { #### Documentation ([count]) - [file:line] [severity] - [description] +### Multi-Perspective Highlights +#### Security Findings ([count]) +- [file:line] [severity] - [CWE-xxx] [description] + +#### Performance Findings ([count]) +- [file:line] [severity] - [impact] [description] + +### Cross-Referenced Items (多角度交叉验证) +- [file:line] confirmed by [N] sources - [description] + ### Total Debt Items: [count] ``` @@ -192,7 +380,9 @@ deduped.sort((a, b) => { |----------|------------| | CLI tool unavailable | Fall back to ACE search + Grep inline analysis | | CLI returns empty for a dimension | Note incomplete dimension, continue others | -| Too many findings (>100) | Prioritize critical/high, summarize medium/low | -| Timeout on CLI call | Use partial results, note incomplete dimensions | +| Subagent explore fails | Skip explore context, proceed with CLI analysis only | +| Too many findings (>100) | Prioritize critical/high + cross-referenced, summarize rest | +| Timeout on CLI call | Use partial results, note incomplete dimensions/perspectives | | Agent/CLI failure | Retry once, then fallback to inline execution | -| Timeout (>5 min per dimension) | Report partial results, notify scanner | +| Perspective analysis timeout | Use dimension-only results, note missing perspectives | +| All Fan-out layers fail | Fall back to ACE inline scan (guaranteed minimum) | diff --git a/.claude/skills/team-tech-debt/roles/scanner/role.md b/.claude/skills/team-tech-debt/roles/scanner/role.md index 62589e0f..09ad26c4 100644 --- a/.claude/skills/team-tech-debt/roles/scanner/role.md +++ b/.claude/skills/team-tech-debt/roles/scanner/role.md @@ -69,13 +69,14 @@ Bash(`echo '${JSON.stringify({ from: "scanner", to: "coordinator", type: "scan_c | Agent Type | Used By | Purpose | |------------|---------|---------| -| `cli-explore-agent` | scan-debt.md | 代码库结构探索 | +| `cli-explore-agent` | scan-debt.md | 并行代码库结构探索(多角度 fan-out) | ### CLI Capabilities | CLI Tool | Mode | Used By | Purpose | |----------|------|---------|---------| -| `gemini` | analysis | scan-debt.md | 多维度代码分析 | +| `gemini` | analysis | scan-debt.md | 多维度代码分析(dimension fan-out) | +| `gemini` | analysis | scan-debt.md | 多视角深度分析(perspective fan-out) | ## Execution (5-Phase) @@ -116,6 +117,19 @@ const hasGoMod = Bash(`test -f go.mod && echo "yes" || echo "no"`).trim() === 'y // 5 个扫描维度 const dimensions = ["code", "architecture", "testing", "dependency", "documentation"] +// 多视角 Gemini 分析(自动检测任务关键词) +function detectPerspectives(desc) { + const perspectives = [] + if (/security|auth|inject|xss|漏洞|安全/.test(desc)) perspectives.push("security") + if (/performance|speed|optimize|memory|性能|优化/.test(desc)) perspectives.push("performance") + if (/quality|clean|maintain|debt|质量|代码/.test(desc)) perspectives.push("code-quality") + if (/architect|pattern|structure|架构|结构/.test(desc)) perspectives.push("architecture") + // 默认至少 2 个视角 + if (perspectives.length === 0) perspectives.push("code-quality", "architecture") + return perspectives +} +const perspectives = detectPerspectives(task.description) + // 评估复杂度 function assessComplexity(desc) { let score = 0 @@ -134,7 +148,7 @@ const complexity = assessComplexity(task.description) Read("commands/scan-debt.md") ``` -**核心策略**: 按维度并行执行 CLI 分析 +**核心策略**: 三层并行 Fan-out(subagent 探索 + CLI 维度分析 + 多视角 Gemini) ```javascript if (complexity === 'Low') { @@ -144,11 +158,11 @@ if (complexity === 'Low') { query: "code smells, TODO/FIXME, deprecated APIs, complex functions, missing tests" }) } else { - // CLI Fan-out: 每个维度一个 CLI 调用 - for (const dimension of dimensions) { - Bash(`ccw cli -p "..." --tool gemini --mode analysis`, { run_in_background: true }) - } - // 等待所有 CLI 完成 + // Fan-out A: 并行 subagent 探索(codebase 结构理解) + // Fan-out B: 每个维度一个 CLI 调用(并行 gemini 分析) + // Fan-out C: 多视角 Gemini 深度分析(并行 perspective 分析) + // → Fan-in: 聚合 + 去重 + 交叉引用 + Read("commands/scan-debt.md") } ``` diff --git a/.claude/skills/team-tech-debt/roles/validator/commands/verify.md b/.claude/skills/team-tech-debt/roles/validator/commands/verify.md index c068bd8e..12f03b8c 100644 --- a/.claude/skills/team-tech-debt/roles/validator/commands/verify.md +++ b/.claude/skills/team-tech-debt/roles/validator/commands/verify.md @@ -51,25 +51,29 @@ const modifiedFiles = fixLog.files_modified || [] // 获取原始债务分数 const debtScoreBefore = sharedMemory.debt_score_before || 0 -// 检测可用的验证工具 -const hasNpm = Bash(`which npm 2>/dev/null && echo "yes" || echo "no"`).trim() === 'yes' -const hasTsc = Bash(`which npx 2>/dev/null && npx tsc --version 2>/dev/null && echo "yes" || echo "no"`).includes('yes') -const hasEslint = Bash(`npx eslint --version 2>/dev/null && echo "yes" || echo "no"`).includes('yes') -const hasPytest = Bash(`which pytest 2>/dev/null && echo "yes" || echo "no"`).trim() === 'yes' +// Worktree 路径(从 shared memory 加载) +const worktreePath = sharedMemory.worktree?.path || null +const cmdPrefix = worktreePath ? `cd "${worktreePath}" && ` : '' + +// 检测可用的验证工具(在 worktree 中检测) +const hasNpm = Bash(`${cmdPrefix}which npm 2>/dev/null && echo "yes" || echo "no"`).trim() === 'yes' +const hasTsc = Bash(`${cmdPrefix}which npx 2>/dev/null && npx tsc --version 2>/dev/null && echo "yes" || echo "no"`).includes('yes') +const hasEslint = Bash(`${cmdPrefix}npx eslint --version 2>/dev/null && echo "yes" || echo "no"`).includes('yes') +const hasPytest = Bash(`${cmdPrefix}which pytest 2>/dev/null && echo "yes" || echo "no"`).trim() === 'yes' ``` ### Step 2: Execute Strategy ```javascript -// === Check 1: Test Suite === +// === Check 1: Test Suite(worktree 中执行) === let testOutput = '' let testsPassed = true let testRegressions = 0 if (hasNpm) { - testOutput = Bash(`npm test 2>&1 || true`) + testOutput = Bash(`${cmdPrefix}npm test 2>&1 || true`) } else if (hasPytest) { - testOutput = Bash(`python -m pytest 2>&1 || true`) + testOutput = Bash(`${cmdPrefix}python -m pytest 2>&1 || true`) } else { testOutput = 'no-test-runner' } @@ -79,17 +83,17 @@ if (testOutput !== 'no-test-runner') { testRegressions = testsPassed ? 0 : (testOutput.match(/(\d+) failed/)?.[1] || 1) * 1 } -// === Check 2: Type Checking === +// === Check 2: Type Checking(worktree 中执行) === let typeErrors = 0 if (hasTsc) { - const tscOutput = Bash(`npx tsc --noEmit 2>&1 || true`) + const tscOutput = Bash(`${cmdPrefix}npx tsc --noEmit 2>&1 || true`) typeErrors = (tscOutput.match(/error TS/g) || []).length } -// === Check 3: Linting === +// === Check 3: Linting(worktree 中执行) === let lintErrors = 0 if (hasEslint && modifiedFiles.length > 0) { - const lintOutput = Bash(`npx eslint --no-error-on-unmatched-pattern ${modifiedFiles.join(' ')} 2>&1 || true`) + const lintOutput = Bash(`${cmdPrefix}npx eslint --no-error-on-unmatched-pattern ${modifiedFiles.join(' ')} 2>&1 || true`) lintErrors = (lintOutput.match(/(\d+) error/)?.[0]?.match(/\d+/)?.[0] || 0) * 1 } @@ -103,7 +107,7 @@ CONTEXT: ${modifiedFiles.map(f => `@${f}`).join(' ')} EXPECTED: Quality comparison with: metrics_before, metrics_after, improvement_score (0-100), new_issues_found CONSTRAINTS: Focus on the specific changes, not overall project quality` - Bash(`ccw cli -p "${prompt}" --tool gemini --mode analysis --rule analysis-review-code-quality`, { + Bash(`ccw cli -p "${prompt}" --tool gemini --mode analysis --rule analysis-review-code-quality${worktreePath ? ' --cd "' + worktreePath + '"' : ''}`, { run_in_background: true }) // 等待 CLI 完成,解析质量改善分数 @@ -137,7 +141,7 @@ if (totalRegressions > 0 && totalRegressions <= 3) { description: `Fix ${totalRegressions} regressions from debt cleanup`, prompt: `## Goal Fix regressions introduced by tech debt cleanup. - +${worktreePath ? `\n## Worktree(强制)\n- 工作目录: ${worktreePath}\n- **所有文件操作必须在 ${worktreePath} 下进行**\n- Bash 命令使用 cd "${worktreePath}" && ... 前缀\n` : ''} ## Regressions ${regressionDetails.join('\n')} diff --git a/.claude/skills/team-tech-debt/roles/validator/role.md b/.claude/skills/team-tech-debt/roles/validator/role.md index ca952d19..4fe52080 100644 --- a/.claude/skills/team-tech-debt/roles/validator/role.md +++ b/.claude/skills/team-tech-debt/roles/validator/role.md @@ -103,6 +103,10 @@ const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() || let sharedMemory = {} try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {} +// 加载 worktree 路径(由 coordinator 写入 shared memory) +const worktreePath = sharedMemory.worktree?.path || null +const cmdPrefix = worktreePath ? `cd "${worktreePath}" && ` : '' + const debtInventory = sharedMemory.debt_inventory || [] const fixResults = sharedMemory.fix_results || {} const debtScoreBefore = sharedMemory.debt_score_before || debtInventory.length @@ -121,7 +125,7 @@ const modifiedFiles = fixLog.files_modified || [] Read("commands/verify.md") ``` -**核心策略**: 4 层验证 +**核心策略**: 4 层验证(所有命令在 worktree 中执行) ```javascript const validationResults = { @@ -131,24 +135,24 @@ const validationResults = { quality_analysis: { status: 'pending', improvement: 0 } } -// 1. 测试套件 -const testResult = Bash(`npm test 2>&1 || npx vitest run 2>&1 || python -m pytest 2>&1 || echo "no-tests"`) +// 1. 测试套件(worktree 中执行) +const testResult = Bash(`${cmdPrefix}npm test 2>&1 || ${cmdPrefix}npx vitest run 2>&1 || ${cmdPrefix}python -m pytest 2>&1 || echo "no-tests"`) const testsPassed = !/FAIL|error|failed/i.test(testResult) || /no-tests/.test(testResult) validationResults.test_suite = { status: testsPassed ? 'PASS' : 'FAIL', regressions: testsPassed ? 0 : (testResult.match(/(\d+) failed/)?.[1] || 1) * 1 } -// 2. 类型检查 -const typeResult = Bash(`npx tsc --noEmit 2>&1 || echo "skip"`) +// 2. 类型检查(worktree 中执行) +const typeResult = Bash(`${cmdPrefix}npx tsc --noEmit 2>&1 || echo "skip"`) const typeErrors = (typeResult.match(/error TS/g) || []).length validationResults.type_check = { status: typeErrors === 0 || /skip/.test(typeResult) ? 'PASS' : 'FAIL', errors: typeErrors } -// 3. Lint 检查 -const lintResult = Bash(`npx eslint --no-error-on-unmatched-pattern ${modifiedFiles.join(' ')} 2>&1 || echo "skip"`) +// 3. Lint 检查(worktree 中执行) +const lintResult = Bash(`${cmdPrefix}npx eslint --no-error-on-unmatched-pattern ${modifiedFiles.join(' ')} 2>&1 || echo "skip"`) const lintErrors = (lintResult.match(/\d+ error/)?.[0]?.match(/\d+/)?.[0] || 0) * 1 validationResults.lint_check = { status: lintErrors === 0 || /skip/.test(lintResult) ? 'PASS' : 'FAIL',