diff --git a/.claude/skills/team-lifecycle-v3/SKILL.md b/.claude/skills/team-lifecycle-v3/SKILL.md index ab1f065f..3eb19224 100644 --- a/.claude/skills/team-lifecycle-v3/SKILL.md +++ b/.claude/skills/team-lifecycle-v3/SKILL.md @@ -43,20 +43,22 @@ Parse `$ARGUMENTS` to extract `--role`. If absent → Orchestration Mode (auto r ### Role Registry -| Role | File | Task Prefix | Type | -|------|------|-------------|------| -| coordinator | roles/coordinator/role.md | (none) | orchestrator | -| analyst | roles/analyst/role.md | RESEARCH-* | pipeline | -| writer | roles/writer/role.md | DRAFT-* | pipeline | -| discussant | roles/discussant/role.md | DISCUSS-* | pipeline | -| planner | roles/planner/role.md | PLAN-* | pipeline | -| executor | roles/executor/role.md | IMPL-* | pipeline | -| tester | roles/tester/role.md | TEST-* | pipeline | -| reviewer | roles/reviewer/role.md | REVIEW-* + QUALITY-* | pipeline | -| explorer | roles/explorer/role.md | EXPLORE-* | service (on-demand) | -| architect | roles/architect/role.md | ARCH-* | consulting (on-demand) | -| fe-developer | roles/fe-developer/role.md | DEV-FE-* | frontend pipeline | -| fe-qa | roles/fe-qa/role.md | QA-FE-* | frontend pipeline | +| Role | File | Task Prefix | Type | Compact | +|------|------|-------------|------|---------| +| coordinator | [roles/coordinator/role.md](roles/coordinator/role.md) | (none) | orchestrator | **⚠️ 压缩后必须重读** | +| analyst | [roles/analyst/role.md](roles/analyst/role.md) | RESEARCH-* | pipeline | 压缩后必须重读 | +| writer | [roles/writer/role.md](roles/writer/role.md) | DRAFT-* | pipeline | 压缩后必须重读 | +| discussant | [roles/discussant/role.md](roles/discussant/role.md) | DISCUSS-* | pipeline | 压缩后必须重读 | +| planner | [roles/planner/role.md](roles/planner/role.md) | PLAN-* | pipeline | 压缩后必须重读 | +| executor | [roles/executor/role.md](roles/executor/role.md) | IMPL-* | pipeline | 压缩后必须重读 | +| tester | [roles/tester/role.md](roles/tester/role.md) | TEST-* | pipeline | 压缩后必须重读 | +| reviewer | [roles/reviewer/role.md](roles/reviewer/role.md) | REVIEW-* + QUALITY-* | pipeline | 压缩后必须重读 | +| explorer | [roles/explorer/role.md](roles/explorer/role.md) | EXPLORE-* | service (on-demand) | 压缩后必须重读 | +| architect | [roles/architect/role.md](roles/architect/role.md) | ARCH-* | consulting (on-demand) | 压缩后必须重读 | +| fe-developer | [roles/fe-developer/role.md](roles/fe-developer/role.md) | DEV-FE-* | frontend pipeline | 压缩后必须重读 | +| fe-qa | [roles/fe-qa/role.md](roles/fe-qa/role.md) | QA-FE-* | frontend pipeline | 压缩后必须重读 | + +> **⚠️ COMPACT PROTECTION**: 角色文件是执行文档,不是参考资料。当 context compression 发生后,角色指令仅剩摘要时,**必须立即 `Read` 对应 role.md 重新加载后再继续执行**。不得基于摘要执行任何 Phase。 ### Dispatch @@ -179,6 +181,86 @@ Fullstack: PLAN-001 → IMPL-001 ∥ DEV-FE-001 → TEST-001 ∥ QA-FE-001 Full + FE: [Spec pipeline] → PLAN-001 → IMPL-001 ∥ DEV-FE-001 → TEST-001 ∥ QA-FE-001 → REVIEW-001 ``` +### Cadence Control + +**节拍模型**: 事件驱动,每个 beat = coordinator 唤醒 → 处理 → spawn → STOP。 + +``` +Beat Cycle (单次节拍) +═══════════════════════════════════════════════════════════ + Event Coordinator Workers +─────────────────────────────────────────────────────────── + callback/resume ──→ ┌─ handleCallback ─┐ + │ mark completed │ + │ check pipeline │ + ├─ handleSpawnNext ─┤ + │ find ready tasks │ + │ spawn workers ───┼──→ [Worker A] Phase 1-5 + │ (parallel OK) ──┼──→ [Worker B] Phase 1-5 + └─ STOP (idle) ─────┘ │ + │ + callback ←─────────────────────────────────────────┘ + (next beat) SendMessage + TaskUpdate(completed) +═══════════════════════════════════════════════════════════ +``` + +**Pipeline 节拍视图**: + +``` +Spec-only (12 beats, 严格串行) +────────────────────────────────────────────────────────── +Beat 1 2 3 4 5 6 7 8 9 10 11 12 + │ │ │ │ │ │ │ │ │ │ │ │ + R1 → D1 → W1 → D2 → W2 → D3 → W3 → D4 → W4 → D5 → Q1 → D6 + ▲ ▲ + pipeline sign-off + start pause + +R=RESEARCH D=DISCUSS W=DRAFT(writer) Q=QUALITY + +Impl-only (3 beats, 含并行窗口) +────────────────────────────────────────────────────────── +Beat 1 2 3 + │ │ ┌────┴────┐ + PLAN → IMPL ──→ TEST ∥ REVIEW ← 并行窗口 + └────┬────┘ + pipeline + done + +Full-lifecycle (15 beats, spec→impl 过渡含检查点) +────────────────────────────────────────────────────────── +Beat 1-12: [Spec pipeline 同上] + │ +Beat 12 (D6 完成): ⏸ CHECKPOINT ── 用户确认后 resume + │ +Beat 13 14 15 + PLAN → IMPL → TEST ∥ REVIEW + +Fullstack (含双并行窗口) +────────────────────────────────────────────────────────── +Beat 1 2 3 4 + │ ┌────┴────┐ ┌────┴────┐ │ + PLAN → IMPL ∥ DEV-FE → TEST ∥ QA-FE → REVIEW + ▲ ▲ ▲ + 并行窗口 1 并行窗口 2 同步屏障 +``` + +**检查点 (Checkpoint)**: + +| 触发条件 | 位置 | 行为 | +|----------|------|------| +| Spec→Impl 过渡 | DISCUSS-006 完成后 | ⏸ 暂停,等待用户 `resume` 确认 | +| GC 循环上限 | QA-FE max 2 rounds | 超出轮次 → 停止迭代,报告当前状态 | +| Pipeline 停滞 | 无 ready + 无 running | 检查缺失任务,报告用户 | + +**Stall 检测** (coordinator `handleCheck` 时执行): + +| 检查项 | 条件 | 处理 | +|--------|------|------| +| Worker 无响应 | in_progress 任务无回调 | 报告等待中的任务列表,建议用户 `resume` | +| Pipeline 死锁 | 无 ready + 无 running + 有 pending | 检查 blockedBy 依赖链,报告卡点 | +| GC 循环超限 | DEV-FE / QA-FE 迭代 > max_rounds | 终止循环,输出最新 QA 报告 | + ### Task Metadata Registry | Task ID | Role | Phase | Dependencies | Description | @@ -282,12 +364,12 @@ Coordinator supports `--resume` / `--continue` for interrupted sessions: | Resource | Path | Usage | |----------|------|-------| -| Document Standards | specs/document-standards.md | YAML frontmatter, naming, structure | -| Quality Gates | specs/quality-gates.md | Per-phase quality gates | -| Product Brief Template | templates/product-brief.md | DRAFT-001 | -| Requirements Template | templates/requirements-prd.md | DRAFT-002 | -| Architecture Template | templates/architecture-doc.md | DRAFT-003 | -| Epics Template | templates/epics-template.md | DRAFT-004 | +| Document Standards | [specs/document-standards.md](specs/document-standards.md) | YAML frontmatter, naming, structure | +| Quality Gates | [specs/quality-gates.md](specs/quality-gates.md) | Per-phase quality gates | +| Product Brief Template | [templates/product-brief.md](templates/product-brief.md) | DRAFT-001 | +| Requirements Template | [templates/requirements-prd.md](templates/requirements-prd.md) | DRAFT-002 | +| Architecture Template | [templates/architecture-doc.md](templates/architecture-doc.md) | DRAFT-003 | +| Epics Template | [templates/epics-template.md](templates/epics-template.md) | DRAFT-004 | ## Error Handling diff --git a/.claude/skills/workflow-lite-plan/SKILL.md b/.claude/skills/workflow-lite-plan/SKILL.md index f59595ed..e0508846 100644 --- a/.claude/skills/workflow-lite-plan/SKILL.md +++ b/.claude/skills/workflow-lite-plan/SKILL.md @@ -189,6 +189,6 @@ Plan mode and execute mode are triggered by skill name routing (see Mode Detecti | Phase | Document | Purpose | Compact | |-------|----------|---------|---------| | 1 | [phases/01-lite-plan.md](phases/01-lite-plan.md) | Complete planning pipeline: exploration, clarification, planning, confirmation, handoff | Phase 1 完成后可压缩 | -| 2 | [phases/02-lite-execute.md](phases/02-lite-execute.md) | Complete execution engine: input modes, task grouping, batch execution, code review | **⚠️ 执行期间禁止压缩,压缩后必须重读** | +| 2 | [phases/02-lite-execute.md](phases/02-lite-execute.md) | Complete execution engine: input modes, task grouping, batch execution, code review | **⚠️ 压缩时必须完整保留,若仅剩摘要须重读** | -**Phase 2 Compact Rule**: Phase 2 是执行引擎,包含 Step 1-6 的完整执行协议。如果 compact 发生且 Phase 2 内容仅剩摘要,**必须立即 `Read("phases/02-lite-execute.md")` 重新加载后再继续执行**。不得基于摘要执行任何 Step。 +**Phase 2 Compact Rule**: Phase 2 是执行引擎,包含 Step 1-6 的完整执行协议。compact 压缩时必须完整保留 Phase 2 内容。若 compact 后 Phase 2 仅剩摘要,**必须立即 `Read("phases/02-lite-execute.md")` 重新加载后再继续执行**。不得基于摘要执行任何 Step。 diff --git a/.claude/skills/workflow-skill-designer/SKILL.md b/.claude/skills/workflow-skill-designer/SKILL.md index 11c5447f..cb3bc53a 100644 --- a/.claude/skills/workflow-skill-designer/SKILL.md +++ b/.claude/skills/workflow-skill-designer/SKILL.md @@ -174,9 +174,9 @@ Multi-phase workflows span long conversations. Context compression (compact) may | Phase | Document | Purpose | Compact | |-------|----------|---------|---------| | 1 | phases/01-xxx.md | Planning pipeline | Phase 1 完成后可压缩 | -| 2 | phases/02-xxx.md | Execution engine | **⚠️ 执行期间禁止压缩,压缩后必须重读** | +| 2 | phases/02-xxx.md | Execution engine | **⚠️ 压缩时必须完整保留,若仅剩摘要须重读** | -**Phase N Compact Rule**: Phase N 是执行引擎,包含 Step 1-M 的完整执行协议。如果 compact 发生且 Phase N 内容仅剩摘要,**必须立即 `Read("phases/0N-xxx.md")` 重新加载后再继续执行**。不得基于摘要执行任何 Step。 +**Phase N Compact Rule**: Phase N 是执行引擎,包含 Step 1-M 的完整执行协议。compact 压缩时必须完整保留 Phase N 内容。若 compact 后仅剩摘要,**必须立即 `Read("phases/0N-xxx.md")` 重新加载后再继续执行**。不得基于摘要执行任何 Step。 ``` **Phase 文件顶部** — Compact Protection directive: diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts index dda89c0c..5cc84757 100644 --- a/ccw/frontend/src/lib/api.ts +++ b/ccw/frontend/src/lib/api.ts @@ -7141,11 +7141,19 @@ import type { AnalysisSessionSummary, AnalysisSessionDetail } from '../types/ana * Fetch list of analysis sessions */ export async function fetchAnalysisSessions( - projectPath?: string + projectPath?: string, + options?: { limit?: number; offset?: number } ): Promise { - const data = await fetchApi<{ success: boolean; data: AnalysisSessionSummary[]; error?: string }>( - withPath('/api/analysis', projectPath) - ); + const params = new URLSearchParams(); + if (options?.limit) params.set('limit', String(options.limit)); + if (options?.offset) params.set('offset', String(options.offset)); + + const queryString = params.toString(); + const path = queryString + ? `${withPath('/api/analysis', projectPath)}&${queryString}` + : withPath('/api/analysis', projectPath); + + const data = await fetchApi<{ success: boolean; data: AnalysisSessionSummary[]; error?: string }>(path); if (!data.success) { throw new Error(data.error || 'Failed to fetch analysis sessions'); } diff --git a/ccw/frontend/src/pages/AnalysisPage.tsx b/ccw/frontend/src/pages/AnalysisPage.tsx index ab6f9745..6169f76a 100644 --- a/ccw/frontend/src/pages/AnalysisPage.tsx +++ b/ccw/frontend/src/pages/AnalysisPage.tsx @@ -24,10 +24,11 @@ import { Card } from '@/components/ui/Card'; import { Input } from '@/components/ui/Input'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; -import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/Tabs'; +import { TabsNavigation } from '@/components/ui/TabsNavigation'; import { fetchAnalysisSessions, fetchAnalysisDetail } from '@/lib/api'; import { MessageRenderer } from '@/components/shared/CliStreamMonitor/MessageRenderer'; import { JsonCardView } from '@/components/shared/JsonCardView'; +import { cn } from '@/lib/utils'; import type { AnalysisSessionSummary } from '@/types/analysis'; // ========== Session Card Component ========== @@ -41,23 +42,22 @@ interface SessionCardProps { function SessionCard({ session, onClick, isSelected }: SessionCardProps) { return ( -
-
-
- - {session.topic} -
-

{session.id}

+
+
+ +

{session.topic}

-
- +

{session.id}

+
+ {session.status === 'completed' ? ( <>完成 ) : ( @@ -82,6 +82,8 @@ interface DetailPanelProps { } function DetailPanel({ sessionId, projectPath, onClose }: DetailPanelProps) { + const [activeTab, setActiveTab] = useState('discussion'); + const { data: detail, isLoading, error } = useQuery({ queryKey: ['analysis-detail', sessionId, projectPath], queryFn: () => fetchAnalysisDetail(sessionId, projectPath), @@ -110,13 +112,20 @@ function DetailPanel({ sessionId, projectPath, onClose }: DetailPanelProps) { // Build available tabs based on content const tabs = [ - { id: 'discussion', label: '讨论记录', icon: MessageSquare, content: detail.discussion }, - { id: 'conclusions', label: '结论', icon: CheckCircle, content: detail.conclusions }, - { id: 'explorations', label: '代码探索', icon: Code, content: detail.explorations }, - { id: 'perspectives', label: '视角分析', icon: FileText, content: detail.perspectives }, - ].filter(tab => tab.content); + { value: 'discussion', label: '讨论记录', icon: }, + { value: 'conclusions', label: '结论', icon: }, + { value: 'explorations', label: '代码探索', icon: }, + { value: 'perspectives', label: '视角分析', icon: }, + ].filter(tab => { + const key = tab.value as keyof typeof detail; + return detail[key]; + }); - const defaultTab = tabs[0]?.id || 'discussion'; + // Ensure activeTab is valid + const validTab = tabs.find(t => t.value === activeTab); + if (!validTab && tabs.length > 0) { + setActiveTab(tabs[0].value); + } return (
@@ -138,46 +147,35 @@ function DetailPanel({ sessionId, projectPath, onClose }: DetailPanelProps) { {/* Tabs Content */} {tabs.length > 0 ? ( - - - {tabs.map(tab => ( - - - {tab.label} - - ))} - - + <> +
{/* Discussion Tab */} - - {detail.discussion && ( - - )} - + {activeTab === 'discussion' && detail.discussion && ( + + )} {/* Conclusions Tab */} - - {detail.conclusions && ( - - )} - + {activeTab === 'conclusions' && detail.conclusions && ( + + )} {/* Explorations Tab */} - - {detail.explorations && ( - - )} - + {activeTab === 'explorations' && detail.explorations && ( + + )} {/* Perspectives Tab */} - - {detail.perspectives && ( - - )} - + {activeTab === 'perspectives' && detail.perspectives && ( + + )}
-
+ ) : (
暂无分析内容 @@ -210,14 +208,16 @@ export function AnalysisPage() { {/* Left Panel - List */}
{/* Header */} -
-

- - Analysis Viewer -

-

- 查看 /workflow:analyze-with-file 命令的分析结果 -

+
+ +
+

+ Analysis Viewer +

+

+ 查看 /workflow:analyze-with-file 命令的分析结果 +

+
{/* Search */} diff --git a/ccw/src/core/routes/analysis-routes.ts b/ccw/src/core/routes/analysis-routes.ts index b684d8ab..38ab1930 100644 --- a/ccw/src/core/routes/analysis-routes.ts +++ b/ccw/src/core/routes/analysis-routes.ts @@ -8,11 +8,16 @@ */ import { readdir, readFile, stat } from 'fs/promises'; -import { existsSync } from 'fs'; +import { existsSync, statSync } from 'fs'; import { join } from 'path'; import type { RouteContext } from './types.js'; import { resolvePath } from '../../utils/path-resolver.js'; +// Concurrency limit for processing folders +const MAX_CONCURRENT = 10; +// Timeout for individual file operations (ms) +const FILE_TIMEOUT = 5000; + /** * Analysis session summary for list view */ @@ -153,6 +158,8 @@ export async function handleAnalysisRoutes(ctx: RouteContext): Promise if (pathname === '/api/analysis' && req.method === 'GET') { try { const projectPath = ctx.url.searchParams.get('projectPath') || initialPath; + const limit = Math.min(parseInt(ctx.url.searchParams.get('limit') || '50', 10), 100); + const offset = parseInt(ctx.url.searchParams.get('offset') || '0', 10); const resolvedPath = resolvePath(projectPath); const analysisDir = join(resolvedPath, '.workflow', '.analysis'); @@ -163,18 +170,22 @@ export async function handleAnalysisRoutes(ctx: RouteContext): Promise } const folders = await readdir(analysisDir); - const sessions: AnalysisSessionSummary[] = []; - for (const folder of folders) { - const summary = await getSessionSummary(analysisDir, folder); - if (summary) sessions.push(summary); - } + // Parallel processing for better performance + const summaries = await Promise.all( + folders.map(folder => getSessionSummary(analysisDir, folder)) + ); - // Sort by date descending - sessions.sort((a, b) => b.createdAt.localeCompare(a.createdAt)); + // Filter valid sessions and sort by date descending + const allSessions = summaries + .filter((s): s is AnalysisSessionSummary => s !== null) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); + + const total = allSessions.length; + const paginatedSessions = allSessions.slice(offset, offset + limit); res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ success: true, data: sessions, total: sessions.length })); + res.end(JSON.stringify({ success: true, data: paginatedSessions, total })); return true; } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' });