From 65762af25408f889e95950f87463a9bea3bcd58e Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 18 Feb 2026 18:40:12 +0800 Subject: [PATCH] feat: Add explorer and synthesizer roles with commands for codebase exploration and synthesis - Implemented `explorer` role for parallel codebase exploration using `cli-explore-agent`. - Created `explore.md` command documentation detailing exploration strategy and execution steps. - Established `synthesizer` role for integrating insights from explorations, analyses, and discussions. - Developed `synthesize.md` command documentation outlining synthesis strategy and output format. - Configured team settings in `team-config.json` to support new roles and pipeline modes. - Added regression test for CodexLens bootstrap fallback to ensure robustness in error handling. --- .claude/skills/team-ultra-analyze/SKILL.md | 391 ++++++++++++++++++ .../roles/analyst/commands/analyze.md | 210 ++++++++++ .../team-ultra-analyze/roles/analyst/role.md | 290 +++++++++++++ .../roles/coordinator/commands/dispatch.md | 234 +++++++++++ .../roles/coordinator/commands/monitor.md | 376 +++++++++++++++++ .../roles/coordinator/role.md | 328 +++++++++++++++ .../roles/discussant/commands/deepen.md | 222 ++++++++++ .../roles/discussant/role.md | 273 ++++++++++++ .../roles/explorer/commands/explore.md | 194 +++++++++ .../team-ultra-analyze/roles/explorer/role.md | 243 +++++++++++ .../roles/synthesizer/commands/synthesize.md | 255 ++++++++++++ .../roles/synthesizer/role.md | 225 ++++++++++ .../team-ultra-analyze/specs/team-config.json | 131 ++++++ .../codex-lens-bootstrap-fallback.test.js | 93 +++++ 14 files changed, 3465 insertions(+) create mode 100644 .claude/skills/team-ultra-analyze/SKILL.md create mode 100644 .claude/skills/team-ultra-analyze/roles/analyst/commands/analyze.md create mode 100644 .claude/skills/team-ultra-analyze/roles/analyst/role.md create mode 100644 .claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md create mode 100644 .claude/skills/team-ultra-analyze/roles/coordinator/commands/monitor.md create mode 100644 .claude/skills/team-ultra-analyze/roles/coordinator/role.md create mode 100644 .claude/skills/team-ultra-analyze/roles/discussant/commands/deepen.md create mode 100644 .claude/skills/team-ultra-analyze/roles/discussant/role.md create mode 100644 .claude/skills/team-ultra-analyze/roles/explorer/commands/explore.md create mode 100644 .claude/skills/team-ultra-analyze/roles/explorer/role.md create mode 100644 .claude/skills/team-ultra-analyze/roles/synthesizer/commands/synthesize.md create mode 100644 .claude/skills/team-ultra-analyze/roles/synthesizer/role.md create mode 100644 .claude/skills/team-ultra-analyze/specs/team-config.json create mode 100644 ccw/tests/codex-lens-bootstrap-fallback.test.js diff --git a/.claude/skills/team-ultra-analyze/SKILL.md b/.claude/skills/team-ultra-analyze/SKILL.md new file mode 100644 index 00000000..a013b166 --- /dev/null +++ b/.claude/skills/team-ultra-analyze/SKILL.md @@ -0,0 +1,391 @@ +--- +name: team-ultra-analyze +description: Unified team skill for deep collaborative analysis. All roles invoke this skill with --role arg for role-specific execution. Triggers on "team ultra-analyze", "team analyze". +allowed-tools: TeamCreate(*), TeamDelete(*), SendMessage(*), TaskCreate(*), TaskUpdate(*), TaskList(*), TaskGet(*), Task(*), AskUserQuestion(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*) +--- + +# Team Ultra Analyze + +深度协作分析团队技能。将单体分析工作流拆分为 5 角色协作:探索→分析→讨论→综合。支持 Quick/Standard/Deep 三种管道模式,通过讨论循环实现用户引导的渐进式理解深化。所有成员通过 `--role=xxx` 路由到角色执行逻辑。 + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────┐ +│ Skill(skill="team-ultra-analyze", args="--role=xxx") │ +└──────────────────────┬──────────────────────────────────┘ + │ Role Router + ┌──────────┬───────┼───────────┬───────────┐ + ↓ ↓ ↓ ↓ ↓ +┌────────┐┌────────┐┌────────┐┌──────────┐┌───────────┐ +│coordi- ││explorer││analyst ││discussant││synthesizer│ +│nator ││EXPLORE-││ANALYZE-││DISCUSS-* ││SYNTH-* │ +│ roles/ ││* roles/││* roles/││ roles/ ││ roles/ │ +└────────┘└────────┘└────────┘└──────────┘└───────────┘ +``` + +## Command Architecture + +``` +roles/ +├── coordinator/ +│ ├── role.md # 编排:话题澄清、管道选择、讨论循环、结果汇报 +│ └── commands/ +│ ├── dispatch.md # 任务链创建与依赖管理 +│ └── monitor.md # 进度监控 + 讨论循环 +├── explorer/ +│ ├── role.md # 代码库探索 +│ └── commands/ +│ └── explore.md # cli-explore-agent 并行探索 +├── analyst/ +│ ├── role.md # 深度分析 +│ └── commands/ +│ └── analyze.md # CLI 多视角分析 +├── discussant/ +│ ├── role.md # 讨论处理 + 方向调整 +│ └── commands/ +│ └── deepen.md # 深入探索 +└── synthesizer/ + ├── role.md # 综合结论 + └── commands/ + └── synthesize.md # 跨视角整合 +``` + +**设计原则**: role.md 保留 Phase 1(Task Discovery)和 Phase 5(Report)内联。Phase 2-4 根据复杂度决定内联或委派到 `commands/*.md`。 + +## Role Router + +### Input Parsing + +Parse `$ARGUMENTS` to extract `--role`: + +```javascript +const args = "$ARGUMENTS" +const roleMatch = args.match(/--role[=\s]+(\w+)/) + +if (!roleMatch) { + throw new Error("Missing --role argument. Available roles: coordinator, explorer, analyst, discussant, synthesizer") +} + +const role = roleMatch[1] +const teamName = args.match(/--team[=\s]+([\w-]+)/)?.[1] || "ultra-analyze" +``` + +### Role Dispatch + +```javascript +const VALID_ROLES = { + "coordinator": { file: "roles/coordinator/role.md", prefix: null }, + "explorer": { file: "roles/explorer/role.md", prefix: "EXPLORE" }, + "analyst": { file: "roles/analyst/role.md", prefix: "ANALYZE" }, + "discussant": { file: "roles/discussant/role.md", prefix: "DISCUSS" }, + "synthesizer": { file: "roles/synthesizer/role.md", prefix: "SYNTH" } +} + +if (!VALID_ROLES[role]) { + throw new Error(`Unknown role: ${role}. Available: ${Object.keys(VALID_ROLES).join(', ')}`) +} + +// Read and execute role-specific logic +Read(VALID_ROLES[role].file) +// → Execute the 5-phase process defined in that file +``` + +### Available Roles + +| Role | Task Prefix | Responsibility | Role File | +|------|-------------|----------------|-----------| +| `coordinator` | N/A | 话题澄清、管道选择、会话管理、讨论循环 | [roles/coordinator/role.md](roles/coordinator/role.md) | +| `explorer` | EXPLORE-* | cli-explore-agent 多角度并行代码库探索 | [roles/explorer/role.md](roles/explorer/role.md) | +| `analyst` | ANALYZE-* | CLI 多视角深度分析 | [roles/analyst/role.md](roles/analyst/role.md) | +| `discussant` | DISCUSS-* | 用户反馈处理、方向调整、深入分析 | [roles/discussant/role.md](roles/discussant/role.md) | +| `synthesizer` | SYNTH-* | 跨视角整合、结论生成、决策追踪 | [roles/synthesizer/role.md](roles/synthesizer/role.md) | + +## Shared Infrastructure + +### Role Isolation Rules + +**核心原则**: 每个角色仅能执行自己职责范围内的工作。 + +#### Output Tagging(强制) + +所有角色的输出必须带 `[role_name]` 标识前缀: + +```javascript +SendMessage({ content: `## [${role}] ...`, summary: `[${role}] ...` }) +mcp__ccw-tools__team_msg({ summary: `[${role}] ...` }) +``` + +#### Coordinator 隔离 + +| 允许 | 禁止 | +|------|------| +| 话题澄清 (AskUserQuestion) | ❌ 直接执行代码探索或分析 | +| 创建任务链 (TaskCreate) | ❌ 直接调用 cli-explore-agent | +| 管道选择 + 讨论循环驱动 | ❌ 直接调用 CLI 分析工具 | +| 监控进度 (消息总线) | ❌ 绕过 worker 自行完成 | + +#### Worker 隔离 + +| 允许 | 禁止 | +|------|------| +| 处理自己前缀的任务 | ❌ 处理其他角色前缀的任务 | +| 读写 shared-memory.json (自己的字段) | ❌ 为其他角色创建任务 | +| SendMessage 给 coordinator | ❌ 直接与其他 worker 通信 | + +### Team Configuration + +```javascript +const TEAM_CONFIG = { + name: "ultra-analyze", + sessionDir: ".workflow/.team/UAN-{slug}-{date}/", + msgDir: ".workflow/.team-msg/ultra-analyze/", + sharedMemory: "shared-memory.json", + analysisDimensions: ["architecture", "implementation", "performance", "security", "concept", "comparison", "decision"], + maxDiscussionRounds: 5 +} +``` + +### Shared Memory(核心产物) + +```javascript +// 各角色读取共享记忆 +const memoryPath = `${sessionFolder}/shared-memory.json` +let sharedMemory = {} +try { sharedMemory = JSON.parse(Read(memoryPath)) } catch {} + +// 各角色写入自己负责的字段: +// explorer → sharedMemory.explorations +// analyst → sharedMemory.analyses +// discussant → sharedMemory.discussions +// synthesizer → sharedMemory.synthesis +// coordinator → sharedMemory.decision_trail + current_understanding +Write(memoryPath, JSON.stringify(sharedMemory, null, 2)) +``` + +### Message Bus (All Roles) + +```javascript +mcp__ccw-tools__team_msg({ + operation: "log", + team: teamName, + from: role, + to: "coordinator", + type: "", + summary: `[${role}] `, + ref: "" +}) +``` + +| Role | Types | +|------|-------| +| coordinator | `pipeline_selected`, `discussion_round`, `direction_adjusted`, `task_unblocked`, `error`, `shutdown` | +| explorer | `exploration_ready`, `error` | +| analyst | `analysis_ready`, `error` | +| discussant | `discussion_processed`, `error` | +| synthesizer | `synthesis_ready`, `error` | + +### CLI 回退 + +```javascript +Bash(`ccw team log --team "${teamName}" --from "${role}" --to "coordinator" --type "" --summary "<摘要>" --json`) +``` + +### Task Lifecycle (All Worker Roles) + +```javascript +const tasks = TaskList() +const myTasks = tasks.filter(t => + t.subject.startsWith(`${VALID_ROLES[role].prefix}-`) && + t.owner === role && + t.status === 'pending' && + t.blockedBy.length === 0 +) +if (myTasks.length === 0) return +const task = TaskGet({ taskId: myTasks[0].id }) +TaskUpdate({ taskId: task.id, status: 'in_progress' }) + +// Phase 2-4: Role-specific +// Phase 5: Report + Loop +mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: role, to: "coordinator", type: "...", summary: `[${role}] ...` }) +SendMessage({ type: "message", recipient: "coordinator", content: `## [${role}] ...`, summary: `[${role}] ...` }) +TaskUpdate({ taskId: task.id, status: 'completed' }) +``` + +## Three-Mode Pipeline Architecture + +``` +Quick: EXPLORE-001 → ANALYZE-001 → SYNTH-001 +Standard: [EXPLORE-001..N](parallel) → [ANALYZE-001..N](parallel) → DISCUSS-001 → SYNTH-001 +Deep: [EXPLORE-001..N] → [ANALYZE-001..N] → DISCUSS-001 → ANALYZE-fix → DISCUSS-002 → ... → SYNTH-001 +``` + +### Mode Auto-Detection + +```javascript +function detectPipelineMode(args, taskDescription) { + const modeMatch = args.match(/--mode[=\s]+(quick|standard|deep)/) + if (modeMatch) return modeMatch[1] + // 自动检测 + if (/快速|quick|overview|概览/.test(taskDescription)) return 'quick' + if (/深入|deep|thorough|详细|全面/.test(taskDescription)) return 'deep' + return 'standard' +} +``` + +### Discussion Loop (Deep Mode) + +``` +coordinator(AskUser) → DISCUSS-N(deepen) → [optional ANALYZE-fix] → coordinator(AskUser) → ... → SYNTH +``` + +## Decision Recording Protocol + +**⚠️ CRITICAL**: 继承自原 analyze-with-file 命令。分析过程中以下情况必须立即记录到 discussion.md: + +| Trigger | What to Record | Target Section | +|---------|---------------|----------------| +| **Direction choice** | 选择了什么、为什么、放弃了哪些替代方案 | `#### Decision Log` | +| **Key finding** | 发现内容、影响范围、置信度 | `#### Key Findings` | +| **Assumption change** | 旧假设→新理解、变更原因、影响 | `#### Corrected Assumptions` | +| **User feedback** | 用户原始输入、采纳/调整理由 | `#### User Input` | + +## Unified Session Directory + +``` +.workflow/.team/UAN-{slug}-{YYYY-MM-DD}/ +├── shared-memory.json # 探索/分析/讨论/综合 共享记忆 +├── discussion.md # ⭐ 理解演进 & 讨论时间线 +├── explorations/ # Explorer output +│ ├── exploration-001.json +│ └── exploration-002.json +├── analyses/ # Analyst output +│ ├── analysis-001.json +│ └── analysis-002.json +├── discussions/ # Discussant output +│ └── discussion-round-001.json +└── conclusions.json # Synthesizer output +``` + +## Coordinator Spawn Template + +```javascript +TeamCreate({ team_name: teamName }) + +// Explorer +Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "explorer", + prompt: `你是 team "${teamName}" 的 EXPLORER。 + +当你收到 EXPLORE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=explorer") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 EXPLORE-* 前缀的任务,不得执行其他角色的工作 +- 所有输出(SendMessage、team_msg)必须带 [explorer] 标识前缀 +- 仅与 coordinator 通信,不得直接联系其他 worker +- 不得使用 TaskCreate 为其他角色创建任务 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 EXPLORE-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=explorer") 执行 +3. team_msg log + SendMessage 结果给 coordinator(带 [explorer] 标识) +4. TaskUpdate completed → 检查下一个任务` +}) + +// Analyst +Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "analyst", + prompt: `你是 team "${teamName}" 的 ANALYST。 + +当你收到 ANALYZE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=analyst") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 ANALYZE-* 前缀的任务 +- 所有输出必须带 [analyst] 标识前缀 +- 仅与 coordinator 通信 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 ANALYZE-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=analyst") 执行 +3. team_msg log + SendMessage 结果给 coordinator +4. TaskUpdate completed → 检查下一个任务` +}) + +// Discussant +Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "discussant", + prompt: `你是 team "${teamName}" 的 DISCUSSANT。 + +当你收到 DISCUSS-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=discussant") 执行。 + +当前需求: ${taskDescription} + +## 角色准则(强制) +- 你只能处理 DISCUSS-* 前缀的任务 +- 所有输出必须带 [discussant] 标识前缀 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 DISCUSS-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=discussant") 执行 +3. team_msg log + SendMessage +4. TaskUpdate completed → 检查下一个任务` +}) + +// Synthesizer +Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "synthesizer", + prompt: `你是 team "${teamName}" 的 SYNTHESIZER。 + +当你收到 SYNTH-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=synthesizer") 执行。 + +当前需求: ${taskDescription} + +## 角色准则(强制) +- 你只能处理 SYNTH-* 前缀的任务 +- 所有输出必须带 [synthesizer] 标识前缀 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 SYNTH-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=synthesizer") 执行 +3. team_msg log + SendMessage +4. TaskUpdate completed → 检查下一个任务` +}) +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| Unknown --role value | Error with available role list | +| Missing --role arg | Error with usage hint | +| Role file not found | Error with expected path (roles/{name}/role.md) | +| Task prefix conflict | Log warning, proceed | +| Discussion loop stuck >5 rounds | Force synthesis, offer continuation | +| CLI tool unavailable | Fallback chain: gemini → codex → manual analysis | +| Explorer agent fails | Continue with available context, note limitation | diff --git a/.claude/skills/team-ultra-analyze/roles/analyst/commands/analyze.md b/.claude/skills/team-ultra-analyze/roles/analyst/commands/analyze.md new file mode 100644 index 00000000..e3ece55b --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/analyst/commands/analyze.md @@ -0,0 +1,210 @@ +# Command: analyze + +> CLI 多视角深度分析。基于探索结果,通过 CLI 工具执行深度分析并生成结构化洞察。 + +## When to Use + +- Phase 3 of Analyst +- 探索结果已就绪,需要深度分析 +- 每个 ANALYZE-* 任务触发一次 + +**Trigger conditions**: +- Analyst Phase 2 完成后(上下文已加载) +- 方向调整时创建的 ANALYZE-fix 任务 + +## Strategy + +### Delegation Mode + +**Mode**: CLI(通过 ccw cli 执行分析,Bash run_in_background: true) + +### Decision Logic + +```javascript +// 根据 perspective 选择 CLI 工具和分析模板 +function buildAnalysisConfig(perspective, isDirectionFix) { + const configs = { + 'technical': { + tool: 'gemini', + rule: 'analysis-analyze-code-patterns', + focus: 'Implementation patterns, code quality, technical debt, feasibility', + tasks: [ + 'Analyze code structure and organization patterns', + 'Identify technical debt and anti-patterns', + 'Evaluate error handling and edge cases', + 'Assess testing coverage and quality' + ] + }, + 'architectural': { + tool: 'claude', + rule: 'analysis-review-architecture', + focus: 'System design, scalability, component coupling, boundaries', + tasks: [ + 'Evaluate module boundaries and coupling', + 'Analyze data flow and component interactions', + 'Assess scalability and extensibility', + 'Review design pattern usage and consistency' + ] + }, + 'business': { + tool: 'codex', + rule: 'analysis-analyze-code-patterns', + focus: 'Business logic, domain models, value delivery, stakeholder impact', + tasks: [ + 'Map business logic to code implementation', + 'Identify domain model completeness', + 'Evaluate business rule enforcement', + 'Assess impact on stakeholders and users' + ] + }, + 'domain_expert': { + tool: 'gemini', + rule: 'analysis-analyze-code-patterns', + focus: 'Domain-specific patterns, standards compliance, best practices', + tasks: [ + 'Compare against domain best practices', + 'Check standards and convention compliance', + 'Identify domain-specific anti-patterns', + 'Evaluate domain model accuracy' + ] + } + } + + const config = configs[perspective] || configs['technical'] + + if (isDirectionFix) { + config.rule = 'analysis-diagnose-bug-root-cause' + config.tasks = [ + 'Re-analyze from adjusted perspective', + 'Identify previously missed patterns', + 'Generate new insights from fresh angle', + 'Update discussion points based on direction change' + ] + } + + return config +} +``` + +## Execution Steps + +### Step 1: Context Preparation + +```javascript +const config = buildAnalysisConfig(perspective, isDirectionFix) + +// 构建探索上下文摘要 +const explorationSummary = ` +PRIOR EXPLORATION CONTEXT: +- Key files: ${(explorationContext.relevant_files || []).slice(0, 8).map(f => f.path || f).join(', ')} +- Patterns found: ${(explorationContext.patterns || []).slice(0, 5).join('; ')} +- Key findings: ${(explorationContext.key_findings || []).slice(0, 5).join('; ')} +- Questions from exploration: ${(explorationContext.questions_for_analysis || []).slice(0, 3).join('; ')}` +``` + +### Step 2: Execute CLI Analysis + +```javascript +const cliPrompt = `PURPOSE: ${isDirectionFix + ? `Supplementary analysis with adjusted focus on "${adjustedFocus}" for topic "${topic}"` + : `Deep analysis of "${topic}" from ${perspective} perspective`} +Success: ${isDirectionFix + ? 'New insights from adjusted direction with clear evidence' + : 'Actionable insights with confidence levels and evidence references'} + +${explorationSummary} + +TASK: +${config.tasks.map(t => `• ${t}`).join('\n')} +• Generate structured findings with confidence levels (high/medium/low) +• Identify discussion points requiring user input +• List open questions needing further exploration + +MODE: analysis +CONTEXT: @**/* | Topic: ${topic} +EXPECTED: JSON-structured analysis with sections: key_insights (with confidence), key_findings (with evidence), discussion_points, open_questions, recommendations (with priority) +CONSTRAINTS: Focus on ${perspective} perspective | ${dimensions.join(', ')} dimensions${isDirectionFix ? ` | Adjusted focus: ${adjustedFocus}` : ''}` + +Bash({ + command: `ccw cli -p "${cliPrompt}" --tool ${config.tool} --mode analysis --rule ${config.rule}`, + run_in_background: true +}) + +// ⚠️ STOP POINT: Wait for CLI callback before continuing +``` + +### Step 3: Result Processing + +```javascript +// CLI 结果返回后,解析并结构化 +const outputPath = `${sessionFolder}/analyses/analysis-${analyzeNum}.json` + +// 从 CLI 输出中提取结构化数据 +// CLI 输出通常是 markdown,需要解析为 JSON +const analysisResult = { + perspective, + dimensions, + is_direction_fix: isDirectionFix, + adjusted_focus: adjustedFocus || null, + key_insights: [ + // 从 CLI 输出提取,每个包含 {insight, confidence, evidence} + ], + key_findings: [ + // 具体发现 {finding, file_ref, impact} + ], + discussion_points: [ + // 需要用户输入的讨论要点 + ], + open_questions: [ + // 未解决的问题 + ], + recommendations: [ + // {action, rationale, priority} + ], + _metadata: { + cli_tool: config.tool, + cli_rule: config.rule, + perspective, + is_direction_fix: isDirectionFix, + timestamp: new Date().toISOString() + } +} + +Write(outputPath, JSON.stringify(analysisResult, null, 2)) +``` + +## Output Format + +```json +{ + "perspective": "technical", + "dimensions": ["architecture", "implementation"], + "is_direction_fix": false, + "key_insights": [ + {"insight": "Authentication uses stateless JWT", "confidence": "high", "evidence": "src/auth/jwt.ts:L42"} + ], + "key_findings": [ + {"finding": "No rate limiting on login endpoint", "file_ref": "src/routes/auth.ts:L15", "impact": "Security risk"} + ], + "discussion_points": [ + "Should we implement token rotation for refresh tokens?" + ], + "open_questions": [ + "What is the expected concurrent user load?" + ], + "recommendations": [ + {"action": "Add rate limiting to auth endpoints", "rationale": "Prevent brute force attacks", "priority": "high"} + ], + "_metadata": {"cli_tool": "gemini", "cli_rule": "analysis-analyze-code-patterns", "timestamp": "..."} +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| CLI tool unavailable | Try fallback: gemini → codex → claude | +| CLI timeout | Retry with shorter prompt, or use exploration results directly | +| CLI returns empty | Use exploration findings as-is, note analysis gap | +| Invalid CLI output | Extract what's parseable, fill gaps with defaults | +| Exploration context missing | Analyze with topic keywords only | diff --git a/.claude/skills/team-ultra-analyze/roles/analyst/role.md b/.claude/skills/team-ultra-analyze/roles/analyst/role.md new file mode 100644 index 00000000..fc80b98a --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/analyst/role.md @@ -0,0 +1,290 @@ +# Role: analyst + +深度分析师。基于 explorer 的代码库探索结果,通过 CLI 多视角深度分析,生成结构化洞察和讨论要点。 + +## Role Identity + +- **Name**: `analyst` +- **Task Prefix**: `ANALYZE-*` +- **Responsibility**: Read-only analysis(深度分析) +- **Communication**: SendMessage to coordinator only +- **Output Tag**: `[analyst]` + +## Role Boundaries + +### MUST + +- 仅处理 `ANALYZE-*` 前缀的任务 +- 所有输出必须带 `[analyst]` 标识 +- 仅通过 SendMessage 与 coordinator 通信 +- 基于 explorer 的探索结果进行深度分析 +- 将分析结果写入 shared-memory.json 的 `analyses` 字段 + +### MUST NOT + +- ❌ 执行代码库探索(属于 explorer) +- ❌ 处理用户反馈(属于 discussant) +- ❌ 生成最终结论(属于 synthesizer) +- ❌ 为其他角色创建任务 +- ❌ 直接与其他 worker 通信 +- ❌ 修改源代码 + +## Message Types + +| Type | Direction | Trigger | Description | +|------|-----------|---------|-------------| +| `analysis_ready` | analyst → coordinator | 分析完成 | 包含洞察、讨论要点、开放问题 | +| `error` | analyst → coordinator | 分析失败 | 阻塞性错误 | + +## Toolbox + +### Available Commands + +| Command | File | Phase | Description | +|---------|------|-------|-------------| +| `analyze` | [commands/analyze.md](commands/analyze.md) | Phase 3 | CLI 多视角深度分析 | + +### Subagent Capabilities + +> Analyst 不直接使用 subagent + +### CLI Capabilities + +| CLI Tool | Mode | Used By | Purpose | +|----------|------|---------|---------| +| `gemini` | analysis | analyze.md | 技术/领域分析 | +| `codex` | analysis | analyze.md | 业务视角分析 | +| `claude` | analysis | analyze.md | 架构视角分析 | + +## Execution (5-Phase) + +### Phase 1: Task Discovery + +```javascript +const tasks = TaskList() +const myTasks = tasks.filter(t => + t.subject.startsWith('ANALYZE-') && + t.owner === 'analyst' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (myTasks.length === 0) return // idle + +const task = TaskGet({ taskId: myTasks[0].id }) +TaskUpdate({ taskId: task.id, status: 'in_progress' }) +``` + +### Phase 2: Context Loading + +```javascript +// 从任务描述中提取上下文 +const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() +const topic = task.description.match(/topic:\s*(.+)/)?.[1]?.trim() +const perspective = task.description.match(/perspective:\s*(.+)/)?.[1]?.trim() || 'technical' +const dimensions = (task.description.match(/dimensions:\s*(.+)/)?.[1]?.trim() || 'general').split(', ') +const isDirectionFix = task.description.includes('type: direction-fix') +const adjustedFocus = task.description.match(/adjusted_focus:\s*(.+)/)?.[1]?.trim() + +// 读取 shared memory +let sharedMemory = {} +try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {} + +// 读取对应的探索结果 +const analyzeNum = task.subject.match(/ANALYZE-(\w+)/)?.[1] || '001' +let explorationContext = {} + +if (isDirectionFix) { + // 方向调整:读取所有已有探索结果 + const explorationFiles = Glob({ pattern: `${sessionFolder}/explorations/*.json` }) + const allExplorations = explorationFiles.map(f => JSON.parse(Read(f))) + explorationContext = { + relevant_files: allExplorations.flatMap(e => e.relevant_files || []).slice(0, 10), + patterns: allExplorations.flatMap(e => e.patterns || []), + key_findings: allExplorations.flatMap(e => e.key_findings || []) + } +} else { + // 正常分析:读取对应编号的探索结果 + try { + explorationContext = JSON.parse(Read(`${sessionFolder}/explorations/exploration-${analyzeNum}.json`)) + } catch { + // 尝试读取任意可用的探索结果 + const explorationFiles = Glob({ pattern: `${sessionFolder}/explorations/*.json` }) + if (explorationFiles.length > 0) { + explorationContext = JSON.parse(Read(explorationFiles[0])) + } + } +} + +// 确定 CLI 工具 +const PERSPECTIVE_TOOLS = { + 'technical': 'gemini', + 'architectural': 'claude', + 'business': 'codex', + 'domain_expert': 'gemini' +} +const cliTool = PERSPECTIVE_TOOLS[perspective] || 'gemini' +``` + +### Phase 3: Deep Analysis via CLI + +```javascript +// Read commands/analyze.md for full CLI analysis implementation +Read("commands/analyze.md") +``` + +**核心策略**: 基于探索结果,通过 CLI 执行深度分析 + +```javascript +const analysisPrompt = isDirectionFix + ? `PURPOSE: 补充分析 - 方向调整至 "${adjustedFocus}" +Success: 针对新方向的深入洞察 + +PRIOR EXPLORATION CONTEXT: +- Key files: ${(explorationContext.relevant_files || []).slice(0, 5).map(f => f.path || f).join(', ')} +- Patterns: ${(explorationContext.patterns || []).slice(0, 3).join(', ')} +- Previous findings: ${(explorationContext.key_findings || []).slice(0, 3).join(', ')} + +TASK: +• Focus analysis on: ${adjustedFocus} +• Build on previous exploration findings +• Identify new insights from adjusted perspective +• Generate discussion points for user + +MODE: analysis +CONTEXT: @**/* | Topic: ${topic} +EXPECTED: Structured analysis with adjusted focus, new insights, updated discussion points +CONSTRAINTS: Focus on ${adjustedFocus}` + : `PURPOSE: Analyze topic '${topic}' from ${perspective} perspective across ${dimensions.join(', ')} dimensions +Success: Actionable insights with clear reasoning and evidence + +PRIOR EXPLORATION CONTEXT: +- Key files: ${(explorationContext.relevant_files || []).slice(0, 5).map(f => f.path || f).join(', ')} +- Patterns found: ${(explorationContext.patterns || []).slice(0, 3).join(', ')} +- Key findings: ${(explorationContext.key_findings || []).slice(0, 3).join(', ')} + +TASK: +• Build on exploration findings above +• Analyze from ${perspective} perspective: ${dimensions.join(', ')} +• Identify patterns, anti-patterns, and opportunities +• Generate discussion points for user clarification +• Assess confidence level for each insight + +MODE: analysis +CONTEXT: @**/* | Topic: ${topic} +EXPECTED: Structured analysis with: key insights (with confidence), discussion points, open questions, recommendations with rationale +CONSTRAINTS: Focus on ${dimensions.join(', ')} | ${perspective} perspective` + +Bash({ + command: `ccw cli -p "${analysisPrompt}" --tool ${cliTool} --mode analysis`, + run_in_background: true +}) + +// ⚠️ STOP POINT: Wait for CLI callback +``` + +### Phase 4: Result Aggregation + +```javascript +// CLI 结果返回后,构建分析输出 +const outputPath = `${sessionFolder}/analyses/analysis-${analyzeNum}.json` + +const analysisResult = { + perspective, + dimensions, + is_direction_fix: isDirectionFix, + adjusted_focus: adjustedFocus || null, + key_insights: [], // 从 CLI 结果提取 + key_findings: [], // 具体发现 + discussion_points: [], // 讨论要点 + open_questions: [], // 开放问题 + recommendations: [], // 建议 + confidence_levels: {}, // 各洞察的置信度 + evidence: [], // 证据引用 + _metadata: { + cli_tool: cliTool, + perspective, + timestamp: new Date().toISOString() + } +} + +Write(outputPath, JSON.stringify(analysisResult, null, 2)) +``` + +### Phase 5: Report to Coordinator + +```javascript +// 更新 shared memory +sharedMemory.analyses = sharedMemory.analyses || [] +sharedMemory.analyses.push({ + id: `analysis-${analyzeNum}`, + perspective, + is_direction_fix: isDirectionFix, + insight_count: analysisResult.key_insights?.length || 0, + finding_count: analysisResult.key_findings?.length || 0, + timestamp: new Date().toISOString() +}) +Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2)) + +const resultSummary = `${perspective} 视角: ${analysisResult.key_insights?.length || 0} 个洞察, ${analysisResult.discussion_points?.length || 0} 个讨论点` + +mcp__ccw-tools__team_msg({ + operation: "log", + team: teamName, + from: "analyst", + to: "coordinator", + type: "analysis_ready", + summary: `[analyst] ${resultSummary}`, + ref: outputPath +}) + +SendMessage({ + type: "message", + recipient: "coordinator", + content: `## [analyst] Analysis Results + +**Task**: ${task.subject} +**Perspective**: ${perspective}${isDirectionFix ? ` (Direction Fix: ${adjustedFocus})` : ''} +**CLI Tool**: ${cliTool} + +### Summary +${resultSummary} + +### Key Insights +${(analysisResult.key_insights || []).slice(0, 5).map(i => `- ${i}`).join('\n')} + +### Discussion Points +${(analysisResult.discussion_points || []).slice(0, 3).map(p => `- ${p}`).join('\n')} + +### Open Questions +${(analysisResult.open_questions || []).slice(0, 3).map(q => `- ${q}`).join('\n')} + +### Output +${outputPath}`, + summary: `[analyst] ANALYZE complete: ${resultSummary}` +}) + +TaskUpdate({ taskId: task.id, status: 'completed' }) + +// Check for next task +const nextTasks = TaskList().filter(t => + t.subject.startsWith('ANALYZE-') && + t.owner === 'analyst' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (nextTasks.length > 0) { + // Continue with next task → back to Phase 1 +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| No ANALYZE-* tasks available | Idle, wait for coordinator assignment | +| CLI tool unavailable | Fallback chain: gemini → codex → claude | +| No exploration results found | Analyze with topic keywords only, note limitation | +| CLI timeout | Use partial results, report incomplete | +| Invalid exploration JSON | Skip context, analyze from scratch | diff --git a/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md new file mode 100644 index 00000000..3097f363 --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md @@ -0,0 +1,234 @@ +# Command: dispatch + +> 任务链创建与依赖管理。根据管道模式创建 pipeline 任务链并分配给 worker 角色。 + +## When to Use + +- Phase 3 of Coordinator +- 管道模式已确定,需要创建任务链 +- 团队已创建,worker 已 spawn + +**Trigger conditions**: +- Coordinator Phase 2 完成后 +- 讨论循环中需要创建补充分析任务 +- 方向调整需要创建新探索/分析任务 + +## Strategy + +### Delegation Mode + +**Mode**: Direct(coordinator 直接操作 TaskCreate/TaskUpdate) + +### Decision Logic + +```javascript +// 根据 pipelineMode 和 perspectives 选择 pipeline +function buildPipeline(pipelineMode, perspectives, sessionFolder, taskDescription, dimensions) { + const pipelines = { + 'quick': [ + { prefix: 'EXPLORE', suffix: '001', owner: 'explorer', desc: '代码库探索', meta: `perspective: general\ndimensions: ${dimensions.join(', ')}`, blockedBy: [] }, + { prefix: 'ANALYZE', suffix: '001', owner: 'analyst', desc: '综合分析', meta: `perspective: technical\ndimensions: ${dimensions.join(', ')}`, blockedBy: ['EXPLORE-001'] }, + { prefix: 'SYNTH', suffix: '001', owner: 'synthesizer', desc: '结论综合', blockedBy: ['ANALYZE-001'] } + ], + 'standard': buildStandardPipeline(perspectives, dimensions), + 'deep': buildDeepPipeline(perspectives, dimensions) + } + return pipelines[pipelineMode] || pipelines['standard'] +} + +function buildStandardPipeline(perspectives, dimensions) { + const stages = [] + const perspectiveList = perspectives.length > 0 ? perspectives : ['technical'] + + // Parallel explorations + perspectiveList.forEach((p, i) => { + const num = String(i + 1).padStart(3, '0') + stages.push({ + prefix: 'EXPLORE', suffix: num, owner: 'explorer', + desc: `代码库探索 (${p})`, + meta: `perspective: ${p}\ndimensions: ${dimensions.join(', ')}`, + blockedBy: [] + }) + }) + + // Parallel analyses (blocked by corresponding exploration) + perspectiveList.forEach((p, i) => { + const num = String(i + 1).padStart(3, '0') + stages.push({ + prefix: 'ANALYZE', suffix: num, owner: 'analyst', + desc: `深度分析 (${p})`, + meta: `perspective: ${p}\ndimensions: ${dimensions.join(', ')}`, + blockedBy: [`EXPLORE-${num}`] + }) + }) + + // Discussion (blocked by all analyses) + const analyzeIds = perspectiveList.map((_, i) => `ANALYZE-${String(i + 1).padStart(3, '0')}`) + stages.push({ + prefix: 'DISCUSS', suffix: '001', owner: 'discussant', + desc: '讨论处理 (Round 1)', + meta: `round: 1\ntype: initial`, + blockedBy: analyzeIds + }) + + // Synthesis (blocked by discussion) + stages.push({ + prefix: 'SYNTH', suffix: '001', owner: 'synthesizer', + desc: '结论综合', + blockedBy: ['DISCUSS-001'] + }) + + return stages +} + +function buildDeepPipeline(perspectives, dimensions) { + // Same as standard but SYNTH is not created initially + // It will be created after discussion loop completes + const stages = buildStandardPipeline(perspectives, dimensions) + // Remove SYNTH — will be created dynamically after discussion loop + return stages.filter(s => s.prefix !== 'SYNTH') +} +``` + +## Execution Steps + +### Step 1: Context Preparation + +```javascript +const pipeline = buildPipeline(pipelineMode, selectedPerspectives, sessionFolder, taskDescription, dimensions) +``` + +### Step 2: Execute Strategy + +```javascript +const taskIds = {} + +for (const stage of pipeline) { + const taskSubject = `${stage.prefix}-${stage.suffix}: ${stage.desc}` + + // 构建任务描述(包含 session 和上下文信息) + const fullDesc = [ + stage.desc, + `\nsession: ${sessionFolder}`, + `\ntopic: ${taskDescription}`, + stage.meta ? `\n${stage.meta}` : '', + `\n\n目标: ${taskDescription}` + ].join('') + + // 创建任务 + TaskCreate({ + subject: taskSubject, + description: fullDesc, + activeForm: `${stage.desc}进行中` + }) + + // 记录任务 ID + const allTasks = TaskList() + const newTask = allTasks.find(t => t.subject.startsWith(`${stage.prefix}-${stage.suffix}`)) + taskIds[`${stage.prefix}-${stage.suffix}`] = newTask.id + + // 设置 owner 和依赖 + const blockedByIds = stage.blockedBy + .map(dep => taskIds[dep]) + .filter(Boolean) + + TaskUpdate({ + taskId: newTask.id, + owner: stage.owner, + addBlockedBy: blockedByIds + }) +} +``` + +### Step 3: Result Processing + +```javascript +// 验证任务链 +const allTasks = TaskList() +const chainTasks = pipeline.map(s => taskIds[`${s.prefix}-${s.suffix}`]).filter(Boolean) +const chainValid = chainTasks.length === pipeline.length + +if (!chainValid) { + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "error", + summary: `[coordinator] 任务链创建不完整: ${chainTasks.length}/${pipeline.length}` + }) +} +``` + +## Discussion Loop Task Creation + +讨论循环中动态创建任务: + +```javascript +// 创建新一轮讨论任务 +function createDiscussionTask(round, type, userFeedback, sessionFolder) { + const suffix = String(round).padStart(3, '0') + TaskCreate({ + subject: `DISCUSS-${suffix}: 讨论处理 (Round ${round})`, + description: `讨论处理\nsession: ${sessionFolder}\nround: ${round}\ntype: ${type}\nuser_feedback: ${userFeedback}`, + activeForm: `讨论 Round ${round} 进行中` + }) + + const allTasks = TaskList() + const newTask = allTasks.find(t => t.subject.startsWith(`DISCUSS-${suffix}`)) + TaskUpdate({ taskId: newTask.id, owner: 'discussant' }) + return newTask.id +} + +// 创建补充分析任务(方向调整时) +function createAnalysisFix(round, adjustedFocus, sessionFolder) { + const suffix = `fix-${round}` + TaskCreate({ + subject: `ANALYZE-${suffix}: 补充分析 (方向调整 Round ${round})`, + description: `补充分析\nsession: ${sessionFolder}\nadjusted_focus: ${adjustedFocus}\ntype: direction-fix`, + activeForm: `补充分析 Round ${round} 进行中` + }) + + const allTasks = TaskList() + const newTask = allTasks.find(t => t.subject.startsWith(`ANALYZE-${suffix}`)) + TaskUpdate({ taskId: newTask.id, owner: 'analyst' }) + return newTask.id +} + +// 创建最终综合任务 +function createSynthesisTask(sessionFolder, blockedByIds) { + TaskCreate({ + subject: `SYNTH-001: 结论综合`, + description: `跨视角整合\nsession: ${sessionFolder}\ntype: final`, + activeForm: `结论综合进行中` + }) + + const allTasks = TaskList() + const newTask = allTasks.find(t => t.subject.startsWith('SYNTH-001')) + TaskUpdate({ + taskId: newTask.id, + owner: 'synthesizer', + addBlockedBy: blockedByIds + }) + return newTask.id +} +``` + +## Output Format + +``` +## Task Chain Created + +### Mode: [quick|standard|deep] +### Pipeline Stages: [count] +- [prefix]-[suffix]: [description] (owner: [role], blocked by: [deps]) + +### Verification: PASS/FAIL +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| Task creation fails | Retry once, then report to user | +| Dependency cycle detected | Flatten dependencies, warn coordinator | +| Invalid pipelineMode | Default to 'standard' mode | +| Too many perspectives (>4) | Truncate to first 4, warn user | +| Timeout (>5 min) | Report partial results, notify coordinator | diff --git a/.claude/skills/team-ultra-analyze/roles/coordinator/commands/monitor.md b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/monitor.md new file mode 100644 index 00000000..399b9d8a --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/monitor.md @@ -0,0 +1,376 @@ +# Command: monitor + +> 阶段驱动的协调循环 + 讨论循环。按 pipeline 阶段顺序等待 worker 完成,驱动讨论循环,执行最终综合触发。 + +## When to Use + +- Phase 4 of Coordinator +- 任务链已创建并分发 +- 需要持续监控直到所有任务完成 + +**Trigger conditions**: +- dispatch 完成后立即启动 +- 讨论循环创建新任务后重新进入 + +## Strategy + +### Delegation Mode + +**Mode**: Stage-driven(按阶段顺序等待,非轮询)+ Discussion-loop(讨论循环由 coordinator 驱动) + +### 设计原则 + +> **模型执行没有时间概念**。禁止空转 while 循环检查状态。 +> 使用固定 sleep 间隔 + 最大轮询次数,避免无意义的 API 调用浪费。 + +### Decision Logic + +```javascript +// 消息路由表 +const routingTable = { + // Explorer 完成 + 'exploration_ready': { action: 'Mark EXPLORE complete, unblock ANALYZE' }, + // Analyst 完成 + 'analysis_ready': { action: 'Mark ANALYZE complete, unblock DISCUSS or SYNTH' }, + // Discussant 完成 + 'discussion_processed': { action: 'Mark DISCUSS complete, trigger user feedback collection', special: 'discussion_feedback' }, + // Synthesizer 完成 + 'synthesis_ready': { action: 'Mark SYNTH complete, prepare final report', special: 'finalize' }, + // 错误 + 'error': { action: 'Assess severity, retry or escalate', special: 'error_handler' } +} +``` + +### 等待策略常量 + +```javascript +const POLL_INTERVAL_SEC = 300 // 每次检查间隔 5 分钟 +const MAX_POLLS_PER_STAGE = 6 // 单阶段最多等待 6 次(~30 分钟) +const SLEEP_CMD = process.platform === 'win32' + ? `timeout /t ${POLL_INTERVAL_SEC} /nobreak >nul 2>&1` + : `sleep ${POLL_INTERVAL_SEC}` + +// ★ 统一 auto mode 检测 +const autoYes = /\b(-y|--yes)\b/.test(args) +``` + +## Execution Steps + +### Step 1: Context Preparation + +```javascript +// 从 shared memory 获取当前状态 +const sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) + +let discussionRound = 0 +const MAX_DISCUSSION_ROUNDS = pipelineMode === 'deep' ? 5 : (pipelineMode === 'standard' ? 1 : 0) + +// 获取 pipeline 阶段列表(来自 dispatch 创建的任务链) +const allTasks = TaskList() +const pipelineTasks = allTasks + .filter(t => t.owner && t.owner !== 'coordinator') + .sort((a, b) => Number(a.id) - Number(b.id)) +``` + +### Step 2: Stage-Driven Execution (Exploration + Analysis) + +> 按 pipeline 阶段顺序,逐阶段等待完成。 + +```javascript +// 处理 EXPLORE 和 ANALYZE 阶段 +const preDiscussionTasks = pipelineTasks.filter(t => + t.subject.startsWith('EXPLORE-') || t.subject.startsWith('ANALYZE-') +) + +for (const stageTask of preDiscussionTasks) { + let stageComplete = false + let pollCount = 0 + + while (!stageComplete && pollCount < MAX_POLLS_PER_STAGE) { + Bash(SLEEP_CMD) + pollCount++ + + // 1. 检查消息总线 + const messages = mcp__ccw-tools__team_msg({ + operation: "list", team: teamName, last: 5 + }) + + // 2. 路由消息 + for (const msg of messages) { + const handler = routingTable[msg.type] + if (!handler) continue + processMessage(msg, handler) + } + + // 3. 确认任务状态(兜底) + const currentTask = TaskGet({ taskId: stageTask.id }) + stageComplete = currentTask.status === 'completed' || currentTask.status === 'deleted' + } + + // 阶段超时处理 + if (!stageComplete) { + handleStageTimeout(stageTask, pollCount, autoYes) + } +} +``` + +### Step 2.1: Update discussion.md with Round 1 + +```javascript +// 读取所有探索和分析结果 +const explorationFiles = Glob({ pattern: `${sessionFolder}/explorations/*.json` }) +const analysisFiles = Glob({ pattern: `${sessionFolder}/analyses/*.json` }) + +const explorations = explorationFiles.map(f => JSON.parse(Read(f))) +const analyses = analysisFiles.map(f => JSON.parse(Read(f))) + +// 更新 discussion.md — Round 1 +const round1Content = ` +### Round 1 - Initial Exploration & Analysis (${new Date().toISOString()}) + +#### Exploration Results +${explorations.map(e => `- **${e.perspective || 'general'}**: ${e.key_findings?.slice(0, 3).join('; ') || 'No findings'}`).join('\n')} + +#### Analysis Results +${analyses.map(a => `- **${a.perspective || 'general'}**: ${a.key_insights?.slice(0, 3).join('; ') || 'No insights'}`).join('\n')} + +#### Key Findings +${analyses.flatMap(a => a.key_findings || []).slice(0, 5).map(f => `- ${f}`).join('\n')} + +#### Discussion Points +${analyses.flatMap(a => a.discussion_points || []).slice(0, 5).map(p => `- ${p}`).join('\n')} + +#### Decision Log +> **Decision**: Selected ${pipelineMode} pipeline with ${explorations.length} exploration(s) and ${analyses.length} analysis perspective(s) +> - **Context**: Topic analysis and user preference +> - **Chosen**: ${pipelineMode} mode — **Reason**: ${pipelineMode === 'quick' ? 'Fast overview requested' : pipelineMode === 'deep' ? 'Thorough analysis needed' : 'Balanced depth and breadth'} +` + +Edit({ + file_path: `${sessionFolder}/discussion.md`, + old_string: '## Discussion Timeline\n', + new_string: `## Discussion Timeline\n${round1Content}\n` +}) +``` + +### Step 3: Discussion Loop (Standard/Deep mode) + +```javascript +if (MAX_DISCUSSION_ROUNDS === 0) { + // Quick mode: skip discussion, go to synthesis + createSynthesisTask(sessionFolder, [lastAnalyzeTaskId]) +} else { + // Wait for initial DISCUSS-001 to complete + // Then enter discussion loop + + while (discussionRound < MAX_DISCUSSION_ROUNDS) { + // 等待当前 DISCUSS 任务完成 + const currentDiscussId = `DISCUSS-${String(discussionRound + 1).padStart(3, '0')}` + // ... wait for completion (same pattern as Step 2) + + // 收集用户反馈 + const feedbackResult = AskUserQuestion({ + questions: [{ + question: `Round ${discussionRound + 1} 分析结果已就绪。请选择下一步:`, + header: "Discussion Feedback", + multiSelect: false, + options: [ + { label: "同意,继续深入", description: "分析方向正确,继续深入探索" }, + { label: "需要调整方向", description: "有不同理解或关注点" }, + { label: "分析完成", description: "已获得足够信息" }, + { label: "有具体问题", description: "有特定问题需要解答" } + ] + }] + }) + + const feedback = feedbackResult["Discussion Feedback"] + + // 📌 记录用户反馈到 decision_trail + const latestMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) + latestMemory.decision_trail.push({ + round: discussionRound + 1, + decision: feedback, + context: `User feedback at discussion round ${discussionRound + 1}`, + timestamp: new Date().toISOString() + }) + Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(latestMemory, null, 2)) + + if (feedback === "分析完成") { + // 📌 Record completion decision + appendToDiscussion(sessionFolder, discussionRound + 1, { + user_input: "分析完成", + decision: "Exit discussion loop, proceed to synthesis", + reason: "User satisfied with current analysis depth" + }) + break + } + + if (feedback === "需要调整方向") { + // 收集调整方向 + const directionResult = AskUserQuestion({ + questions: [{ + question: "请选择新的关注方向:", + header: "Direction Adjustment", + multiSelect: false, + options: [ + { label: "代码细节", description: "深入具体代码实现" }, + { label: "架构层面", description: "关注系统架构设计" }, + { label: "最佳实践", description: "对比行业最佳实践" }, + { label: "自定义", description: "输入自定义方向" } + ] + }] + }) + + const newDirection = directionResult["Direction Adjustment"] + + // 📌 Record direction change + appendToDiscussion(sessionFolder, discussionRound + 1, { + user_input: `调整方向: ${newDirection}`, + decision: `Direction adjusted to: ${newDirection}`, + reason: "User requested focus change" + }) + + // 创建补充分析 + 新讨论任务 + const fixId = createAnalysisFix(discussionRound + 1, newDirection, sessionFolder) + discussionRound++ + createDiscussionTask(discussionRound + 1, 'direction-adjusted', newDirection, sessionFolder) + continue + } + + if (feedback === "有具体问题") { + // 📌 Record question + appendToDiscussion(sessionFolder, discussionRound + 1, { + user_input: "有具体问题(由 discussant 处理)", + decision: "Create discussion task for specific questions" + }) + + discussionRound++ + createDiscussionTask(discussionRound + 1, 'specific-questions', 'User has specific questions', sessionFolder) + continue + } + + // 同意,继续深入 + appendToDiscussion(sessionFolder, discussionRound + 1, { + user_input: "同意,继续深入", + decision: "Continue deepening in current direction" + }) + + discussionRound++ + if (discussionRound < MAX_DISCUSSION_ROUNDS) { + createDiscussionTask(discussionRound + 1, 'deepen', 'Continue current direction', sessionFolder) + } + } + + // 创建最终综合任务 + const lastDiscussTaskId = getLastCompletedTaskId('DISCUSS') + createSynthesisTask(sessionFolder, [lastDiscussTaskId]) +} +``` + +### Step 3.1: Discussion Helper Functions + +```javascript +function appendToDiscussion(sessionFolder, round, data) { + const roundContent = ` +### Round ${round + 1} - Discussion (${new Date().toISOString()}) + +#### User Input +${data.user_input} + +#### Decision Log +> **Decision**: ${data.decision} +> - **Context**: Discussion round ${round + 1} +> - **Reason**: ${data.reason || 'User-directed'} + +#### Updated Understanding +${data.updated_understanding || '(Updated by discussant)'} + +` + // Append to discussion.md + const currentContent = Read(`${sessionFolder}/discussion.md`) + Write(`${sessionFolder}/discussion.md`, currentContent + roundContent) +} + +function handleStageTimeout(stageTask, pollCount, autoYes) { + const elapsedMin = Math.round(pollCount * POLL_INTERVAL_SEC / 60) + + if (autoYes) { + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "error", + summary: `[coordinator] [auto] 阶段 ${stageTask.subject} 超时 (${elapsedMin}min),自动跳过` + }) + TaskUpdate({ taskId: stageTask.id, status: 'deleted' }) + return + } + + const decision = AskUserQuestion({ + questions: [{ + question: `阶段 "${stageTask.subject}" 已等待 ${elapsedMin} 分钟仍未完成。如何处理?`, + header: "Stage Wait", + multiSelect: false, + options: [ + { label: "继续等待", description: `再等 ${MAX_POLLS_PER_STAGE} 轮` }, + { label: "跳过此阶段", description: "标记为跳过,继续后续流水线" }, + { label: "终止流水线", description: "停止整个分析流程" } + ] + }] + }) + + const answer = decision["Stage Wait"] + if (answer === "跳过此阶段") { + TaskUpdate({ taskId: stageTask.id, status: 'deleted' }) + } else if (answer === "终止流水线") { + mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "shutdown", + summary: `[coordinator] 用户终止流水线,当前阶段: ${stageTask.subject}` + }) + } +} +``` + +### Step 4: Wait for Synthesis + Result Processing + +```javascript +// 等待 SYNTH-001 完成 +// ... same wait pattern + +// 汇总所有结果 +const finalMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) +const allFinalTasks = TaskList() +const workerTasks = allFinalTasks.filter(t => t.owner && t.owner !== 'coordinator') +const summary = { + total_tasks: workerTasks.length, + completed_tasks: workerTasks.filter(t => t.status === 'completed').length, + discussion_rounds: discussionRound, + has_synthesis: !!finalMemory.synthesis, + decisions_made: finalMemory.decision_trail?.length || 0 +} +``` + +## Output Format + +``` +## Coordination Summary + +### Pipeline Status: COMPLETE +### Mode: [quick|standard|deep] +### Tasks: [completed]/[total] +### Discussion Rounds: [count] +### Decisions Made: [count] + +### Message Log (last 10) +- [timestamp] [from] → [to]: [type] - [summary] +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| Message bus unavailable | Fall back to TaskList polling only | +| Stage timeout (交互模式) | AskUserQuestion:继续等待 / 跳过 / 终止 | +| Stage timeout (自动模式) | 自动跳过,记录日志 | +| Teammate unresponsive (2x) | Respawn teammate with same task | +| Discussion loop stuck >5 rounds | Force synthesis, offer continuation | +| Synthesis fails | Report partial results from analyses | diff --git a/.claude/skills/team-ultra-analyze/roles/coordinator/role.md b/.claude/skills/team-ultra-analyze/roles/coordinator/role.md new file mode 100644 index 00000000..838d195a --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/coordinator/role.md @@ -0,0 +1,328 @@ +# Role: coordinator + +分析团队协调者。编排 pipeline:话题澄清 → 管道选择 → 团队创建 → 任务分发 → 讨论循环 → 结果汇报。 + +## Role Identity + +- **Name**: `coordinator` +- **Task Prefix**: N/A (coordinator creates tasks, doesn't receive them) +- **Responsibility**: Orchestration +- **Communication**: SendMessage to all teammates +- **Output Tag**: `[coordinator]` + +## Role Boundaries + +### MUST + +- 所有输出(SendMessage、team_msg、日志)必须带 `[coordinator]` 标识 +- 仅负责话题澄清、管道选择、任务创建/分发、讨论循环驱动、结果汇报 +- 通过 TaskCreate 创建任务并分配给 worker 角色 +- 通过消息总线监控 worker 进度并路由消息 +- 讨论循环中通过 AskUserQuestion 收集用户反馈 + +### MUST NOT + +- ❌ **直接执行任何业务任务**(代码探索、CLI 分析、综合整合等) +- ❌ 直接调用 cli-explore-agent、code-developer 等实现类 subagent +- ❌ 直接调用 CLI 分析工具(ccw cli) +- ❌ 绕过 worker 角色自行完成应委派的工作 +- ❌ 在输出中省略 `[coordinator]` 标识 + +> **核心原则**: coordinator 是指挥者,不是执行者。所有实际工作必须通过 TaskCreate 委派给 worker 角色。 + +## Message Types + +| Type | Direction | Trigger | Description | +|------|-----------|---------|-------------| +| `pipeline_selected` | coordinator → all | 管道模式确定 | Quick/Standard/Deep | +| `discussion_round` | coordinator → discussant | 用户反馈收集后 | 触发讨论处理 | +| `direction_adjusted` | coordinator → analyst | 方向调整 | 触发补充分析 | +| `task_unblocked` | coordinator → worker | 依赖解除 | 任务可执行 | +| `error` | coordinator → user | 协调错误 | 阻塞性问题 | +| `shutdown` | coordinator → all | 团队关闭 | 清理资源 | + +## Toolbox + +### Available Commands + +| Command | File | Phase | Description | +|---------|------|-------|-------------| +| `dispatch` | [commands/dispatch.md](commands/dispatch.md) | Phase 3 | 任务链创建与依赖管理 | +| `monitor` | [commands/monitor.md](commands/monitor.md) | Phase 4 | 讨论循环 + 进度监控 | + +### Subagent Capabilities + +> Coordinator 不直接使用 subagent(通过 worker 角色间接使用) + +### CLI Capabilities + +> Coordinator 不直接使用 CLI 分析工具 + +## Execution + +### Phase 1: Topic Understanding & Requirement Clarification + +```javascript +const args = "$ARGUMENTS" + +// 提取话题描述 +const taskDescription = args.replace(/--role[=\s]+\w+/, '').replace(/--team[=\s]+[\w-]+/, '').replace(/--mode[=\s]+\w+/, '').trim() + +// ★ 统一 auto mode 检测 +const autoYes = /\b(-y|--yes)\b/.test(args) + +// 管道模式选择 +function detectPipelineMode(args, desc) { + const modeMatch = args.match(/--mode[=\s]+(quick|standard|deep)/) + if (modeMatch) return modeMatch[1] + if (/快速|quick|overview|概览/.test(desc)) return 'quick' + if (/深入|deep|thorough|详细|全面/.test(desc)) return 'deep' + return 'standard' +} + +let pipelineMode = detectPipelineMode(args, taskDescription) + +// 维度检测 +const DIMENSION_KEYWORDS = { + architecture: /架构|architecture|design|structure|设计/, + implementation: /实现|implement|code|coding|代码/, + performance: /性能|performance|optimize|bottleneck|优化/, + security: /安全|security|auth|permission|权限/, + concept: /概念|concept|theory|principle|原理/, + comparison: /比较|compare|vs|difference|区别/, + decision: /决策|decision|choice|tradeoff|选择/ +} + +const detectedDimensions = Object.entries(DIMENSION_KEYWORDS) + .filter(([_, regex]) => regex.test(taskDescription)) + .map(([dim]) => dim) + +const dimensions = detectedDimensions.length > 0 ? detectedDimensions : ['general'] + +// 交互式澄清(非 auto 模式) +if (!autoYes) { + // 1. Focus 方向选择 + const DIMENSION_DIRECTIONS = { + architecture: ['System Design', 'Component Interactions', 'Technology Choices', 'Design Patterns', 'Scalability Strategy'], + implementation: ['Code Structure', 'Implementation Details', 'Code Patterns', 'Error Handling', 'Algorithm Analysis'], + performance: ['Performance Bottlenecks', 'Optimization Opportunities', 'Resource Utilization', 'Caching Strategy'], + security: ['Security Vulnerabilities', 'Authentication/Authorization', 'Access Control', 'Data Protection'], + concept: ['Conceptual Foundation', 'Core Mechanisms', 'Fundamental Patterns', 'Trade-offs & Reasoning'], + comparison: ['Solution Comparison', 'Pros & Cons Analysis', 'Technology Evaluation'], + decision: ['Decision Criteria', 'Trade-off Analysis', 'Risk Assessment', 'Impact Analysis'], + general: ['Overview', 'Key Patterns', 'Potential Issues', 'Improvement Opportunities'] + } + + const directionOptions = dimensions.flatMap(d => (DIMENSION_DIRECTIONS[d] || []).slice(0, 3)) + .map(d => ({ label: d, description: `Focus on ${d}` })) + + const focusResult = AskUserQuestion({ + questions: [{ + question: "选择分析方向(可多选)", + header: "Analysis Focus", + multiSelect: true, + options: directionOptions + }] + }) + + // 2. 视角选择(Standard/Deep 模式) + let selectedPerspectives = ['technical'] + if (pipelineMode !== 'quick') { + const perspectiveResult = AskUserQuestion({ + questions: [{ + question: "选择分析视角(可多选,最多4个)", + header: "Analysis Perspectives", + multiSelect: true, + options: [ + { label: "Technical", description: "实现、代码模式、技术可行性" }, + { label: "Architectural", description: "系统设计、可扩展性、组件交互" }, + { label: "Business", description: "价值、ROI、利益相关者影响" }, + { label: "Domain Expert", description: "领域特定模式、最佳实践、标准" } + ] + }] + }) + // Parse selected perspectives + } + + // 3. 深度选择 + const depthResult = AskUserQuestion({ + questions: [{ + question: "选择分析深度", + header: "Analysis Depth", + multiSelect: false, + options: [ + { label: "Quick Overview", description: "快速概览 (10-15min)" }, + { label: "Standard Analysis", description: "标准分析 (30-60min)" }, + { label: "Deep Dive", description: "深度分析 (1-2hr)" } + ] + }] + }) + + const depthMap = { 'Quick Overview': 'quick', 'Standard Analysis': 'standard', 'Deep Dive': 'deep' } + pipelineMode = depthMap[depthResult["Analysis Depth"]] || pipelineMode +} +``` + +### Phase 2: Create Team + Spawn Teammates + +```javascript +const teamName = "ultra-analyze" +const sessionSlug = taskDescription.slice(0, 30).replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '-') +const sessionDate = new Date().toISOString().slice(0, 10) +const sessionFolder = `.workflow/.team/UAN-${sessionSlug}-${sessionDate}` +Bash(`mkdir -p "${sessionFolder}/explorations" "${sessionFolder}/analyses" "${sessionFolder}/discussions"`) + +// 初始化 shared memory +Write(`${sessionFolder}/shared-memory.json`, JSON.stringify({ + explorations: [], + analyses: [], + discussions: [], + synthesis: null, + decision_trail: [], + current_understanding: { + established: [], + clarified: [], + key_insights: [] + } +}, null, 2)) + +// 初始化 discussion.md +Write(`${sessionFolder}/discussion.md`, `# Analysis Discussion + +## Session Metadata +- **ID**: UAN-${sessionSlug}-${sessionDate} +- **Topic**: ${taskDescription} +- **Started**: ${new Date().toISOString()} +- **Dimensions**: ${dimensions.join(', ')} +- **Pipeline**: ${pipelineMode} + +## User Context +- **Focus Areas**: ${dimensions.join(', ')} +- **Analysis Depth**: ${pipelineMode} + +## Initial Understanding +- **Dimensions**: ${dimensions.join(', ')} +- **Scope**: ${taskDescription} + +## Discussion Timeline + +`) + +TeamCreate({ team_name: teamName }) + +// Spawn teammates (see SKILL.md Coordinator Spawn Template) +// Explorer, Analyst, Discussant, Synthesizer +``` + +### Phase 3: Create Task Chain + +根据 pipelineMode 创建不同的任务链: + +```javascript +// Read commands/dispatch.md for full implementation +Read("commands/dispatch.md") +``` + +**Quick Mode**: +``` +EXPLORE-001 → ANALYZE-001 → SYNTH-001 +``` + +**Standard Mode**: +``` +[EXPLORE-001..N](parallel) → [ANALYZE-001..N](parallel) → DISCUSS-001 → SYNTH-001 +``` + +**Deep Mode**: +``` +[EXPLORE-001..N](parallel) → [ANALYZE-001..N](parallel) → DISCUSS-001 → [ANALYZE-fix] → DISCUSS-002 → ... → SYNTH-001 +``` + +### Phase 4: Discussion Loop + Coordination + +```javascript +// Read commands/monitor.md for full implementation +Read("commands/monitor.md") +``` + +| Received Message | Action | +|-----------------|--------| +| `exploration_ready` | 标记 EXPLORE complete → 解锁 ANALYZE | +| `analysis_ready` | 标记 ANALYZE complete → 解锁 DISCUSS 或 SYNTH | +| `discussion_processed` | 标记 DISCUSS complete → AskUser → 决定下一步 | +| `synthesis_ready` | 标记 SYNTH complete → 进入 Phase 5 | +| Worker: `error` | 评估严重性 → 重试或上报用户 | + +**讨论循环逻辑** (Standard/Deep mode): +```javascript +let discussionRound = 0 +const MAX_ROUNDS = pipelineMode === 'deep' ? 5 : 1 + +while (discussionRound < MAX_ROUNDS) { + // 等待 DISCUSS-N 完成 + // AskUserQuestion: 同意继续 / 调整方向 / 分析完成 / 有具体问题 + // 根据用户选择: + // 同意继续 → 创建 DISCUSS-(N+1) + // 调整方向 → 创建 ANALYZE-fix + DISCUSS-(N+1) + // 分析完成 → 退出循环,创建 SYNTH-001 + // 有具体问题 → 创建 DISCUSS-(N+1) with questions + discussionRound++ +} +``` + +### Phase 5: Report + Persist + +```javascript +// 读取 shared memory 汇总结果 +const memory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) + +const report = { + mode: pipelineMode, + topic: taskDescription, + explorations_count: memory.explorations?.length || 0, + analyses_count: memory.analyses?.length || 0, + discussion_rounds: memory.discussions?.length || 0, + decisions_made: memory.decision_trail?.length || 0, + has_synthesis: !!memory.synthesis +} + +mcp__ccw-tools__team_msg({ + operation: "log", team: teamName, from: "coordinator", + to: "user", type: "pipeline_selected", + summary: `[coordinator] 分析完成: ${report.explorations_count}次探索, ${report.analyses_count}次分析, ${report.discussion_rounds}轮讨论` +}) + +SendMessage({ + content: `## [coordinator] Analysis Complete\n\n${JSON.stringify(report, null, 2)}\n\n📄 Discussion: ${sessionFolder}/discussion.md\n📊 Conclusions: ${sessionFolder}/conclusions.json`, + summary: `[coordinator] Analysis complete: ${pipelineMode} mode` +}) + +// 询问下一步(auto 模式跳过,默认关闭团队) +if (!autoYes) { + AskUserQuestion({ + questions: [{ + question: "分析流程已完成。下一步:", + header: "Next", + multiSelect: false, + options: [ + { label: "创建Issue", description: "基于结论创建 Issue" }, + { label: "生成任务", description: "启动 workflow-lite-plan 规划实施" }, + { label: "导出报告", description: "生成独立分析报告" }, + { label: "关闭团队", description: "关闭所有 teammate 并清理" } + ] + }] + }) +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| Teammate unresponsive | Send follow-up, 2x → respawn | +| Explorer finds nothing | Continue with limited context, note limitation | +| Discussion loop stuck >5 rounds | Force synthesis, offer continuation | +| CLI unavailable | Fallback chain: gemini → codex → manual | +| User timeout in discussion | Save state, show resume command | +| Max rounds reached | Force synthesis, offer continuation option | +| Session folder conflict | Append timestamp suffix | diff --git a/.claude/skills/team-ultra-analyze/roles/discussant/commands/deepen.md b/.claude/skills/team-ultra-analyze/roles/discussant/commands/deepen.md new file mode 100644 index 00000000..e6ca96ba --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/discussant/commands/deepen.md @@ -0,0 +1,222 @@ +# Command: deepen + +> 深入探索与补充分析。根据讨论类型执行针对性的代码探索或 CLI 分析。 + +## When to Use + +- Phase 3 of Discussant +- 用户反馈已收集,需要深入处理 +- 每个 DISCUSS-* 任务触发一次 + +**Trigger conditions**: +- initial: 首轮讨论,汇总分析结果 +- deepen: 继续深入当前方向 +- direction-adjusted: 方向调整后重新分析 +- specific-questions: 回答用户具体问题 + +## Strategy + +### Delegation Mode + +**Mode**: Mixed(简单汇总内联,深入探索用 subagent/CLI) + +### Decision Logic + +```javascript +function selectDeepenStrategy(discussType, complexity) { + const strategies = { + 'initial': { + mode: 'inline', + description: 'Summarize all analysis results into discussion format' + }, + 'deepen': { + mode: complexity === 'High' ? 'cli' : 'subagent', + description: 'Further exploration in current direction' + }, + 'direction-adjusted': { + mode: 'cli', + description: 'Re-analyze from new perspective' + }, + 'specific-questions': { + mode: 'subagent', + description: 'Targeted exploration to answer questions' + } + } + return strategies[discussType] || strategies['initial'] +} +``` + +## Execution Steps + +### Step 1: Strategy Selection + +```javascript +const strategy = selectDeepenStrategy(discussType, assessComplexity(userFeedback)) +``` + +### Step 2: Execute by Type + +#### Initial Discussion + +```javascript +function processInitialDiscussion() { + // 汇总所有分析结果 + const summary = { + perspectives_analyzed: allAnalyses.map(a => a.perspective), + total_insights: currentInsights.length, + total_findings: currentFindings.length, + convergent_themes: identifyConvergentThemes(allAnalyses), + conflicting_views: identifyConflicts(allAnalyses), + top_discussion_points: discussionPoints.slice(0, 5), + open_questions: openQuestions.slice(0, 5) + } + + roundContent.updated_understanding.new_insights = summary.convergent_themes + roundContent.new_findings = currentFindings.slice(0, 10) + roundContent.new_questions = openQuestions.slice(0, 5) +} + +function identifyConvergentThemes(analyses) { + // 跨视角找共同主题 + const allInsights = analyses.flatMap(a => + (a.key_insights || []).map(i => typeof i === 'string' ? i : i.insight) + ) + // 简单去重 + 聚合 + return [...new Set(allInsights)].slice(0, 5) +} + +function identifyConflicts(analyses) { + // 识别视角间的矛盾 + return [] // 由实际分析结果决定 +} +``` + +#### Deepen Discussion + +```javascript +function processDeepenDiscussion() { + // 在当前方向上进一步探索 + Task({ + subagent_type: "cli-explore-agent", + run_in_background: false, + description: `Deepen exploration: ${topic} (round ${round})`, + prompt: ` +## Context +Topic: ${topic} +Round: ${round} +Previous findings: ${currentFindings.slice(0, 5).join('; ')} +Open questions: ${openQuestions.slice(0, 3).join('; ')} + +## MANDATORY FIRST STEPS +1. Focus on open questions from previous analysis +2. Search for specific patterns mentioned in findings +3. Look for edge cases and exceptions + +## Exploration Focus +- Deepen understanding of confirmed patterns +- Investigate open questions +- Find additional evidence for uncertain insights + +## Output +Write to: ${sessionFolder}/discussions/deepen-${discussNum}.json +Schema: {new_findings, answered_questions, remaining_questions, evidence} +` + }) + + // 读取深入探索结果 + let deepenResult = {} + try { + deepenResult = JSON.parse(Read(`${sessionFolder}/discussions/deepen-${discussNum}.json`)) + } catch {} + + roundContent.updated_understanding.new_insights = deepenResult.new_findings || [] + roundContent.new_findings = deepenResult.new_findings || [] + roundContent.new_questions = deepenResult.remaining_questions || [] +} +``` + +#### Direction Adjusted + +```javascript +function processDirectionAdjusted() { + // 方向调整后,通过 CLI 重新分析 + Bash({ + command: `ccw cli -p "PURPOSE: Re-analyze '${topic}' with adjusted focus on '${userFeedback}' +Success: New insights from adjusted direction + +PREVIOUS ANALYSIS CONTEXT: +- Previous insights: ${currentInsights.slice(0, 5).map(i => typeof i === 'string' ? i : i.insight).join('; ')} +- Direction change reason: User requested focus on '${userFeedback}' + +TASK: +• Re-evaluate findings from new perspective +• Identify what changes with adjusted focus +• Find new patterns relevant to adjusted direction +• Note what previous findings remain valid + +MODE: analysis +CONTEXT: @**/* | Topic: ${topic} +EXPECTED: Updated analysis with: validated findings, new insights, invalidated assumptions +CONSTRAINTS: Focus on ${userFeedback} +" --tool gemini --mode analysis`, + run_in_background: true + }) + + // ⚠️ STOP: Wait for CLI callback + + roundContent.updated_understanding.corrected = ['Direction adjusted per user request'] + roundContent.updated_understanding.new_insights = [] // From CLI result +} +``` + +#### Specific Questions + +```javascript +function processSpecificQuestions() { + // 针对用户问题进行探索 + Task({ + subagent_type: "cli-explore-agent", + run_in_background: false, + description: `Answer questions: ${topic}`, + prompt: ` +## Context +Topic: ${topic} +User questions: ${userFeedback} +Known findings: ${currentFindings.slice(0, 5).join('; ')} + +## MANDATORY FIRST STEPS +1. Search for code related to user's questions +2. Trace execution paths relevant to questions +3. Check configuration and environment factors + +## Output +Write to: ${sessionFolder}/discussions/questions-${discussNum}.json +Schema: {answers: [{question, answer, evidence, confidence}], follow_up_questions} +` + }) + + let questionResult = {} + try { + questionResult = JSON.parse(Read(`${sessionFolder}/discussions/questions-${discussNum}.json`)) + } catch {} + + roundContent.updated_understanding.new_insights = + (questionResult.answers || []).map(a => `Q: ${a.question} → A: ${a.answer}`) + roundContent.new_questions = questionResult.follow_up_questions || [] +} +``` + +### Step 3: Result Processing + +```javascript +// 结果已写入 roundContent,由 role.md Phase 4 处理 +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| cli-explore-agent fails | Use existing analysis results, note limitation | +| CLI timeout | Report partial results | +| No previous analyses | Process as initial with empty context | +| User feedback unparseable | Treat as 'deepen' type | diff --git a/.claude/skills/team-ultra-analyze/roles/discussant/role.md b/.claude/skills/team-ultra-analyze/roles/discussant/role.md new file mode 100644 index 00000000..48096a1d --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/discussant/role.md @@ -0,0 +1,273 @@ +# Role: discussant + +讨论处理者。根据 coordinator 传递的用户反馈,执行方向调整、深入探索或补充分析,更新讨论时间线。 + +## Role Identity + +- **Name**: `discussant` +- **Task Prefix**: `DISCUSS-*` +- **Responsibility**: Analysis + Exploration(讨论处理) +- **Communication**: SendMessage to coordinator only +- **Output Tag**: `[discussant]` + +## Role Boundaries + +### MUST + +- 仅处理 `DISCUSS-*` 前缀的任务 +- 所有输出必须带 `[discussant]` 标识 +- 仅通过 SendMessage 与 coordinator 通信 +- 基于用户反馈和已有分析结果执行深入探索 +- 将讨论结果写入 shared-memory.json 的 `discussions` 字段 +- 更新 discussion.md 的讨论时间线 + +### MUST NOT + +- ❌ 直接与用户交互(AskUserQuestion 由 coordinator 驱动) +- ❌ 生成最终结论(属于 synthesizer) +- ❌ 为其他角色创建任务 +- ❌ 直接与其他 worker 通信 +- ❌ 修改源代码 + +## Message Types + +| Type | Direction | Trigger | Description | +|------|-----------|---------|-------------| +| `discussion_processed` | discussant → coordinator | 讨论处理完成 | 包含更新的理解和新发现 | +| `error` | discussant → coordinator | 处理失败 | 阻塞性错误 | + +## Toolbox + +### Available Commands + +| Command | File | Phase | Description | +|---------|------|-------|-------------| +| `deepen` | [commands/deepen.md](commands/deepen.md) | Phase 3 | 深入探索与补充分析 | + +### Subagent Capabilities + +| Agent Type | Used By | Purpose | +|------------|---------|---------| +| `cli-explore-agent` | deepen.md | 针对性代码库探索 | + +### CLI Capabilities + +| CLI Tool | Mode | Used By | Purpose | +|----------|------|---------|---------| +| `gemini` | analysis | deepen.md | 深入分析 | + +## Execution (5-Phase) + +### Phase 1: Task Discovery + +```javascript +const tasks = TaskList() +const myTasks = tasks.filter(t => + t.subject.startsWith('DISCUSS-') && + t.owner === 'discussant' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (myTasks.length === 0) return // idle + +const task = TaskGet({ taskId: myTasks[0].id }) +TaskUpdate({ taskId: task.id, status: 'in_progress' }) +``` + +### Phase 2: Context Loading + +```javascript +// 从任务描述中提取上下文 +const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() +const topic = task.description.match(/topic:\s*(.+)/)?.[1]?.trim() +const round = parseInt(task.description.match(/round:\s*(\d+)/)?.[1] || '1') +const discussType = task.description.match(/type:\s*(.+)/)?.[1]?.trim() || 'initial' +const userFeedback = task.description.match(/user_feedback:\s*(.+)/)?.[1]?.trim() || '' + +// 读取 shared memory +let sharedMemory = {} +try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {} + +// 读取已有分析结果 +const analysisFiles = Glob({ pattern: `${sessionFolder}/analyses/*.json` }) +const allAnalyses = analysisFiles.map(f => { + try { return JSON.parse(Read(f)) } catch { return null } +}).filter(Boolean) + +// 读取已有探索结果 +const explorationFiles = Glob({ pattern: `${sessionFolder}/explorations/*.json` }) +const allExplorations = explorationFiles.map(f => { + try { return JSON.parse(Read(f)) } catch { return null } +}).filter(Boolean) + +// 聚合当前理解 +const currentFindings = allAnalyses.flatMap(a => a.key_findings || []) +const currentInsights = allAnalyses.flatMap(a => a.key_insights || []) +const openQuestions = allAnalyses.flatMap(a => a.open_questions || []) +const discussionPoints = allAnalyses.flatMap(a => a.discussion_points || []) +``` + +### Phase 3: Discussion Processing + +```javascript +// Read commands/deepen.md for full implementation +Read("commands/deepen.md") +``` + +**根据 discussType 选择处理策略**: + +```javascript +const discussNum = task.subject.match(/DISCUSS-(\d+)/)?.[1] || '001' +const outputPath = `${sessionFolder}/discussions/discussion-round-${discussNum}.json` + +switch (discussType) { + case 'initial': + // 首轮讨论:汇总所有分析结果,生成讨论摘要 + processInitialDiscussion() + break + + case 'deepen': + // 继续深入:在当前方向上进一步探索 + processDeepenDiscussion() + break + + case 'direction-adjusted': + // 方向调整:基于新方向重新组织发现 + processDirectionAdjusted() + break + + case 'specific-questions': + // 具体问题:针对用户问题进行分析 + processSpecificQuestions() + break +} +``` + +### Phase 4: Update Discussion Timeline + +```javascript +// 构建讨论轮次内容 +const roundContent = { + round, + type: discussType, + user_feedback: userFeedback, + updated_understanding: { + confirmed: [], // 确认的假设 + corrected: [], // 纠正的假设 + new_insights: [] // 新发现 + }, + new_findings: [], + new_questions: [], + timestamp: new Date().toISOString() +} + +Write(outputPath, JSON.stringify(roundContent, null, 2)) + +// 更新 discussion.md +const discussionMdContent = ` +### Round ${round + 1} - Discussion (${new Date().toISOString()}) + +#### Type +${discussType} + +#### User Input +${userFeedback || '(Initial discussion round)'} + +#### Updated Understanding +${roundContent.updated_understanding.confirmed.length > 0 + ? `**Confirmed**: ${roundContent.updated_understanding.confirmed.map(c => `\n- ✅ ${c}`).join('')}` : ''} +${roundContent.updated_understanding.corrected.length > 0 + ? `**Corrected**: ${roundContent.updated_understanding.corrected.map(c => `\n- 🔄 ${c}`).join('')}` : ''} +${roundContent.updated_understanding.new_insights.length > 0 + ? `**New Insights**: ${roundContent.updated_understanding.new_insights.map(i => `\n- 💡 ${i}`).join('')}` : ''} + +#### New Findings +${(roundContent.new_findings || []).map(f => `- ${f}`).join('\n') || '(None)'} + +#### Open Questions +${(roundContent.new_questions || []).map(q => `- ${q}`).join('\n') || '(None)'} +` + +const currentDiscussion = Read(`${sessionFolder}/discussion.md`) +Write(`${sessionFolder}/discussion.md`, currentDiscussion + discussionMdContent) +``` + +### Phase 5: Report to Coordinator + +```javascript +// 更新 shared memory +sharedMemory.discussions = sharedMemory.discussions || [] +sharedMemory.discussions.push({ + id: `discussion-round-${discussNum}`, + round, + type: discussType, + new_insight_count: roundContent.updated_understanding.new_insights?.length || 0, + corrected_count: roundContent.updated_understanding.corrected?.length || 0, + timestamp: new Date().toISOString() +}) + +// 更新 current_understanding +sharedMemory.current_understanding = sharedMemory.current_understanding || { established: [], clarified: [], key_insights: [] } +sharedMemory.current_understanding.established.push(...(roundContent.updated_understanding.confirmed || [])) +sharedMemory.current_understanding.clarified.push(...(roundContent.updated_understanding.corrected || [])) +sharedMemory.current_understanding.key_insights.push(...(roundContent.updated_understanding.new_insights || [])) + +Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2)) + +const resultSummary = `Round ${round}: ${roundContent.updated_understanding.new_insights?.length || 0} 新洞察, ${roundContent.updated_understanding.corrected?.length || 0} 纠正` + +mcp__ccw-tools__team_msg({ + operation: "log", + team: teamName, + from: "discussant", + to: "coordinator", + type: "discussion_processed", + summary: `[discussant] ${resultSummary}`, + ref: outputPath +}) + +SendMessage({ + type: "message", + recipient: "coordinator", + content: `## [discussant] Discussion Round ${round} Results + +**Task**: ${task.subject} +**Type**: ${discussType} + +### Summary +${resultSummary} + +### Key Updates +${roundContent.updated_understanding.new_insights?.slice(0, 3).map(i => `- 💡 ${i}`).join('\n') || '(No new insights)'} +${roundContent.updated_understanding.corrected?.slice(0, 3).map(c => `- 🔄 ${c}`).join('\n') || ''} + +### Output +${outputPath}`, + summary: `[discussant] DISCUSS complete: ${resultSummary}` +}) + +TaskUpdate({ taskId: task.id, status: 'completed' }) + +// Check for next task +const nextTasks = TaskList().filter(t => + t.subject.startsWith('DISCUSS-') && + t.owner === 'discussant' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (nextTasks.length > 0) { + // Continue with next task → back to Phase 1 +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| No DISCUSS-* tasks available | Idle, wait for coordinator assignment | +| No analysis results found | Report empty discussion, notify coordinator | +| CLI tool unavailable | Use existing analysis results for discussion | +| User feedback unclear | Process as 'deepen' type, note ambiguity | +| Session folder missing | Error to coordinator | diff --git a/.claude/skills/team-ultra-analyze/roles/explorer/commands/explore.md b/.claude/skills/team-ultra-analyze/roles/explorer/commands/explore.md new file mode 100644 index 00000000..2e487aca --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/explorer/commands/explore.md @@ -0,0 +1,194 @@ +# Command: explore + +> cli-explore-agent 并行代码库探索。根据话题和视角,通过 subagent 收集代码库上下文。 + +## When to Use + +- Phase 3 of Explorer +- 需要收集代码库上下文供后续分析 +- 每个 EXPLORE-* 任务触发一次 + +**Trigger conditions**: +- Explorer Phase 2 完成后 +- 任务包含明确的 perspective 和 dimensions + +## Strategy + +### Delegation Mode + +**Mode**: Subagent(cli-explore-agent 执行实际探索) + +### Decision Logic + +```javascript +// 根据 perspective 确定探索策略 +function buildExplorationStrategy(perspective, dimensions, topic) { + const strategies = { + 'general': { + focus: 'Overall codebase structure and patterns', + searches: [topic, ...dimensions], + depth: 'broad' + }, + 'technical': { + focus: 'Implementation details, code patterns, technical feasibility', + searches: [`${topic} implementation`, `${topic} pattern`, `${topic} handler`], + depth: 'medium' + }, + 'architectural': { + focus: 'System design, module boundaries, component interactions', + searches: [`${topic} module`, `${topic} service`, `${topic} interface`], + depth: 'broad' + }, + 'business': { + focus: 'Business logic, domain models, value flows', + searches: [`${topic} model`, `${topic} domain`, `${topic} workflow`], + depth: 'medium' + }, + 'domain_expert': { + focus: 'Domain-specific patterns, standards compliance, best practices', + searches: [`${topic} standard`, `${topic} convention`, `${topic} best practice`], + depth: 'deep' + } + } + return strategies[perspective] || strategies['general'] +} +``` + +## Execution Steps + +### Step 1: Context Preparation + +```javascript +const strategy = buildExplorationStrategy(perspective, dimensions, topic) +const exploreNum = task.subject.match(/EXPLORE-(\d+)/)?.[1] || '001' +const outputPath = `${sessionFolder}/explorations/exploration-${exploreNum}.json` +``` + +### Step 2: Execute Exploration + +```javascript +Task({ + subagent_type: "cli-explore-agent", + run_in_background: false, + description: `Explore codebase: ${topic} (${perspective})`, + prompt: ` +## Analysis Context +Topic: ${topic} +Perspective: ${perspective} — ${strategy.focus} +Dimensions: ${dimensions.join(', ')} +Session: ${sessionFolder} + +## MANDATORY FIRST STEPS +1. Run: ccw tool exec get_modules_by_depth '{}' +2. Execute searches: ${strategy.searches.map(s => `"${s}"`).join(', ')} +3. Read: .workflow/project-tech.json (if exists) + +## Exploration Focus (${perspective} angle) +- **Depth**: ${strategy.depth} +- **Focus**: ${strategy.focus} +${dimensions.map(d => `- ${d}: Identify relevant code patterns, structures, and relationships`).join('\n')} + +## Search Strategy +${strategy.searches.map((s, i) => `${i + 1}. Search for: "${s}" — find related files, functions, types`).join('\n')} + +## Additional Exploration +- Identify entry points related to the topic +- Map dependencies between relevant modules +- Note any configuration or environment dependencies +- Look for test files that reveal expected behavior + +## Output +Write findings to: ${outputPath} + +Schema: +{ + "perspective": "${perspective}", + "relevant_files": [ + {"path": "string", "relevance": "high|medium|low", "summary": "what this file does"} + ], + "patterns": ["pattern descriptions found in codebase"], + "key_findings": ["important discoveries"], + "module_map": {"module_name": ["related_files"]}, + "questions_for_analysis": ["questions that need deeper analysis"], + "_metadata": { + "agent": "cli-explore-agent", + "perspective": "${perspective}", + "search_queries": ${JSON.stringify(strategy.searches)}, + "timestamp": "ISO string" + } +} +` +}) +``` + +### Step 3: Result Processing + +```javascript +// 验证输出文件 +let result = {} +try { + result = JSON.parse(Read(outputPath)) +} catch { + // Fallback: ACE search + const aceResults = mcp__ace-tool__search_context({ + project_root_path: ".", + query: `${topic} ${perspective}` + }) + + result = { + perspective, + relevant_files: [], + patterns: [], + key_findings: [`ACE fallback: ${aceResults?.summary || 'No results'}`], + questions_for_analysis: [`What is the ${perspective} perspective on ${topic}?`], + _metadata: { + agent: 'ace-fallback', + perspective, + timestamp: new Date().toISOString() + } + } + Write(outputPath, JSON.stringify(result, null, 2)) +} + +// 质量验证 +const quality = { + has_files: (result.relevant_files?.length || 0) > 0, + has_findings: (result.key_findings?.length || 0) > 0, + has_patterns: (result.patterns?.length || 0) > 0 +} + +if (!quality.has_files && !quality.has_findings) { + // 补充搜索 + const supplementary = mcp__ace-tool__search_context({ + project_root_path: ".", + query: topic + }) + // Merge supplementary results +} +``` + +## Output Format + +```json +{ + "perspective": "technical", + "relevant_files": [ + {"path": "src/auth/handler.ts", "relevance": "high", "summary": "Authentication request handler"} + ], + "patterns": ["Repository pattern used for data access", "Middleware chain for auth"], + "key_findings": ["JWT tokens stored in HTTP-only cookies", "Rate limiting at gateway level"], + "module_map": {"auth": ["src/auth/handler.ts", "src/auth/middleware.ts"]}, + "questions_for_analysis": ["Is the token refresh mechanism secure?"], + "_metadata": {"agent": "cli-explore-agent", "perspective": "technical", "timestamp": "..."} +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| cli-explore-agent unavailable | Fall back to ACE search + Grep | +| Agent produces no output file | Create minimal result with ACE fallback | +| Agent timeout | Use partial results if available | +| Invalid JSON output | Attempt repair, fall back to raw text extraction | +| Session folder missing | Create directory, continue | diff --git a/.claude/skills/team-ultra-analyze/roles/explorer/role.md b/.claude/skills/team-ultra-analyze/roles/explorer/role.md new file mode 100644 index 00000000..bdd46fd9 --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/explorer/role.md @@ -0,0 +1,243 @@ +# Role: explorer + +代码库探索者。通过 cli-explore-agent 多角度并行探索代码库,收集结构化上下文供后续分析使用。 + +## Role Identity + +- **Name**: `explorer` +- **Task Prefix**: `EXPLORE-*` +- **Responsibility**: Orchestration(代码库探索编排) +- **Communication**: SendMessage to coordinator only +- **Output Tag**: `[explorer]` + +## Role Boundaries + +### MUST + +- 仅处理 `EXPLORE-*` 前缀的任务 +- 所有输出必须带 `[explorer]` 标识 +- 仅通过 SendMessage 与 coordinator 通信 +- 严格在代码库探索职责范围内工作 +- 将探索结果写入 shared-memory.json 的 `explorations` 字段 + +### MUST NOT + +- ❌ 执行深度分析(属于 analyst) +- ❌ 处理用户反馈(属于 discussant) +- ❌ 生成结论或建议(属于 synthesizer) +- ❌ 为其他角色创建任务 +- ❌ 直接与其他 worker 通信 + +## Message Types + +| Type | Direction | Trigger | Description | +|------|-----------|---------|-------------| +| `exploration_ready` | explorer → coordinator | 探索完成 | 包含发现的文件、模式、关键发现 | +| `error` | explorer → coordinator | 探索失败 | 阻塞性错误 | + +## Toolbox + +### Available Commands + +| Command | File | Phase | Description | +|---------|------|-------|-------------| +| `explore` | [commands/explore.md](commands/explore.md) | Phase 3 | cli-explore-agent 并行探索 | + +### Subagent Capabilities + +| Agent Type | Used By | Purpose | +|------------|---------|---------| +| `cli-explore-agent` | explore.md | 多角度代码库探索 | + +### CLI Capabilities + +> Explorer 不直接使用 CLI 分析工具(通过 cli-explore-agent 间接使用) + +## Execution (5-Phase) + +### Phase 1: Task Discovery + +```javascript +const tasks = TaskList() +const myTasks = tasks.filter(t => + t.subject.startsWith('EXPLORE-') && + t.owner === 'explorer' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (myTasks.length === 0) return // idle + +const task = TaskGet({ taskId: myTasks[0].id }) +TaskUpdate({ taskId: task.id, status: 'in_progress' }) +``` + +### Phase 2: Context & Scope Assessment + +```javascript +// 从任务描述中提取上下文 +const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() +const topic = task.description.match(/topic:\s*(.+)/)?.[1]?.trim() +const perspective = task.description.match(/perspective:\s*(.+)/)?.[1]?.trim() || 'general' +const dimensions = (task.description.match(/dimensions:\s*(.+)/)?.[1]?.trim() || 'general').split(', ') + +// 读取 shared memory +let sharedMemory = {} +try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {} + +// 评估探索范围 +const exploreNum = task.subject.match(/EXPLORE-(\d+)/)?.[1] || '001' +``` + +### Phase 3: Codebase Exploration + +```javascript +// Read commands/explore.md for full cli-explore-agent implementation +Read("commands/explore.md") +``` + +**核心策略**: 通过 cli-explore-agent 执行代码库探索 + +```javascript +Task({ + subagent_type: "cli-explore-agent", + run_in_background: false, + description: `Explore codebase: ${topic} (${perspective})`, + prompt: ` +## Analysis Context +Topic: ${topic} +Perspective: ${perspective} +Dimensions: ${dimensions.join(', ')} +Session: ${sessionFolder} + +## MANDATORY FIRST STEPS +1. Run: ccw tool exec get_modules_by_depth '{}' +2. Execute relevant searches based on topic keywords +3. Read: .workflow/project-tech.json (if exists) + +## Exploration Focus (${perspective} angle) +${dimensions.map(d => `- ${d}: Identify relevant code patterns and structures`).join('\n')} + +## Output +Write findings to: ${sessionFolder}/explorations/exploration-${exploreNum}.json + +Schema: { + perspective: "${perspective}", + relevant_files: [{path, relevance, summary}], + patterns: [string], + key_findings: [string], + questions_for_analysis: [string], + _metadata: {agent: "cli-explore-agent", timestamp} +} +` +}) +``` + +### Phase 4: Result Validation + +```javascript +// 验证探索结果 +const outputPath = `${sessionFolder}/explorations/exploration-${exploreNum}.json` +let explorationResult = {} +try { + explorationResult = JSON.parse(Read(outputPath)) +} catch { + // Agent 未写入文件,使用空结果 + explorationResult = { + perspective, + relevant_files: [], + patterns: [], + key_findings: ['Exploration produced no structured output'], + questions_for_analysis: [], + _metadata: { agent: 'cli-explore-agent', timestamp: new Date().toISOString(), status: 'partial' } + } + Write(outputPath, JSON.stringify(explorationResult, null, 2)) +} + +// 基本质量检查 +const hasFiles = explorationResult.relevant_files?.length > 0 +const hasFindings = explorationResult.key_findings?.length > 0 + +if (!hasFiles && !hasFindings) { + // 探索结果为空,尝试 ACE 搜索兜底 + const aceResults = mcp__ace-tool__search_context({ + project_root_path: ".", + query: topic + }) + // 补充到结果中 +} +``` + +### Phase 5: Report to Coordinator + +```javascript +// 更新 shared memory +sharedMemory.explorations = sharedMemory.explorations || [] +sharedMemory.explorations.push({ + id: `exploration-${exploreNum}`, + perspective, + file_count: explorationResult.relevant_files?.length || 0, + finding_count: explorationResult.key_findings?.length || 0, + timestamp: new Date().toISOString() +}) +Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2)) + +const resultSummary = `${perspective} 视角: ${explorationResult.relevant_files?.length || 0} 个相关文件, ${explorationResult.key_findings?.length || 0} 个发现` + +mcp__ccw-tools__team_msg({ + operation: "log", + team: teamName, + from: "explorer", + to: "coordinator", + type: "exploration_ready", + summary: `[explorer] ${resultSummary}`, + ref: outputPath +}) + +SendMessage({ + type: "message", + recipient: "coordinator", + content: `## [explorer] Exploration Results + +**Task**: ${task.subject} +**Perspective**: ${perspective} +**Status**: ${hasFiles || hasFindings ? 'Findings Available' : 'Limited Results'} + +### Summary +${resultSummary} + +### Top Findings +${(explorationResult.key_findings || []).slice(0, 5).map(f => `- ${f}`).join('\n')} + +### Questions for Analysis +${(explorationResult.questions_for_analysis || []).slice(0, 3).map(q => `- ${q}`).join('\n')} + +### Output +${outputPath}`, + summary: `[explorer] EXPLORE complete: ${resultSummary}` +}) + +TaskUpdate({ taskId: task.id, status: 'completed' }) + +// Check for next task +const nextTasks = TaskList().filter(t => + t.subject.startsWith('EXPLORE-') && + t.owner === 'explorer' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (nextTasks.length > 0) { + // Continue with next task → back to Phase 1 +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| No EXPLORE-* tasks available | Idle, wait for coordinator assignment | +| cli-explore-agent fails | Fall back to ACE search + Grep inline | +| Exploration scope too broad | Narrow to topic keywords, report partial | +| Agent timeout | Use partial results, note incomplete | +| Session folder missing | Create it, warn coordinator | diff --git a/.claude/skills/team-ultra-analyze/roles/synthesizer/commands/synthesize.md b/.claude/skills/team-ultra-analyze/roles/synthesizer/commands/synthesize.md new file mode 100644 index 00000000..926de385 --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/synthesizer/commands/synthesize.md @@ -0,0 +1,255 @@ +# Command: synthesize + +> 跨视角整合。从所有探索、分析、讨论结果中提取主题、解决冲突、生成最终结论和建议。 + +## When to Use + +- Phase 3 of Synthesizer +- 所有探索、分析、讨论已完成 +- 每个 SYNTH-* 任务触发一次 + +**Trigger conditions**: +- 讨论循环结束后(用户选择"分析完成"或达到最大轮次) +- Quick 模式下分析完成后直接触发 + +## Strategy + +### Delegation Mode + +**Mode**: Inline(纯整合,不调用外部工具) + +### Decision Logic + +```javascript +function buildSynthesisStrategy(explorationCount, analysisCount, discussionCount) { + if (analysisCount <= 1 && discussionCount === 0) { + return 'simple' // Quick mode: 单视角直接总结 + } + if (discussionCount > 2) { + return 'deep' // Deep mode: 多轮讨论需要追踪演进 + } + return 'standard' // Standard: 多视角交叉整合 +} +``` + +## Execution Steps + +### Step 1: Context Preparation + +```javascript +const strategy = buildSynthesisStrategy( + allExplorations.length, allAnalyses.length, allDiscussions.length +) + +// 提取所有洞察 +const allInsights = allAnalyses.flatMap(a => + (a.key_insights || []).map(i => ({ + ...(typeof i === 'string' ? { insight: i } : i), + perspective: a.perspective + })) +) + +// 提取所有发现 +const allFindings = allAnalyses.flatMap(a => + (a.key_findings || []).map(f => ({ + ...(typeof f === 'string' ? { finding: f } : f), + perspective: a.perspective + })) +) + +// 提取所有建议 +const allRecommendations = allAnalyses.flatMap(a => + (a.recommendations || []).map(r => ({ + ...(typeof r === 'string' ? { action: r } : r), + perspective: a.perspective + })) +) + +// 提取讨论演进 +const discussionEvolution = allDiscussions.map(d => ({ + round: d.round, + type: d.type, + confirmed: d.updated_understanding?.confirmed || [], + corrected: d.updated_understanding?.corrected || [], + new_insights: d.updated_understanding?.new_insights || [] +})) +``` + +### Step 2: Cross-Perspective Synthesis + +```javascript +// 1. Theme Extraction — 跨视角共同主题 +const themes = extractThemes(allInsights) + +// 2. Conflict Resolution — 视角间矛盾 +const conflicts = identifyConflicts(allAnalyses) + +// 3. Evidence Consolidation — 证据汇总 +const consolidatedEvidence = consolidateEvidence(allFindings) + +// 4. Recommendation Prioritization — 建议优先级排序 +const prioritizedRecommendations = prioritizeRecommendations(allRecommendations) + +// 5. Decision Trail Integration — 决策追踪整合 +const decisionSummary = summarizeDecisions(decisionTrail) + +function extractThemes(insights) { + // 按关键词聚类,识别跨视角共同主题 + const themeMap = {} + for (const insight of insights) { + const text = insight.insight || insight + // 简单聚类:相似洞察归为同一主题 + const key = text.slice(0, 30) + if (!themeMap[key]) { + themeMap[key] = { theme: text, perspectives: [], count: 0 } + } + themeMap[key].perspectives.push(insight.perspective) + themeMap[key].count++ + } + return Object.values(themeMap) + .sort((a, b) => b.count - a.count) + .slice(0, 10) +} + +function identifyConflicts(analyses) { + // 识别不同视角间的矛盾发现 + const conflicts = [] + for (let i = 0; i < analyses.length; i++) { + for (let j = i + 1; j < analyses.length; j++) { + // 比较两个视角的发现是否矛盾 + // 实际实现中需要语义比较 + } + } + return conflicts +} + +function consolidateEvidence(findings) { + // 去重并按文件引用聚合 + const byFile = {} + for (const f of findings) { + const ref = f.file_ref || f.finding + if (!byFile[ref]) byFile[ref] = [] + byFile[ref].push(f) + } + return byFile +} + +function prioritizeRecommendations(recommendations) { + const priorityOrder = { high: 0, medium: 1, low: 2 } + return recommendations + .sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2)) + .slice(0, 10) +} + +function summarizeDecisions(trail) { + return trail.map(d => ({ + round: d.round, + decision: d.decision, + context: d.context, + impact: d.impact || 'Shaped analysis direction' + })) +} +``` + +### Step 3: Build Conclusions + +```javascript +const conclusions = { + session_id: sessionFolder.split('/').pop(), + topic, + completed: new Date().toISOString(), + total_rounds: allDiscussions.length, + strategy_used: strategy, + + summary: generateSummary(themes, allFindings, allDiscussions), + + key_conclusions: themes.slice(0, 7).map(t => ({ + point: t.theme, + evidence: t.perspectives.join(', ') + ' perspectives', + confidence: t.count >= 3 ? 'high' : t.count >= 2 ? 'medium' : 'low' + })), + + recommendations: prioritizedRecommendations.map(r => ({ + action: r.action, + rationale: r.rationale || 'Based on analysis findings', + priority: r.priority || 'medium', + source_perspective: r.perspective + })), + + open_questions: allAnalyses + .flatMap(a => a.open_questions || []) + .filter((q, i, arr) => arr.indexOf(q) === i) + .slice(0, 5), + + follow_up_suggestions: generateFollowUps(conclusions), + + decision_trail: decisionSummary, + + cross_perspective_synthesis: { + convergent_themes: themes.filter(t => t.perspectives.length > 1), + conflicts_resolved: conflicts, + unique_contributions: allAnalyses.map(a => ({ + perspective: a.perspective, + unique_insights: (a.key_insights || []).slice(0, 2) + })) + }, + + _metadata: { + explorations: allExplorations.length, + analyses: allAnalyses.length, + discussions: allDiscussions.length, + decisions: decisionTrail.length, + synthesis_strategy: strategy + } +} + +function generateSummary(themes, findings, discussions) { + const topThemes = themes.slice(0, 3).map(t => t.theme).join('; ') + const roundCount = discussions.length + return `Analysis of "${topic}" identified ${themes.length} key themes across ${allAnalyses.length} perspective(s) and ${roundCount} discussion round(s). Top themes: ${topThemes}` +} + +function generateFollowUps(conclusions) { + const suggestions = [] + if ((conclusions.open_questions || []).length > 2) { + suggestions.push({ type: 'deeper-analysis', summary: 'Further analysis needed for open questions' }) + } + if ((conclusions.recommendations || []).some(r => r.priority === 'high')) { + suggestions.push({ type: 'issue-creation', summary: 'Create issues for high-priority recommendations' }) + } + suggestions.push({ type: 'implementation-plan', summary: 'Generate implementation plan from recommendations' }) + return suggestions +} +``` + +## Output Format + +```json +{ + "session_id": "UAN-auth-analysis-2026-02-18", + "topic": "认证架构优化", + "completed": "2026-02-18T...", + "total_rounds": 2, + "summary": "Analysis identified 5 key themes...", + "key_conclusions": [ + {"point": "JWT stateless approach is sound", "evidence": "technical, architectural", "confidence": "high"} + ], + "recommendations": [ + {"action": "Add rate limiting", "rationale": "Prevent brute force", "priority": "high"} + ], + "open_questions": ["Token rotation strategy?"], + "decision_trail": [ + {"round": 1, "decision": "Focus on security", "context": "User preference"} + ] +} +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| No analyses available | Synthesize from explorations only | +| Single perspective only | Generate focused synthesis without cross-perspective | +| Irreconcilable conflicts | Present both sides with trade-off analysis | +| Empty discussion rounds | Skip discussion evolution, focus on analysis results | +| Shared memory corrupted | Rebuild from individual JSON files | diff --git a/.claude/skills/team-ultra-analyze/roles/synthesizer/role.md b/.claude/skills/team-ultra-analyze/roles/synthesizer/role.md new file mode 100644 index 00000000..1d2e2add --- /dev/null +++ b/.claude/skills/team-ultra-analyze/roles/synthesizer/role.md @@ -0,0 +1,225 @@ +# Role: synthesizer + +综合整合者。跨视角整合所有探索、分析、讨论结果,生成最终结论、建议和决策追踪。 + +## Role Identity + +- **Name**: `synthesizer` +- **Task Prefix**: `SYNTH-*` +- **Responsibility**: Read-only analysis(综合结论) +- **Communication**: SendMessage to coordinator only +- **Output Tag**: `[synthesizer]` + +## Role Boundaries + +### MUST + +- 仅处理 `SYNTH-*` 前缀的任务 +- 所有输出必须带 `[synthesizer]` 标识 +- 仅通过 SendMessage 与 coordinator 通信 +- 整合所有角色的产出生成最终结论 +- 将综合结果写入 shared-memory.json 的 `synthesis` 字段 +- 更新 discussion.md 的结论部分 + +### MUST NOT + +- ❌ 执行新的代码探索或 CLI 分析 +- ❌ 与用户直接交互 +- ❌ 为其他角色创建任务 +- ❌ 直接与其他 worker 通信 +- ❌ 修改源代码 + +## Message Types + +| Type | Direction | Trigger | Description | +|------|-----------|---------|-------------| +| `synthesis_ready` | synthesizer → coordinator | 综合完成 | 包含最终结论和建议 | +| `error` | synthesizer → coordinator | 综合失败 | 阻塞性错误 | + +## Toolbox + +### Available Commands + +| Command | File | Phase | Description | +|---------|------|-------|-------------| +| `synthesize` | [commands/synthesize.md](commands/synthesize.md) | Phase 3 | 跨视角整合 | + +### Subagent Capabilities + +> Synthesizer 不使用 subagent + +### CLI Capabilities + +> Synthesizer 不使用 CLI 工具(纯整合角色) + +## Execution (5-Phase) + +### Phase 1: Task Discovery + +```javascript +const tasks = TaskList() +const myTasks = tasks.filter(t => + t.subject.startsWith('SYNTH-') && + t.owner === 'synthesizer' && + t.status === 'pending' && + t.blockedBy.length === 0 +) + +if (myTasks.length === 0) return +const task = TaskGet({ taskId: myTasks[0].id }) +TaskUpdate({ taskId: task.id, status: 'in_progress' }) +``` + +### Phase 2: Context Loading + Shared Memory Read + +```javascript +const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1]?.trim() +const topic = task.description.match(/topic:\s*(.+)/)?.[1]?.trim() + +const memoryPath = `${sessionFolder}/shared-memory.json` +let sharedMemory = {} +try { sharedMemory = JSON.parse(Read(memoryPath)) } catch {} + +// Read all explorations +const explorationFiles = Glob({ pattern: `${sessionFolder}/explorations/*.json` }) +const allExplorations = explorationFiles.map(f => { + try { return JSON.parse(Read(f)) } catch { return null } +}).filter(Boolean) + +// Read all analyses +const analysisFiles = Glob({ pattern: `${sessionFolder}/analyses/*.json` }) +const allAnalyses = analysisFiles.map(f => { + try { return JSON.parse(Read(f)) } catch { return null } +}).filter(Boolean) + +// Read all discussion rounds +const discussionFiles = Glob({ pattern: `${sessionFolder}/discussions/discussion-round-*.json` }) +const allDiscussions = discussionFiles.map(f => { + try { return JSON.parse(Read(f)) } catch { return null } +}).filter(Boolean) + +// Read decision trail +const decisionTrail = sharedMemory.decision_trail || [] +const currentUnderstanding = sharedMemory.current_understanding || {} +``` + +### Phase 3: Synthesis Execution + +```javascript +// Read commands/synthesize.md for full implementation +Read("commands/synthesize.md") +``` + +### Phase 4: Write Conclusions + Update discussion.md + +```javascript +const synthNum = task.subject.match(/SYNTH-(\d+)/)?.[1] || '001' +const conclusionsPath = `${sessionFolder}/conclusions.json` + +// 写入 conclusions.json +Write(conclusionsPath, JSON.stringify(conclusions, null, 2)) + +// 更新 discussion.md — 结论部分 +const conclusionsMd = ` +## Conclusions + +### Summary +${conclusions.summary} + +### Key Conclusions +${conclusions.key_conclusions.map((c, i) => `${i + 1}. **${c.point}** (Confidence: ${c.confidence}) + - Evidence: ${c.evidence}`).join('\n')} + +### Recommendations +${conclusions.recommendations.map((r, i) => `${i + 1}. **[${r.priority}]** ${r.action} + - Rationale: ${r.rationale}`).join('\n')} + +### Remaining Questions +${(conclusions.open_questions || []).map(q => `- ${q}`).join('\n') || '(None)'} + +## Decision Trail + +### Critical Decisions +${decisionTrail.map(d => `- **Round ${d.round}**: ${d.decision} — ${d.context}`).join('\n') || '(None)'} + +## Current Understanding (Final) + +### What We Established +${(currentUnderstanding.established || []).map(e => `- ${e}`).join('\n') || '(None)'} + +### What Was Clarified/Corrected +${(currentUnderstanding.clarified || []).map(c => `- ${c}`).join('\n') || '(None)'} + +### Key Insights +${(currentUnderstanding.key_insights || []).map(i => `- ${i}`).join('\n') || '(None)'} + +## Session Statistics +- **Explorations**: ${allExplorations.length} +- **Analyses**: ${allAnalyses.length} +- **Discussion Rounds**: ${allDiscussions.length} +- **Decisions Made**: ${decisionTrail.length} +- **Completed**: ${new Date().toISOString()} +` + +const currentDiscussion = Read(`${sessionFolder}/discussion.md`) +Write(`${sessionFolder}/discussion.md`, currentDiscussion + conclusionsMd) +``` + +### Phase 5: Report to Coordinator + Shared Memory Write + +```javascript +sharedMemory.synthesis = { + conclusion_count: conclusions.key_conclusions?.length || 0, + recommendation_count: conclusions.recommendations?.length || 0, + open_question_count: conclusions.open_questions?.length || 0, + timestamp: new Date().toISOString() +} +Write(memoryPath, JSON.stringify(sharedMemory, null, 2)) + +const resultSummary = `${conclusions.key_conclusions?.length || 0} 结论, ${conclusions.recommendations?.length || 0} 建议` + +mcp__ccw-tools__team_msg({ + operation: "log", + team: teamName, + from: "synthesizer", + to: "coordinator", + type: "synthesis_ready", + summary: `[synthesizer] Synthesis complete: ${resultSummary}`, + ref: conclusionsPath +}) + +SendMessage({ + type: "message", + recipient: "coordinator", + content: `## [synthesizer] Synthesis Results + +**Task**: ${task.subject} +**Topic**: ${topic} + +### Summary +${conclusions.summary} + +### Top Conclusions +${(conclusions.key_conclusions || []).slice(0, 3).map((c, i) => `${i + 1}. **${c.point}** (${c.confidence})`).join('\n')} + +### Top Recommendations +${(conclusions.recommendations || []).slice(0, 3).map((r, i) => `${i + 1}. [${r.priority}] ${r.action}`).join('\n')} + +### Artifacts +- 📄 Discussion: ${sessionFolder}/discussion.md +- 📊 Conclusions: ${conclusionsPath}`, + summary: `[synthesizer] SYNTH complete: ${resultSummary}` +}) + +TaskUpdate({ taskId: task.id, status: 'completed' }) +``` + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| No SYNTH-* tasks | Idle, wait for assignment | +| No analyses/discussions found | Synthesize from explorations only | +| Conflicting analyses | Present both sides, recommend user decision | +| Empty shared memory | Generate minimal conclusions from discussion.md | +| Only one perspective | Create focused single-perspective synthesis | diff --git a/.claude/skills/team-ultra-analyze/specs/team-config.json b/.claude/skills/team-ultra-analyze/specs/team-config.json new file mode 100644 index 00000000..5fb7f468 --- /dev/null +++ b/.claude/skills/team-ultra-analyze/specs/team-config.json @@ -0,0 +1,131 @@ +{ + "team_name": "ultra-analyze", + "version": "1.0.0", + "description": "深度分析团队 - 将单体分析工作流拆分为5角色协作:探索→分析→讨论→综合,支持多管道模式和讨论循环", + "skill_entry": "team-ultra-analyze", + "invocation": "Skill(skill=\"team-ultra-analyze\", args=\"--role=coordinator ...\")", + + "roles": { + "coordinator": { + "name": "coordinator", + "responsibility": "Orchestration", + "task_prefix": null, + "description": "分析团队协调者。话题澄清、管道选择、会话管理、讨论循环驱动、结果汇报", + "message_types_sent": ["pipeline_selected", "discussion_round", "direction_adjusted", "task_unblocked", "error", "shutdown"], + "message_types_received": ["exploration_ready", "analysis_ready", "discussion_processed", "synthesis_ready", "error"], + "commands": ["dispatch", "monitor"] + }, + "explorer": { + "name": "explorer", + "responsibility": "Orchestration (代码库探索编排)", + "task_prefix": "EXPLORE-*", + "description": "代码库探索者。通过 cli-explore-agent 多角度并行探索代码库,收集上下文", + "message_types_sent": ["exploration_ready", "error"], + "message_types_received": [], + "commands": ["explore"], + "subagents": ["cli-explore-agent"] + }, + "analyst": { + "name": "analyst", + "responsibility": "Read-only analysis (深度分析)", + "task_prefix": "ANALYZE-*", + "description": "深度分析师。基于探索结果,通过 CLI 多视角深度分析,生成结构化洞察", + "message_types_sent": ["analysis_ready", "error"], + "message_types_received": [], + "commands": ["analyze"], + "cli_tools": ["gemini", "codex", "claude"] + }, + "discussant": { + "name": "discussant", + "responsibility": "Analysis + Exploration (讨论处理)", + "task_prefix": "DISCUSS-*", + "description": "讨论处理者。根据用户反馈调整分析方向,执行深入探索或补充分析", + "message_types_sent": ["discussion_processed", "error"], + "message_types_received": [], + "commands": ["deepen"], + "cli_tools": ["gemini"], + "subagents": ["cli-explore-agent"] + }, + "synthesizer": { + "name": "synthesizer", + "responsibility": "Read-only analysis (综合结论)", + "task_prefix": "SYNTH-*", + "description": "综合整合者。跨视角整合所有探索、分析、讨论结果,生成最终结论和建议", + "message_types_sent": ["synthesis_ready", "error"], + "message_types_received": [], + "commands": ["synthesize"] + } + }, + + "pipeline_modes": { + "quick": { + "description": "快速分析:单探索→单分析→直接综合", + "stages": ["EXPLORE", "ANALYZE", "SYNTH"], + "entry_role": "explorer", + "estimated_time": "10-15min" + }, + "standard": { + "description": "标准分析:多角度并行探索→多视角分析→讨论→综合", + "stages": ["EXPLORE-multi", "ANALYZE-multi", "DISCUSS", "SYNTH"], + "entry_role": "explorer", + "parallel_stages": [["EXPLORE-001", "EXPLORE-002"], ["ANALYZE-001", "ANALYZE-002"]], + "estimated_time": "30-60min" + }, + "deep": { + "description": "深度分析:多探索→多分析→讨论循环→综合", + "stages": ["EXPLORE-multi", "ANALYZE-multi", "DISCUSS-loop", "SYNTH"], + "entry_role": "explorer", + "parallel_stages": [["EXPLORE-001", "EXPLORE-002", "EXPLORE-003"], ["ANALYZE-001", "ANALYZE-002", "ANALYZE-003"]], + "discussion_loop": { "max_rounds": 5, "participants": ["discussant", "analyst"] }, + "estimated_time": "1-2hr" + } + }, + + "discussion_loop": { + "max_rounds": 5, + "trigger": "user feedback via coordinator", + "participants": ["discussant", "analyst"], + "flow": "coordinator(AskUser) → DISCUSS-N(deepen) → [optional ANALYZE-fix] → coordinator(AskUser) → ... → SYNTH" + }, + + "shared_memory": { + "file": "shared-memory.json", + "fields": { + "explorations": { "owner": "explorer", "type": "array" }, + "analyses": { "owner": "analyst", "type": "array" }, + "discussions": { "owner": "discussant", "type": "array" }, + "synthesis": { "owner": "synthesizer", "type": "object" }, + "decision_trail": { "owner": "coordinator", "type": "array" }, + "current_understanding": { "owner": "coordinator", "type": "object" } + } + }, + + "collaboration_patterns": [ + "CP-1: Linear Pipeline (Quick mode)", + "CP-3: Fan-out (Explorer/Analyst parallel exploration)", + "CP-2: Review-Fix Cycle (Discussion loop: Discussant ↔ Analyst)", + "CP-8: User-in-the-loop (Coordinator ↔ User discussion rounds)" + ], + + "session_directory": { + "pattern": ".workflow/.team/UAN-{slug}-{date}", + "subdirectories": ["explorations", "analyses", "discussions"] + }, + + "analysis_dimensions": { + "architecture": ["架构", "architecture", "design", "structure", "设计"], + "implementation": ["实现", "implement", "code", "coding", "代码"], + "performance": ["性能", "performance", "optimize", "bottleneck", "优化"], + "security": ["安全", "security", "auth", "permission", "权限"], + "concept": ["概念", "concept", "theory", "principle", "原理"], + "comparison": ["比较", "compare", "vs", "difference", "区别"], + "decision": ["决策", "decision", "choice", "tradeoff", "选择"] + }, + + "analysis_perspectives": { + "technical": { "tool": "gemini", "focus": "Implementation, code patterns, technical feasibility" }, + "architectural": { "tool": "claude", "focus": "System design, scalability, component interactions" }, + "business": { "tool": "codex", "focus": "Value, ROI, stakeholder impact, strategy" }, + "domain_expert": { "tool": "gemini", "focus": "Domain-specific patterns, best practices, standards" } + } +} diff --git a/ccw/tests/codex-lens-bootstrap-fallback.test.js b/ccw/tests/codex-lens-bootstrap-fallback.test.js new file mode 100644 index 00000000..cd9c1593 --- /dev/null +++ b/ccw/tests/codex-lens-bootstrap-fallback.test.js @@ -0,0 +1,93 @@ +/** + * Regression test: CodexLens bootstrap falls back to pip when UV bootstrap fails. + * + * We simulate a "broken UV" by pointing CCW_UV_PATH to the current Node executable. + * `node --version` exits 0 so isUvAvailable() returns true, but `node venv ...` fails, + * forcing the bootstrap code to try the pip path. + * + * This test runs bootstrapVenv in a child process to avoid mutating process-wide + * environment variables that could affect other tests. + */ + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// repo root: /ccw/tests -> +const REPO_ROOT = join(__dirname, '..', '..'); + +function runNodeEvalModule(script, env) { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, ['--input-type=module', '-e', script], { + cwd: REPO_ROOT, + env, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (d) => { stdout += d.toString(); }); + child.stderr.on('data', (d) => { stderr += d.toString(); }); + + child.on('error', (err) => reject(err)); + child.on('close', (code) => resolve({ code, stdout, stderr })); + }); +} + +describe('CodexLens bootstrap fallback', () => { + it('falls back to pip when UV bootstrap fails', { timeout: 10 * 60 * 1000 }, async () => { + const dataDir = mkdtempSync(join(tmpdir(), 'codexlens-bootstrap-fallback-')); + + try { + const script = ` +import { bootstrapVenv } from './ccw/dist/tools/codex-lens.js'; + +(async () => { + const result = await bootstrapVenv(); + console.log('@@RESULT@@' + JSON.stringify(result)); +})().catch((e) => { + console.error(e?.stack || String(e)); + process.exit(1); +}); +`; + + const env = { + ...process.env, + // Isolate test venv + dependencies from user/global CodexLens state. + CODEXLENS_DATA_DIR: dataDir, + // Make isUvAvailable() return true, but createVenv() fail. + CCW_UV_PATH: process.execPath, + }; + + const { code, stdout, stderr } = await runNodeEvalModule(script, env); + assert.equal(code, 0, `bootstrapVenv child process failed:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`); + + const marker = '@@RESULT@@'; + const idx = stdout.lastIndexOf(marker); + assert.ok(idx !== -1, `Missing result marker in stdout:\n${stdout}`); + + const jsonText = stdout.slice(idx + marker.length).trim(); + const parsed = JSON.parse(jsonText); + + assert.equal(parsed?.success, true, `Expected success=true, got:\n${jsonText}`); + assert.ok(Array.isArray(parsed.warnings), 'Expected warnings array on pip fallback result'); + assert.ok(parsed.warnings.some((w) => String(w).includes('UV bootstrap failed')), `Expected UV failure warning, got: ${JSON.stringify(parsed.warnings)}`); + } finally { + try { + rmSync(dataDir, { recursive: true, force: true }); + } catch { + // Best effort cleanup; leave artifacts only if Windows locks prevent removal. + } + } + }); +}); +