mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Add interactive pre-flight checklists for ccw-loop and workflow-plan, including validation and task transformation steps
- Implemented `prep-loop.md` for ccw-loop, detailing source discovery, validation, task transformation, and auto-loop configuration. - Created `prep-plan.md` for workflow planning, covering environment checks, task quality assessment, execution preferences, and final confirmation. - Defined schemas and integration points for `prep-package.json` in both ccw-loop and workflow-plan skills, ensuring proper validation and task handling. - Added error handling mechanisms for various scenarios during the preparation phases.
This commit is contained in:
456
.codex/prompts/prep-loop.md
Normal file
456
.codex/prompts/prep-loop.md
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
---
|
||||||
|
description: "Interactive pre-flight checklist for ccw-loop. Discovers JSONL from collaborative-plan-with-file, analyze-with-file, brainstorm-to-cycle sessions; validates, transforms to ccw-loop task format, writes prep-package.json + tasks.jsonl, then launches the loop."
|
||||||
|
argument-hint: '[SOURCE="<path-to-tasks.jsonl-or-session-folder>"] [MAX_ITER=10]'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pre-Flight Checklist for CCW Loop
|
||||||
|
|
||||||
|
You are an interactive preparation assistant. Your job is to discover and consume task artifacts from upstream planning/analysis/brainstorm skills, validate them, transform into ccw-loop's task format, and launch an **unattended** development loop. Follow each step sequentially. **Ask the user questions when information is missing.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Source Discovery
|
||||||
|
|
||||||
|
### 1.1 Auto-Detect Available Sessions
|
||||||
|
|
||||||
|
Scan for upstream artifacts from the three supported source skills:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const projectRoot = Bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()
|
||||||
|
|
||||||
|
// Source 1: collaborative-plan-with-file
|
||||||
|
const cplanSessions = Glob(`${projectRoot}/.workflow/.planning/CPLAN-*/tasks.jsonl`)
|
||||||
|
.map(p => ({
|
||||||
|
path: p,
|
||||||
|
source: 'collaborative-plan-with-file',
|
||||||
|
type: 'jsonl',
|
||||||
|
session: p.match(/CPLAN-[^/]+/)?.[0],
|
||||||
|
mtime: fs.statSync(p).mtime
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Source 2: analyze-with-file
|
||||||
|
const anlSessions = Glob(`${projectRoot}/.workflow/.analysis/ANL-*/tasks.jsonl`)
|
||||||
|
.map(p => ({
|
||||||
|
path: p,
|
||||||
|
source: 'analyze-with-file',
|
||||||
|
type: 'jsonl',
|
||||||
|
session: p.match(/ANL-[^/]+/)?.[0],
|
||||||
|
mtime: fs.statSync(p).mtime
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Source 3: brainstorm-to-cycle
|
||||||
|
const bsSessions = Glob(`${projectRoot}/.workflow/.brainstorm/*/cycle-task.md`)
|
||||||
|
.map(p => ({
|
||||||
|
path: p,
|
||||||
|
source: 'brainstorm-to-cycle',
|
||||||
|
type: 'markdown',
|
||||||
|
session: p.match(/\.brainstorm\/([^/]+)/)?.[1],
|
||||||
|
mtime: fs.statSync(p).mtime
|
||||||
|
}))
|
||||||
|
|
||||||
|
const allSources = [...cplanSessions, ...anlSessions, ...bsSessions]
|
||||||
|
.sort((a, b) => b.mtime - a.mtime) // Most recent first
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Display Discovered Sources
|
||||||
|
|
||||||
|
```
|
||||||
|
可用的上游任务源
|
||||||
|
════════════════
|
||||||
|
|
||||||
|
collaborative-plan-with-file:
|
||||||
|
1. CPLAN-auth-redesign-20260208 tasks.jsonl (5 tasks, 2h ago)
|
||||||
|
2. CPLAN-api-cleanup-20260205 tasks.jsonl (3 days ago)
|
||||||
|
|
||||||
|
analyze-with-file:
|
||||||
|
3. ANL-perf-audit-20260207 tasks.jsonl (8 tasks, 1d ago)
|
||||||
|
|
||||||
|
brainstorm-to-cycle:
|
||||||
|
4. BS-notification-system cycle-task.md (1d ago)
|
||||||
|
|
||||||
|
手动输入:
|
||||||
|
5. 自定义路径 (输入 JSONL 文件路径或任务描述)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 User Selection
|
||||||
|
|
||||||
|
Ask the user to select a source:
|
||||||
|
|
||||||
|
> "请选择任务来源(输入编号),或输入 JSONL 文件的完整路径:
|
||||||
|
> 也可以输入 'manual' 手动输入任务描述(不使用上游 JSONL)"
|
||||||
|
|
||||||
|
**If `$SOURCE` argument provided**, skip discovery and use directly:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (options.SOURCE) {
|
||||||
|
// Validate path exists
|
||||||
|
if (!fs.existsSync(options.SOURCE)) {
|
||||||
|
console.error(`文件不存在: ${options.SOURCE}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedSource = {
|
||||||
|
path: options.SOURCE,
|
||||||
|
source: inferSource(options.SOURCE),
|
||||||
|
type: options.SOURCE.endsWith('.jsonl') ? 'jsonl' : 'markdown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Source Validation & Task Loading
|
||||||
|
|
||||||
|
### 2.1 For JSONL Sources (collaborative-plan / analyze-with-file)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function validateAndLoadJsonl(jsonlPath) {
|
||||||
|
const content = Read(jsonlPath)
|
||||||
|
const lines = content.trim().split('\n').filter(l => l.trim())
|
||||||
|
const tasks = []
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
try {
|
||||||
|
const task = JSON.parse(lines[i])
|
||||||
|
|
||||||
|
// Required fields check
|
||||||
|
const requiredFields = ['id', 'title', 'description']
|
||||||
|
const missing = requiredFields.filter(f => !task[f])
|
||||||
|
if (missing.length > 0) {
|
||||||
|
errors.push(`Line ${i + 1}: missing fields: ${missing.join(', ')}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate task structure
|
||||||
|
if (task.id && task.title && task.description) {
|
||||||
|
tasks.push(task)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`Line ${i + 1}: invalid JSON: ${e.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tasks, errors, total_lines: lines.length }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Display validation results:
|
||||||
|
|
||||||
|
```
|
||||||
|
JSONL 验证
|
||||||
|
══════════
|
||||||
|
文件: .workflow/.planning/CPLAN-auth-redesign-20260208/tasks.jsonl
|
||||||
|
来源: collaborative-plan-with-file
|
||||||
|
|
||||||
|
✓ 5/5 行解析成功
|
||||||
|
✓ 必需字段完整 (id, title, description)
|
||||||
|
✓ 3 个任务含收敛标准 (convergence)
|
||||||
|
⚠ 2 个任务缺少收敛标准 (将使用默认)
|
||||||
|
|
||||||
|
任务列表:
|
||||||
|
TASK-001 [high] Implement JWT token service (feature, 3 files)
|
||||||
|
TASK-002 [high] Add OAuth2 Google strategy (feature, 2 files)
|
||||||
|
TASK-003 [medium] Create user session middleware (feature, 4 files)
|
||||||
|
TASK-004 [low] Add rate limiting to auth endpoints (enhancement, 2 files)
|
||||||
|
TASK-005 [low] Write integration tests (testing, 5 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 For Markdown Sources (brainstorm-to-cycle)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function loadBrainstormTask(mdPath) {
|
||||||
|
const content = Read(mdPath)
|
||||||
|
|
||||||
|
// Extract enriched task description from cycle-task.md
|
||||||
|
// Format: # Generated Task \n\n **Idea**: ... \n\n --- \n\n {enrichedTask}
|
||||||
|
const taskMatch = content.match(/---\s*\n([\s\S]+)$/)
|
||||||
|
const enrichedTask = taskMatch ? taskMatch[1].trim() : content
|
||||||
|
|
||||||
|
// Parse into a single composite task
|
||||||
|
return {
|
||||||
|
tasks: [{
|
||||||
|
id: 'TASK-001',
|
||||||
|
title: extractTitle(content),
|
||||||
|
description: enrichedTask,
|
||||||
|
type: 'feature',
|
||||||
|
priority: 'high',
|
||||||
|
effort: 'large',
|
||||||
|
source: { tool: 'brainstorm-to-cycle', path: mdPath }
|
||||||
|
}],
|
||||||
|
errors: [],
|
||||||
|
is_composite: true // Single large task from brainstorm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Display:
|
||||||
|
|
||||||
|
```
|
||||||
|
Brainstorm 任务加载
|
||||||
|
══════════════════
|
||||||
|
文件: .workflow/.brainstorm/notification-system/cycle-task.md
|
||||||
|
来源: brainstorm-to-cycle
|
||||||
|
|
||||||
|
ℹ 脑暴输出为复合任务描述(非结构化 JSONL)
|
||||||
|
标题: Build real-time notification system
|
||||||
|
类型: feature (composite)
|
||||||
|
|
||||||
|
是否需要将其拆分为多个子任务?(Y/n)
|
||||||
|
```
|
||||||
|
|
||||||
|
If user selects **Y** (split), analyze the task description and generate sub-tasks:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Analyze and decompose the composite task into 3-7 sub-tasks
|
||||||
|
// Use mcp__ace-tool__search_context to find relevant patterns
|
||||||
|
// Generate structured tasks with convergence criteria
|
||||||
|
```
|
||||||
|
|
||||||
|
If user selects **n** (keep as single), use as-is.
|
||||||
|
|
||||||
|
### 2.3 Validation Gate
|
||||||
|
|
||||||
|
If validation has errors:
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠ 验证发现 {N} 个问题:
|
||||||
|
Line 3: missing fields: description
|
||||||
|
Line 7: invalid JSON
|
||||||
|
|
||||||
|
选项:
|
||||||
|
1. 跳过有问题的行,继续 ({valid_count} 个有效任务)
|
||||||
|
2. 取消,手动修复后重试
|
||||||
|
```
|
||||||
|
|
||||||
|
**Block if 0 valid tasks.** Warn and continue if some tasks invalid.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Task Transformation
|
||||||
|
|
||||||
|
Transform unified JSONL tasks → ccw-loop `develop.tasks[]` format.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function transformToCcwLoopTasks(sourceTasks) {
|
||||||
|
const now = getUtc8ISOString()
|
||||||
|
|
||||||
|
return sourceTasks.map((task, index) => ({
|
||||||
|
// Core fields (ccw-loop native)
|
||||||
|
id: task.id || `task-${String(index + 1).padStart(3, '0')}`,
|
||||||
|
description: task.title
|
||||||
|
? `${task.title}: ${task.description}`
|
||||||
|
: task.description,
|
||||||
|
tool: inferTool(task), // 'gemini' | 'qwen' | 'codex'
|
||||||
|
mode: 'write',
|
||||||
|
status: 'pending',
|
||||||
|
priority: mapPriority(task.priority), // 1 (high) | 2 (medium) | 3 (low)
|
||||||
|
files_changed: (task.files || []).map(f => f.path || f),
|
||||||
|
created_at: now,
|
||||||
|
completed_at: null,
|
||||||
|
|
||||||
|
// Extended fields (preserved from source for agent reference)
|
||||||
|
_source: task.source || { tool: 'manual' },
|
||||||
|
_convergence: task.convergence || null,
|
||||||
|
_type: task.type || 'feature',
|
||||||
|
_effort: task.effort || 'medium',
|
||||||
|
_depends_on: task.depends_on || []
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferTool(task) {
|
||||||
|
// Default to gemini for write tasks
|
||||||
|
return 'gemini'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPriority(priority) {
|
||||||
|
switch (priority) {
|
||||||
|
case 'high': case 'critical': return 1
|
||||||
|
case 'medium': return 2
|
||||||
|
case 'low': return 3
|
||||||
|
default: return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Display transformed tasks:
|
||||||
|
|
||||||
|
```
|
||||||
|
任务转换
|
||||||
|
════════
|
||||||
|
源格式: unified JSONL (collaborative-plan-with-file)
|
||||||
|
目标格式: ccw-loop develop.tasks
|
||||||
|
|
||||||
|
task-001 [P1] Implement JWT token service: Create JWT service... gemini/write pending
|
||||||
|
task-002 [P1] Add OAuth2 Google strategy: Implement passport... gemini/write pending
|
||||||
|
task-003 [P2] Create user session middleware: Add Express... gemini/write pending
|
||||||
|
task-004 [P3] Add rate limiting to auth endpoints: Implement... gemini/write pending
|
||||||
|
task-005 [P3] Write integration tests: Create test suite... gemini/write pending
|
||||||
|
|
||||||
|
共 5 个任务 (2 high, 1 medium, 2 low)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.1 Task Reordering (Optional)
|
||||||
|
|
||||||
|
Ask: "是否需要调整任务顺序或移除某些任务?(输入编号排列如 '1,3,2,5' 或回车保持当前顺序)"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Auto-Loop Configuration
|
||||||
|
|
||||||
|
### 4.1 Present Defaults
|
||||||
|
|
||||||
|
```
|
||||||
|
自动循环配置
|
||||||
|
════════════
|
||||||
|
模式: 全自动 (develop → debug → validate → complete)
|
||||||
|
最大迭代: $MAX_ITER (默认 10)
|
||||||
|
超时: 10 分钟/action
|
||||||
|
|
||||||
|
收敛标准 (从源任务汇总):
|
||||||
|
${tasksWithConvergence} 个任务含收敛标准 → 自动验证
|
||||||
|
${tasksWithoutConvergence} 个任务无收敛标准 → 使用默认 (测试通过)
|
||||||
|
|
||||||
|
需要调整参数吗?(直接回车使用默认值)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Customization (if requested)
|
||||||
|
|
||||||
|
> "请选择要调整的项目:
|
||||||
|
> 1. 最大迭代次数 (当前: 10)
|
||||||
|
> 2. 每个 action 超时 (当前: 10 分钟)
|
||||||
|
> 3. 全部使用默认值"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Final Confirmation
|
||||||
|
|
||||||
|
```
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
Pre-Flight 检查完成
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
|
||||||
|
来源: collaborative-plan-with-file (CPLAN-auth-redesign-20260208)
|
||||||
|
任务数: 5 个 (2 high, 1 medium, 2 low)
|
||||||
|
验证: ✓ 5/5 任务格式正确
|
||||||
|
收敛: 3/5 任务含收敛标准
|
||||||
|
自动模式: ON (最多 10 次迭代)
|
||||||
|
|
||||||
|
任务摘要:
|
||||||
|
1. [P1] Implement JWT token service
|
||||||
|
2. [P1] Add OAuth2 Google strategy
|
||||||
|
3. [P2] Create user session middleware
|
||||||
|
4. [P3] Add rate limiting to auth endpoints
|
||||||
|
5. [P3] Write integration tests
|
||||||
|
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask: "确认启动?(Y/n)"
|
||||||
|
- If **Y** → proceed to Step 6
|
||||||
|
- If **n** → ask which part to revise
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Write Artifacts
|
||||||
|
|
||||||
|
### 6.1 Write prep-package.json
|
||||||
|
|
||||||
|
Write to `{projectRoot}/.workflow/.loop/prep-package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"generated_at": "{ISO8601_UTC+8}",
|
||||||
|
"prep_status": "ready",
|
||||||
|
"target_skill": "ccw-loop",
|
||||||
|
|
||||||
|
"environment": {
|
||||||
|
"project_root": "{projectRoot}",
|
||||||
|
"tech_stack": "{detected tech stack}",
|
||||||
|
"test_framework": "{detected test framework}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"source": {
|
||||||
|
"tool": "collaborative-plan-with-file",
|
||||||
|
"session_id": "CPLAN-auth-redesign-20260208",
|
||||||
|
"jsonl_path": "{projectRoot}/.workflow/.planning/CPLAN-auth-redesign-20260208/tasks.jsonl",
|
||||||
|
"task_count": 5,
|
||||||
|
"tasks_with_convergence": 3
|
||||||
|
},
|
||||||
|
|
||||||
|
"tasks": {
|
||||||
|
"total": 5,
|
||||||
|
"by_priority": { "high": 2, "medium": 1, "low": 2 },
|
||||||
|
"by_type": { "feature": 3, "enhancement": 1, "testing": 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
"auto_loop": {
|
||||||
|
"enabled": true,
|
||||||
|
"no_confirmation": true,
|
||||||
|
"max_iterations": 10,
|
||||||
|
"timeout_per_action_ms": 600000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Write tasks.jsonl
|
||||||
|
|
||||||
|
Write transformed tasks to `{projectRoot}/.workflow/.loop/prep-tasks.jsonl` (ccw-loop format):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const jsonlContent = transformedTasks.map(t => JSON.stringify(t)).join('\n')
|
||||||
|
Write(`${projectRoot}/.workflow/.loop/prep-tasks.jsonl`, jsonlContent)
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm:
|
||||||
|
|
||||||
|
```
|
||||||
|
✓ prep-package.json → .workflow/.loop/prep-package.json
|
||||||
|
✓ prep-tasks.jsonl → .workflow/.loop/prep-tasks.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7: Launch Loop
|
||||||
|
|
||||||
|
Invoke the skill:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ccw-loop --auto TASK="Execute tasks from {source.tool} session {source.session_id}"
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `$ccw-loop` — 展开为 skill 调用
|
||||||
|
- `--auto` — 启用全自动模式
|
||||||
|
- Skill 端会检测 `prep-package.json` 并加载 `prep-tasks.jsonl`
|
||||||
|
|
||||||
|
**Skill 端会做以下检查**(见 Phase 1 Step 1.1):
|
||||||
|
1. 检测 `prep-package.json` 是否存在
|
||||||
|
2. 验证 `prep_status === "ready"`
|
||||||
|
3. 验证 `target_skill === "ccw-loop"`
|
||||||
|
4. 校验 `project_root` 与当前项目一致
|
||||||
|
5. 校验文件时效(24h 内生成)
|
||||||
|
6. 验证 `prep-tasks.jsonl` 存在且可读
|
||||||
|
7. 全部通过 → 加载预构建任务列表;任一失败 → 回退到默认 INIT 行为
|
||||||
|
|
||||||
|
Print:
|
||||||
|
|
||||||
|
```
|
||||||
|
启动 ccw-loop (自动模式)...
|
||||||
|
prep-package.json → Phase 1 自动加载并校验
|
||||||
|
prep-tasks.jsonl → 5 个预构建任务加载到 develop.tasks
|
||||||
|
循环: develop → validate → complete (最多 10 次迭代)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| 情况 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 无可用上游会话 | 提示用户先运行 collaborative-plan / analyze-with-file / brainstorm,或选择手动输入 |
|
||||||
|
| JSONL 格式全部无效 | 报告错误,**不启动 loop** |
|
||||||
|
| JSONL 部分无效 | 警告无效行,用有效任务继续 |
|
||||||
|
| brainstorm cycle-task.md 为空 | 报告错误,建议完成 brainstorm 流程 |
|
||||||
|
| 用户取消确认 | 保存 prep-package.json (prep_status="cancelled"),提示可修改后重新运行 |
|
||||||
|
| Skill 端 prep-package 校验失败 | Skill 打印警告,回退到无 prep 的默认 INIT 行为(不阻塞执行) |
|
||||||
373
.codex/prompts/prep-plan.md
Normal file
373
.codex/prompts/prep-plan.md
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
---
|
||||||
|
description: "Interactive pre-flight checklist for workflow:plan. Validates environment, refines task to GOAL/SCOPE/CONTEXT, collects source docs, configures execution preferences, writes prep-package.json, then launches the workflow."
|
||||||
|
argument-hint: TASK="<task description>" [EXEC_METHOD=agent|cli|hybrid] [CLI_TOOL=codex|gemini|qwen]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pre-Flight Checklist for Workflow Plan
|
||||||
|
|
||||||
|
You are an interactive preparation assistant. Your job is to ensure everything is ready for an **unattended** `workflow:plan` run with `--yes` mode. Follow each step sequentially. **Ask the user questions when information is missing.** At the end, write `prep-package.json` and invoke the skill.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Environment Prerequisites
|
||||||
|
|
||||||
|
Check these items. Report results as a checklist.
|
||||||
|
|
||||||
|
### 1.1 Required (block if any fail)
|
||||||
|
|
||||||
|
- **Project root**: Confirm current working directory is a valid project (has package.json, Cargo.toml, pyproject.toml, go.mod, or similar)
|
||||||
|
- **Writable workspace**: Ensure `.workflow/` directory exists or can be created
|
||||||
|
- **Git status**: Run `git status --short`. If working tree is dirty, WARN but don't block
|
||||||
|
|
||||||
|
### 1.2 Strongly Recommended (warn if missing)
|
||||||
|
|
||||||
|
- **project-tech.json**: Check `{projectRoot}/.workflow/project-tech.json`
|
||||||
|
- If missing: WARN — Phase 1 will call `workflow:init` to generate it. Ask user: "检测到项目使用 [tech stack from package.json], 是否正确?需要补充什么?"
|
||||||
|
- **project-guidelines.json**: Check `{projectRoot}/.workflow/project-guidelines.json`
|
||||||
|
- If missing: WARN — will be generated as empty scaffold. Ask: "有特定的编码规范需要遵循吗?"
|
||||||
|
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
||||||
|
- If missing: Ask: "未检测到测试框架,请指定测试命令(如 `npm test`),或输入 'skip' 跳过"
|
||||||
|
|
||||||
|
### 1.3 Output
|
||||||
|
|
||||||
|
Print formatted checklist:
|
||||||
|
|
||||||
|
```
|
||||||
|
环境检查
|
||||||
|
════════
|
||||||
|
✓ 项目根目录: D:\myproject
|
||||||
|
✓ .workflow/ 目录就绪
|
||||||
|
⚠ Git: 3 个未提交变更
|
||||||
|
✓ project-tech.json: 已检测 (Express + TypeORM + PostgreSQL)
|
||||||
|
⚠ project-guidelines.json: 未找到 (Phase 1 将生成空模板)
|
||||||
|
✓ 测试框架: jest (npm test)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Task Quality Assessment
|
||||||
|
|
||||||
|
### 2.0 Requirement Source Tracking
|
||||||
|
|
||||||
|
**在评估任务质量之前,先追踪需求的原始来源。** 这些引用会写入 prep-package.json,供 Phase 2 context-gather 和 Phase 3 task-generation 使用。
|
||||||
|
|
||||||
|
Ask the user:
|
||||||
|
> "任务需求的来源是什么?可以提供以下一种或多种:
|
||||||
|
> 1. 本地文档路径 (如 docs/prd.md, requirements/feature-spec.md)
|
||||||
|
> 2. GitHub Issue URL (如 https://github.com/org/repo/issues/123)
|
||||||
|
> 3. 设计文档 / 原型链接
|
||||||
|
> 4. 会话中直接描述 (无外部文档)
|
||||||
|
>
|
||||||
|
> 请输入来源路径/URL(多个用逗号分隔),或输入 'none' 表示无外部来源"
|
||||||
|
|
||||||
|
**Processing logic**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const sourceRefs = []
|
||||||
|
|
||||||
|
for (const input of userInputs) {
|
||||||
|
if (input === 'none') break
|
||||||
|
|
||||||
|
const ref = { path: input, type: 'unknown', status: 'unverified' }
|
||||||
|
|
||||||
|
if (input.startsWith('http')) {
|
||||||
|
ref.type = 'url'
|
||||||
|
ref.status = 'linked'
|
||||||
|
} else if (fs.existsSync(input) || fs.existsSync(`${projectRoot}/${input}`)) {
|
||||||
|
ref.type = 'local_file'
|
||||||
|
ref.path = fs.existsSync(input) ? input : `${projectRoot}/${input}`
|
||||||
|
ref.status = 'verified'
|
||||||
|
ref.preview = Read(ref.path, { limit: 20 })
|
||||||
|
} else {
|
||||||
|
ref.type = 'local_file'
|
||||||
|
ref.status = 'not_found'
|
||||||
|
console.warn(`⚠ 文件未找到: ${input}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceRefs.push(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect common requirement docs
|
||||||
|
const autoDetectPaths = [
|
||||||
|
'docs/prd.md', 'docs/PRD.md', 'docs/requirements.md',
|
||||||
|
'docs/design.md', 'docs/spec.md', 'requirements/*.md', 'specs/*.md'
|
||||||
|
]
|
||||||
|
for (const pattern of autoDetectPaths) {
|
||||||
|
const found = Glob(pattern)
|
||||||
|
found.forEach(f => {
|
||||||
|
if (!sourceRefs.some(r => r.path === f)) {
|
||||||
|
sourceRefs.push({ path: f, type: 'auto_detected', status: 'verified' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Display detected sources:
|
||||||
|
|
||||||
|
```
|
||||||
|
需求来源
|
||||||
|
════════
|
||||||
|
✓ docs/prd.md (本地文档, 已验证)
|
||||||
|
✓ https://github.com/.../issues/42 (URL, 已链接)
|
||||||
|
~ requirements/api-spec.md (自动检测)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 Scoring
|
||||||
|
|
||||||
|
Score the user's TASK against 5 dimensions, mapped to workflow:plan's GOAL/SCOPE/CONTEXT format.
|
||||||
|
Each dimension scores 0-2 (0=missing, 1=vague, 2=clear). **Total minimum: 6/10 to proceed.**
|
||||||
|
|
||||||
|
| # | 维度 | 映射 | 评分标准 |
|
||||||
|
|---|------|------|----------|
|
||||||
|
| 1 | **目标** (Objective) | → GOAL | 0=无具体内容 / 1=有方向无细节 / 2=具体可执行 |
|
||||||
|
| 2 | **成功标准** (Success Criteria) | → GOAL 补充 | 0=无 / 1=不可度量 / 2=可测试可验证 |
|
||||||
|
| 3 | **范围** (Scope) | → SCOPE | 0=无 / 1=笼统区域 / 2=具体文件/模块 |
|
||||||
|
| 4 | **约束** (Constraints) | → CONTEXT | 0=无 / 1=泛泛"别破坏" / 2=具体限制条件 |
|
||||||
|
| 5 | **技术上下文** (Tech Context) | → CONTEXT | 0=无 / 1=最少 / 2=丰富 |
|
||||||
|
|
||||||
|
### 2.2 Display Score
|
||||||
|
|
||||||
|
```
|
||||||
|
任务质量评估
|
||||||
|
════════════
|
||||||
|
目标(GOAL): ██████████ 2/2 "Add Google OAuth login with JWT session"
|
||||||
|
成功标准: █████░░░░░ 1/2 "Should work" → 需要细化
|
||||||
|
范围(SCOPE): ██████████ 2/2 "src/auth/*, src/strategies/*"
|
||||||
|
约束(CTX): ░░░░░░░░░░ 0/2 未指定 → 必须补充
|
||||||
|
技术上下文: █████░░░░░ 1/2 "TypeScript" → 可自动增强
|
||||||
|
|
||||||
|
总分: 6/10 (可接受,需交互补充)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Interactive Refinement
|
||||||
|
|
||||||
|
**For each dimension scoring < 2**, ask a targeted question:
|
||||||
|
|
||||||
|
**目标不清 (score 0-1)**:
|
||||||
|
> "请更具体地描述要实现什么功能?例如:'为现有 Express API 添加 Google OAuth 登录,生成 JWT token,支持 /api/auth/google 和 /api/auth/callback 两个端点'"
|
||||||
|
|
||||||
|
**成功标准缺失 (score 0-1)**:
|
||||||
|
> "完成后如何验证?请描述至少 2 个可测试的验收条件。例如:'1. 用户能通过 Google 账号登录 2. 登录后返回有效 JWT 3. 受保护路由能正确验证 token'"
|
||||||
|
|
||||||
|
**范围不明 (score 0-1)**:
|
||||||
|
> "这个任务涉及哪些文件或模块?我检测到以下可能相关的目录: [列出扫描到的相关目录],请确认或补充"
|
||||||
|
|
||||||
|
**约束缺失 (score 0-1)**:
|
||||||
|
> "有哪些限制条件?常见约束:不破坏现有 API / 使用现有数据库 / 不引入新依赖 / 保持现有模式。请选择或自定义"
|
||||||
|
|
||||||
|
**上下文不足 (score 0-1)**:
|
||||||
|
> "我从项目中检测到: [tech stack from project-tech.json]。还有需要知道的技术细节吗?"
|
||||||
|
|
||||||
|
### 2.4 Auto-Enhancement
|
||||||
|
|
||||||
|
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
|
||||||
|
- **Scope**: Use `Glob` and `Grep` to find related files
|
||||||
|
- **Context**: Read `project-tech.json` and key config files
|
||||||
|
- **Constraints**: Infer from `project-guidelines.json`
|
||||||
|
|
||||||
|
### 2.5 Assemble Structured Description
|
||||||
|
|
||||||
|
Map to workflow:plan's GOAL/SCOPE/CONTEXT format:
|
||||||
|
|
||||||
|
```
|
||||||
|
GOAL: {objective + success criteria}
|
||||||
|
SCOPE: {scope boundaries}
|
||||||
|
CONTEXT: {constraints + technical context}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Execution Preferences
|
||||||
|
|
||||||
|
### 3.1 Present Configuration & Ask for Overrides
|
||||||
|
|
||||||
|
```
|
||||||
|
执行配置
|
||||||
|
════════
|
||||||
|
|
||||||
|
自动模式: --yes (跳过所有确认)
|
||||||
|
自动提交: --with-commit (每个任务完成后自动 git commit)
|
||||||
|
|
||||||
|
执行方式: $EXEC_METHOD (默认 agent)
|
||||||
|
agent — Claude agent 直接实现
|
||||||
|
hybrid — Agent 编排 + CLI 处理复杂步骤 (推荐)
|
||||||
|
cli — 全部通过 CLI 工具执行
|
||||||
|
|
||||||
|
CLI 工具: $CLI_TOOL (默认 codex)
|
||||||
|
codex / gemini / qwen / auto
|
||||||
|
|
||||||
|
补充材料: 无 (可后续在 Phase 3 Phase 0 中添加)
|
||||||
|
|
||||||
|
需要调整任何参数吗?(直接回车使用默认值)
|
||||||
|
```
|
||||||
|
|
||||||
|
If user wants to customize, ask:
|
||||||
|
|
||||||
|
> "请选择要调整的项目:
|
||||||
|
> 1. 执行方式 (当前: agent)
|
||||||
|
> 2. CLI 工具 (当前: codex)
|
||||||
|
> 3. 是否自动提交 (当前: 是)
|
||||||
|
> 4. 补充材料路径
|
||||||
|
> 5. 全部使用默认值"
|
||||||
|
|
||||||
|
### 3.2 Build Execution Config
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const executionConfig = {
|
||||||
|
auto_yes: true,
|
||||||
|
with_commit: true,
|
||||||
|
execution_method: userChoice.executionMethod || 'agent',
|
||||||
|
preferred_cli_tool: userChoice.preferredCliTool || 'codex',
|
||||||
|
supplementary_materials: {
|
||||||
|
type: 'none',
|
||||||
|
content: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Final Confirmation Summary
|
||||||
|
|
||||||
|
```
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
Pre-Flight 检查完成
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
|
||||||
|
环境: ✓ 就绪 (3/3 必需, 2/3 推荐)
|
||||||
|
任务质量: 9/10 (优秀)
|
||||||
|
自动模式: ON (--yes --with-commit)
|
||||||
|
执行方式: hybrid (codex)
|
||||||
|
需求来源: 2 个文档 (docs/prd.md, issue #42)
|
||||||
|
|
||||||
|
结构化任务:
|
||||||
|
GOAL: Add Google OAuth login with JWT session management;
|
||||||
|
验收: 用户可 Google 登录, 返回 JWT, 受保护路由验证
|
||||||
|
SCOPE: src/auth/*, src/strategies/*, src/models/User.ts
|
||||||
|
CONTEXT: Express.js + TypeORM + PostgreSQL;
|
||||||
|
约束: 不破坏 /api/login, 使用现有 User 表
|
||||||
|
|
||||||
|
══════════════════════════════════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask: "确认启动?(Y/n)"
|
||||||
|
- If **Y** or Enter → proceed to Step 5
|
||||||
|
- If **n** → ask which part to revise, loop back
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Write prep-package.json
|
||||||
|
|
||||||
|
Write to `{projectRoot}/.workflow/.prep/plan-prep-package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"generated_at": "{ISO8601_UTC+8}",
|
||||||
|
"prep_status": "ready",
|
||||||
|
"target_skill": "workflow-plan-execute",
|
||||||
|
|
||||||
|
"environment": {
|
||||||
|
"project_root": "{projectRoot}",
|
||||||
|
"prerequisites": {
|
||||||
|
"required_passed": true,
|
||||||
|
"recommended_passed": true,
|
||||||
|
"warnings": ["{list of warnings}"]
|
||||||
|
},
|
||||||
|
"tech_stack": "{detected tech stack}",
|
||||||
|
"test_framework": "{detected test framework}",
|
||||||
|
"has_project_tech": true,
|
||||||
|
"has_project_guidelines": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"task": {
|
||||||
|
"original": "{$TASK raw input}",
|
||||||
|
"structured": {
|
||||||
|
"goal": "{GOAL string}",
|
||||||
|
"scope": "{SCOPE string}",
|
||||||
|
"context": "{CONTEXT string}"
|
||||||
|
},
|
||||||
|
"quality_score": 9,
|
||||||
|
"dimensions": {
|
||||||
|
"objective": { "score": 2, "value": "..." },
|
||||||
|
"success_criteria": { "score": 2, "value": "..." },
|
||||||
|
"scope": { "score": 2, "value": "..." },
|
||||||
|
"constraints": { "score": 2, "value": "..." },
|
||||||
|
"context": { "score": 1, "value": "..." }
|
||||||
|
},
|
||||||
|
"source_refs": [
|
||||||
|
{
|
||||||
|
"path": "docs/prd.md",
|
||||||
|
"type": "local_file",
|
||||||
|
"status": "verified",
|
||||||
|
"preview": "# Product Requirements - OAuth Integration\n..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "https://github.com/org/repo/issues/42",
|
||||||
|
"type": "url",
|
||||||
|
"status": "linked"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"execution": {
|
||||||
|
"auto_yes": true,
|
||||||
|
"with_commit": true,
|
||||||
|
"execution_method": "agent",
|
||||||
|
"preferred_cli_tool": "codex",
|
||||||
|
"supplementary_materials": {
|
||||||
|
"type": "none",
|
||||||
|
"content": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm:
|
||||||
|
```
|
||||||
|
✓ prep-package.json 已写入 .workflow/.prep/plan-prep-package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Launch Workflow
|
||||||
|
|
||||||
|
Invoke the skill using `$ARGUMENTS` pass-through:
|
||||||
|
|
||||||
|
```
|
||||||
|
$workflow-plan-execute --yes --with-commit TASK="$TASK_STRUCTURED"
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `$workflow-plan-execute` — 展开为 skill 调用
|
||||||
|
- `$TASK_STRUCTURED` — Step 2 组装的 GOAL/SCOPE/CONTEXT 格式任务
|
||||||
|
- `--yes` — 全自动模式
|
||||||
|
- `--with-commit` — 每任务自动提交(根据 Step 3 配置)
|
||||||
|
|
||||||
|
**Skill 端会做以下检查**(见 Phase 1 消费逻辑):
|
||||||
|
1. 检测 `.workflow/.prep/plan-prep-package.json` 是否存在
|
||||||
|
2. 验证 `prep_status === "ready"` 且 `target_skill === "workflow-plan-execute"`
|
||||||
|
3. 校验 `project_root` 与当前项目一致
|
||||||
|
4. 校验 `quality_score >= 6`
|
||||||
|
5. 校验文件时效(24h 内生成)
|
||||||
|
6. 校验必需字段完整性
|
||||||
|
7. 全部通过 → 加载配置;任一失败 → 回退默认行为 + 打印警告
|
||||||
|
|
||||||
|
Print:
|
||||||
|
```
|
||||||
|
启动 workflow:plan (自动模式)...
|
||||||
|
prep-package.json → Phase 1 自动加载并校验
|
||||||
|
执行方式: hybrid (codex) + auto-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| 情况 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 必需项检查失败 | 报告缺失项,给出修复建议,**不启动 workflow** |
|
||||||
|
| 任务质量 < 6/10 且用户拒绝补充 | 报告各维度得分,建议重写任务描述,**不启动 workflow** |
|
||||||
|
| 用户取消确认 | 保存 prep-package.json (prep_status="needs_refinement"),提示可修改后重新运行 |
|
||||||
|
| 环境检查有警告但非阻塞 | 记录警告到 prep-package.json,继续执行 |
|
||||||
|
| Skill 端 prep-package 校验失败 | Skill 打印警告,回退到无 prep 的默认行为(不阻塞执行) |
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
# CCW Loop-B: Hybrid Orchestrator Pattern
|
|
||||||
|
|
||||||
Iterative development workflow using coordinator + specialized workers architecture.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
CCW Loop-B implements a flexible orchestration pattern:
|
|
||||||
- **Coordinator**: Main agent managing state, user interaction, worker scheduling
|
|
||||||
- **Workers**: Specialized agents (init, develop, debug, validate, complete)
|
|
||||||
- **Modes**: Interactive / Auto / Parallel execution
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Coordinator (Main Agent)
|
|
||||||
|
|
|
||||||
+-- Spawns Workers
|
|
||||||
| - ccw-loop-b-init.md
|
|
||||||
| - ccw-loop-b-develop.md
|
|
||||||
| - ccw-loop-b-debug.md
|
|
||||||
| - ccw-loop-b-validate.md
|
|
||||||
| - ccw-loop-b-complete.md
|
|
||||||
|
|
|
||||||
+-- Batch Wait (parallel mode)
|
|
||||||
+-- Sequential Wait (auto/interactive)
|
|
||||||
+-- State Management
|
|
||||||
+-- User Interaction
|
|
||||||
```
|
|
||||||
|
|
||||||
## Subagent API
|
|
||||||
|
|
||||||
Core APIs for worker orchestration:
|
|
||||||
|
|
||||||
| API | 作用 |
|
|
||||||
|-----|------|
|
|
||||||
| `spawn_agent({ message })` | 创建 worker,返回 `agent_id` |
|
|
||||||
| `wait({ ids, timeout_ms })` | 等待结果(唯一取结果入口) |
|
|
||||||
| `send_input({ id, message })` | 继续交互 |
|
|
||||||
| `close_agent({ id })` | 关闭回收 |
|
|
||||||
|
|
||||||
**可用模式**: 单 agent 深度交互 / 多 agent 并行 / 混合模式
|
|
||||||
|
|
||||||
## Execution Modes
|
|
||||||
|
|
||||||
### Interactive Mode (default)
|
|
||||||
|
|
||||||
Coordinator displays menu, user selects action, spawns corresponding worker.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b TASK="Implement feature X"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flow**:
|
|
||||||
1. Init: Parse task, create breakdown
|
|
||||||
2. Menu: Show options to user
|
|
||||||
3. User selects action (develop/debug/validate)
|
|
||||||
4. Spawn worker for selected action
|
|
||||||
5. Wait for result
|
|
||||||
6. Display result, back to menu
|
|
||||||
7. Repeat until complete
|
|
||||||
|
|
||||||
### Auto Mode
|
|
||||||
|
|
||||||
Automated sequential execution following predefined workflow.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b --mode=auto TASK="Fix bug Y"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flow**:
|
|
||||||
1. Init → 2. Develop → 3. Validate → 4. Complete
|
|
||||||
|
|
||||||
If issues found: loop back to Debug → Develop → Validate
|
|
||||||
|
|
||||||
### Parallel Mode
|
|
||||||
|
|
||||||
Spawn multiple workers simultaneously, batch wait for results.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b --mode=parallel TASK="Analyze module Z"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flow**:
|
|
||||||
1. Init: Create analysis plan
|
|
||||||
2. Spawn workers in parallel: [develop, debug, validate]
|
|
||||||
3. Batch wait: `wait({ ids: [w1, w2, w3] })`
|
|
||||||
4. Merge results
|
|
||||||
5. Coordinator decides next action
|
|
||||||
6. Complete
|
|
||||||
|
|
||||||
## Session Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
{projectRoot}/.workflow/.loop/
|
|
||||||
+-- {loopId}.json # Master state
|
|
||||||
+-- {loopId}.workers/ # Worker outputs
|
|
||||||
| +-- init.output.json
|
|
||||||
| +-- develop.output.json
|
|
||||||
| +-- debug.output.json
|
|
||||||
| +-- validate.output.json
|
|
||||||
| +-- complete.output.json
|
|
||||||
+-- {loopId}.progress/ # Human-readable logs
|
|
||||||
+-- develop.md
|
|
||||||
+-- debug.md
|
|
||||||
+-- validate.md
|
|
||||||
+-- summary.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Worker Responsibilities
|
|
||||||
|
|
||||||
| Worker | Role | Specialization |
|
|
||||||
|--------|------|----------------|
|
|
||||||
| **init** | Session initialization | Task parsing, breakdown, planning |
|
|
||||||
| **develop** | Code implementation | File operations, pattern matching, incremental development |
|
|
||||||
| **debug** | Problem diagnosis | Root cause analysis, hypothesis testing, fix recommendations |
|
|
||||||
| **validate** | Testing & verification | Test execution, coverage analysis, quality gates |
|
|
||||||
| **complete** | Session finalization | Summary generation, commit preparation, cleanup |
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Example 1: Simple Feature Implementation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b TASK="Add user logout function"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Auto flow**:
|
|
||||||
- Init: Parse requirements
|
|
||||||
- Develop: Implement logout in `src/auth.ts`
|
|
||||||
- Validate: Run tests
|
|
||||||
- Complete: Generate commit message
|
|
||||||
|
|
||||||
### Example 2: Bug Investigation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b TASK="Fix memory leak in WebSocket handler"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Interactive flow**:
|
|
||||||
1. Init: Parse issue
|
|
||||||
2. User selects "debug" → Spawn debug worker
|
|
||||||
3. Debug: Root cause analysis → recommends fix
|
|
||||||
4. User selects "develop" → Apply fix
|
|
||||||
5. User selects "validate" → Verify fix works
|
|
||||||
6. User selects "complete" → Generate summary
|
|
||||||
|
|
||||||
### Example 3: Comprehensive Analysis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b --mode=parallel TASK="Analyze payment module for improvements"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parallel flow**:
|
|
||||||
- Spawn [develop, debug, validate] workers simultaneously
|
|
||||||
- Develop: Analyze code quality and patterns
|
|
||||||
- Debug: Identify potential issues
|
|
||||||
- Validate: Check test coverage
|
|
||||||
- Wait for all three to complete
|
|
||||||
- Merge findings into comprehensive report
|
|
||||||
|
|
||||||
### Example 4: Resume Existing Loop
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/ccw-loop-b --loop-id=loop-b-20260122-abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
Continues from previous state, respects status (running/paused).
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### 1. Worker Specialization
|
|
||||||
|
|
||||||
Each worker focuses on one domain:
|
|
||||||
- **No overlap**: Clear boundaries between workers
|
|
||||||
- **Reusable**: Same worker for different tasks
|
|
||||||
- **Composable**: Combine workers for complex workflows
|
|
||||||
|
|
||||||
### 2. Flexible Coordination
|
|
||||||
|
|
||||||
Coordinator adapts to mode:
|
|
||||||
- **Interactive**: Menu-driven, user controls flow
|
|
||||||
- **Auto**: Predetermined sequence
|
|
||||||
- **Parallel**: Concurrent execution with batch wait
|
|
||||||
|
|
||||||
### 3. State Management
|
|
||||||
|
|
||||||
Unified state at `{projectRoot}/.workflow/.loop/{loopId}.json`:
|
|
||||||
- **API compatible**: Works with CCW API
|
|
||||||
- **Extension fields**: Skill-specific data in `skill_state`
|
|
||||||
- **Worker outputs**: Structured JSON for each action
|
|
||||||
|
|
||||||
### 4. Progress Tracking
|
|
||||||
|
|
||||||
Human-readable logs:
|
|
||||||
- **Per-worker progress**: `{action}.md` files
|
|
||||||
- **Summary**: Consolidated achievements
|
|
||||||
- **Commit-ready**: Formatted commit messages
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Start with Init**: Always initialize before execution
|
|
||||||
2. **Use appropriate mode**:
|
|
||||||
- Interactive: Complex tasks needing user decisions
|
|
||||||
- Auto: Well-defined workflows
|
|
||||||
- Parallel: Independent analysis tasks
|
|
||||||
3. **Clean up workers**: `close_agent()` after each worker completes
|
|
||||||
4. **Batch wait wisely**: Use in parallel mode for efficiency
|
|
||||||
5. **Track progress**: Document in progress files
|
|
||||||
6. **Validate often**: After each develop phase
|
|
||||||
|
|
||||||
## Implementation Patterns
|
|
||||||
|
|
||||||
### Pattern 1: Single Worker Deep Interaction
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const workerId = spawn_agent({ message: workerPrompt })
|
|
||||||
const result1 = wait({ ids: [workerId] })
|
|
||||||
|
|
||||||
// Continue with same worker
|
|
||||||
send_input({ id: workerId, message: "Continue with next task" })
|
|
||||||
const result2 = wait({ ids: [workerId] })
|
|
||||||
|
|
||||||
close_agent({ id: workerId })
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 2: Multi-Worker Parallel
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const workers = {
|
|
||||||
develop: spawn_agent({ message: developPrompt }),
|
|
||||||
debug: spawn_agent({ message: debugPrompt }),
|
|
||||||
validate: spawn_agent({ message: validatePrompt })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch wait
|
|
||||||
const results = wait({ ids: Object.values(workers), timeout_ms: 900000 })
|
|
||||||
|
|
||||||
// Process all results
|
|
||||||
Object.values(workers).forEach(id => close_agent({ id }))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern 3: Sequential Worker Chain
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const actions = ['init', 'develop', 'validate', 'complete']
|
|
||||||
|
|
||||||
for (const action of actions) {
|
|
||||||
const workerId = spawn_agent({ message: buildPrompt(action) })
|
|
||||||
const result = wait({ ids: [workerId] })
|
|
||||||
|
|
||||||
updateState(action, result)
|
|
||||||
close_agent({ id: workerId })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Worker timeout | `send_input` request convergence |
|
|
||||||
| Worker fails | Log error, coordinator decides retry strategy |
|
|
||||||
| Partial results | Use completed workers, mark incomplete |
|
|
||||||
| State corruption | Rebuild from progress files |
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.codex/skills/ccw-loop-b/
|
|
||||||
+-- SKILL.md # Entry point
|
|
||||||
+-- README.md # This file
|
|
||||||
+-- phases/
|
|
||||||
| +-- state-schema.md # State structure definition
|
|
||||||
+-- specs/
|
|
||||||
+-- action-catalog.md # Action reference
|
|
||||||
|
|
||||||
.codex/agents/
|
|
||||||
+-- ccw-loop-b-init.md # Worker: Init
|
|
||||||
+-- ccw-loop-b-develop.md # Worker: Develop
|
|
||||||
+-- ccw-loop-b-debug.md # Worker: Debug
|
|
||||||
+-- ccw-loop-b-validate.md # Worker: Validate
|
|
||||||
+-- ccw-loop-b-complete.md # Worker: Complete
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comparison: ccw-loop vs ccw-loop-b
|
|
||||||
|
|
||||||
| Aspect | ccw-loop | ccw-loop-b |
|
|
||||||
|--------|----------|------------|
|
|
||||||
| Pattern | Single agent, multi-phase | Coordinator + workers |
|
|
||||||
| Worker model | Single agent handles all | Specialized workers per action |
|
|
||||||
| Parallelization | Sequential only | Supports parallel mode |
|
|
||||||
| Flexibility | Fixed sequence | Mode-based (interactive/auto/parallel) |
|
|
||||||
| Best for | Simple linear workflows | Complex tasks needing specialization |
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
To add new workers:
|
|
||||||
1. Create worker role file in `.codex/agents/`
|
|
||||||
2. Define clear responsibilities
|
|
||||||
3. Update `action-catalog.md`
|
|
||||||
4. Add worker to coordinator spawn logic
|
|
||||||
5. Test integration with existing workers
|
|
||||||
@@ -1,429 +0,0 @@
|
|||||||
---
|
|
||||||
name: ccw-loop-b
|
|
||||||
description: Hybrid orchestrator pattern for iterative development. Coordinator + specialized workers with batch wait, parallel split, and two-phase clarification. Triggers on "ccw-loop-b".
|
|
||||||
allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep
|
|
||||||
---
|
|
||||||
|
|
||||||
# CCW Loop-B - Hybrid Orchestrator Pattern
|
|
||||||
|
|
||||||
协调器 + 专用 worker 的迭代开发工作流。支持三种执行模式(Interactive / Auto / Parallel),每个 action 由独立 worker agent 执行,协调器负责调度、状态管理和结果汇聚。
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
+------------------------------------------------------------+
|
|
||||||
| Main Coordinator |
|
|
||||||
| 职责: 状态管理 + worker 调度 + 结果汇聚 + 用户交互 |
|
|
||||||
+------------------------------------------------------------+
|
|
||||||
| | |
|
|
||||||
v v v
|
|
||||||
+----------------+ +----------------+ +----------------+
|
|
||||||
| Worker-Develop | | Worker-Debug | | Worker-Validate|
|
|
||||||
| 专注: 代码实现 | | 专注: 问题诊断 | | 专注: 测试验证 |
|
|
||||||
+----------------+ +----------------+ +----------------+
|
|
||||||
| | |
|
|
||||||
v v v
|
|
||||||
.workers/ .workers/ .workers/
|
|
||||||
develop.output.json debug.output.json validate.output.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Subagent API
|
|
||||||
|
|
||||||
| API | 作用 | 注意事项 |
|
|
||||||
|-----|------|----------|
|
|
||||||
| `spawn_agent({ message })` | 创建 worker,返回 `agent_id` | 首条 message 加载角色 |
|
|
||||||
| `wait({ ids, timeout_ms })` | 等待结果 | **唯一取结果入口**,非 close |
|
|
||||||
| `send_input({ id, message })` | 继续交互/追问 | `interrupt=true` 慎用 |
|
|
||||||
| `close_agent({ id })` | 关闭回收 | 不可逆,确认不再交互后才关闭 |
|
|
||||||
|
|
||||||
## Key Design Principles
|
|
||||||
|
|
||||||
1. **协调器保持轻量**: 只做调度和状态管理,具体工作交给 worker
|
|
||||||
2. **Worker 职责单一**: 每个 worker 专注一个领域(develop/debug/validate)
|
|
||||||
3. **角色路径传递**: Worker 自己读取角色文件,主流程不传递内容
|
|
||||||
4. **延迟 close_agent**: 确认不再需要交互后才关闭 worker
|
|
||||||
5. **两阶段工作流**: 复杂任务先澄清后执行,减少返工
|
|
||||||
6. **批量等待优化**: 并行模式用 `wait({ ids: [...] })` 批量等待
|
|
||||||
7. **结果标准化**: Worker 输出遵循统一 WORKER_RESULT 格式
|
|
||||||
8. **灵活模式切换**: 根据任务复杂度选择 interactive/auto/parallel
|
|
||||||
|
|
||||||
## Arguments
|
|
||||||
|
|
||||||
| Arg | Required | Description |
|
|
||||||
|-----|----------|-------------|
|
|
||||||
| TASK | One of TASK or --loop-id | Task description (for new loop) |
|
|
||||||
| --loop-id | One of TASK or --loop-id | Existing loop ID to continue |
|
|
||||||
| --mode | No | `interactive` (default) / `auto` / `parallel` |
|
|
||||||
|
|
||||||
## Execution Modes
|
|
||||||
|
|
||||||
### Mode: Interactive (default)
|
|
||||||
|
|
||||||
协调器展示菜单,用户选择 action,spawn 对应 worker 执行。
|
|
||||||
|
|
||||||
```
|
|
||||||
Coordinator -> Show menu -> User selects -> spawn worker -> wait -> Display result -> Loop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode: Auto
|
|
||||||
|
|
||||||
自动按预设顺序执行,worker 完成后协调器决定下一步。
|
|
||||||
|
|
||||||
```
|
|
||||||
Init -> Develop -> [if issues] Debug -> Validate -> [if fail] Loop back -> Complete
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode: Parallel
|
|
||||||
|
|
||||||
并行 spawn 多个 worker,batch wait 汇聚结果,协调器综合决策。
|
|
||||||
|
|
||||||
```
|
|
||||||
Coordinator -> spawn [develop, debug, validate] in parallel -> wait({ ids: all }) -> Merge -> Decide
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Input Parsing:
|
|
||||||
└─ Parse arguments (TASK | --loop-id + --mode)
|
|
||||||
└─ Convert to structured context (loopId, state, mode)
|
|
||||||
|
|
||||||
Phase 1: Session Initialization
|
|
||||||
└─ Ref: phases/01-session-init.md
|
|
||||||
├─ Create new loop OR resume existing loop
|
|
||||||
├─ Initialize state file and directory structure
|
|
||||||
└─ Output: loopId, state, progressDir, mode
|
|
||||||
|
|
||||||
Phase 2: Orchestration Loop
|
|
||||||
└─ Ref: phases/02-orchestration-loop.md
|
|
||||||
├─ Mode dispatch: interactive / auto / parallel
|
|
||||||
├─ Worker spawn with structured prompt (Goal/Scope/Context/Deliverables)
|
|
||||||
├─ Wait + timeout handling + result parsing
|
|
||||||
├─ State update per iteration
|
|
||||||
└─ close_agent on loop exit
|
|
||||||
```
|
|
||||||
|
|
||||||
**Phase Reference Documents** (read on-demand when phase executes):
|
|
||||||
|
|
||||||
| Phase | Document | Purpose |
|
|
||||||
|-------|----------|---------|
|
|
||||||
| 1 | [phases/01-session-init.md](phases/01-session-init.md) | Argument parsing, state creation/resume, directory init |
|
|
||||||
| 2 | [phases/02-orchestration-loop.md](phases/02-orchestration-loop.md) | 3-mode orchestration, worker spawn, batch wait, result merge |
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
User Input (TASK | --loop-id + --mode)
|
|
||||||
↓
|
|
||||||
[Parse Arguments]
|
|
||||||
↓ loopId, state, mode
|
|
||||||
|
|
||||||
Phase 1: Session Initialization
|
|
||||||
↓ loopId, state (initialized/resumed), progressDir
|
|
||||||
|
|
||||||
Phase 2: Orchestration Loop
|
|
||||||
↓
|
|
||||||
┌─── Interactive Mode ──────────────────────────────────┐
|
|
||||||
│ showMenu → user selects → spawn worker → wait → │
|
|
||||||
│ parseResult → updateState → close worker → loop │
|
|
||||||
└───────────────────────────────────────────────────────┘
|
|
||||||
┌─── Auto Mode ─────────────────────────────────────────┐
|
|
||||||
│ selectNext → spawn worker → wait → parseResult → │
|
|
||||||
│ updateState → close worker → [loop_back?] → next │
|
|
||||||
└───────────────────────────────────────────────────────┘
|
|
||||||
┌─── Parallel Mode ─────────────────────────────────────┐
|
|
||||||
│ spawn [develop, debug, validate] → batch wait → │
|
|
||||||
│ mergeOutputs → coordinator decides → close all │
|
|
||||||
└───────────────────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
return finalState
|
|
||||||
```
|
|
||||||
|
|
||||||
## Session Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
{projectRoot}/.workflow/.loop/
|
|
||||||
├── {loopId}.json # Master state (API + Skill shared)
|
|
||||||
├── {loopId}.workers/ # Worker structured outputs
|
|
||||||
│ ├── init.output.json
|
|
||||||
│ ├── develop.output.json
|
|
||||||
│ ├── debug.output.json
|
|
||||||
│ ├── validate.output.json
|
|
||||||
│ └── complete.output.json
|
|
||||||
└── {loopId}.progress/ # Human-readable progress
|
|
||||||
├── develop.md
|
|
||||||
├── debug.md
|
|
||||||
├── validate.md
|
|
||||||
└── summary.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Management
|
|
||||||
|
|
||||||
Master state file: `{projectRoot}/.workflow/.loop/{loopId}.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"loop_id": "loop-b-20260122-abc123",
|
|
||||||
"title": "Task title",
|
|
||||||
"description": "Full task description",
|
|
||||||
"mode": "interactive | auto | parallel",
|
|
||||||
"status": "running | paused | completed | failed",
|
|
||||||
"current_iteration": 0,
|
|
||||||
"max_iterations": 10,
|
|
||||||
"created_at": "ISO8601",
|
|
||||||
"updated_at": "ISO8601",
|
|
||||||
|
|
||||||
"skill_state": {
|
|
||||||
"phase": "init | develop | debug | validate | complete",
|
|
||||||
"action_index": 0,
|
|
||||||
"workers_completed": [],
|
|
||||||
"parallel_results": null,
|
|
||||||
"pending_tasks": [],
|
|
||||||
"completed_tasks": [],
|
|
||||||
"findings": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Control Signal Checking**: 协调器在每次 spawn worker 前检查 `state.status`:
|
|
||||||
- `running` → continue
|
|
||||||
- `paused` → exit gracefully, wait for resume
|
|
||||||
- `failed` → terminate
|
|
||||||
|
|
||||||
**Recovery**: If state corrupted, rebuild from `.progress/` markdown files and `.workers/*.output.json`.
|
|
||||||
|
|
||||||
## Worker Catalog
|
|
||||||
|
|
||||||
| Worker | Role File | Purpose | Output Files |
|
|
||||||
|--------|-----------|---------|--------------|
|
|
||||||
| [init](workers/worker-init.md) | ccw-loop-b-init.md | 会话初始化、任务解析 | init.output.json |
|
|
||||||
| [develop](workers/worker-develop.md) | ccw-loop-b-develop.md | 代码实现、重构 | develop.output.json, develop.md |
|
|
||||||
| [debug](workers/worker-debug.md) | ccw-loop-b-debug.md | 问题诊断、假设验证 | debug.output.json, debug.md |
|
|
||||||
| [validate](workers/worker-validate.md) | ccw-loop-b-validate.md | 测试执行、覆盖率 | validate.output.json, validate.md |
|
|
||||||
| [complete](workers/worker-complete.md) | ccw-loop-b-complete.md | 总结收尾 | complete.output.json, summary.md |
|
|
||||||
|
|
||||||
### Worker Dependencies
|
|
||||||
|
|
||||||
| Worker | Depends On | Leads To |
|
|
||||||
|--------|------------|----------|
|
|
||||||
| init | - | develop (auto) / menu (interactive) |
|
|
||||||
| develop | init | validate / debug |
|
|
||||||
| debug | init | develop / validate |
|
|
||||||
| validate | develop or debug | complete / develop (if fail) |
|
|
||||||
| complete | - | Terminal |
|
|
||||||
|
|
||||||
### Worker Sequences
|
|
||||||
|
|
||||||
```
|
|
||||||
Simple Task (Auto): init → develop → validate → complete
|
|
||||||
Complex Task (Auto): init → develop → validate (fail) → debug → develop → validate → complete
|
|
||||||
Bug Fix (Auto): init → debug → develop → validate → complete
|
|
||||||
Analysis (Parallel): init → [develop ‖ debug ‖ validate] → complete
|
|
||||||
Interactive: init → menu → user selects → worker → menu → ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Worker Prompt Protocol
|
|
||||||
|
|
||||||
### Spawn Message Structure (§7.1)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function buildWorkerPrompt(action, loopId, state) {
|
|
||||||
return `
|
|
||||||
## TASK ASSIGNMENT
|
|
||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
|
||||||
1. **Read role definition**: ~/.codex/agents/ccw-loop-b-${action}.md (MUST read first)
|
|
||||||
2. Read: ${projectRoot}/.workflow/project-tech.json
|
|
||||||
3. Read: ${projectRoot}/.workflow/project-guidelines.json
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Goal: ${goalForAction(action, state)}
|
|
||||||
|
|
||||||
Scope:
|
|
||||||
- 可做: ${allowedScope(action)}
|
|
||||||
- 不可做: ${forbiddenScope(action)}
|
|
||||||
- 目录限制: ${directoryScope(action, state)}
|
|
||||||
|
|
||||||
Context:
|
|
||||||
- Loop ID: ${loopId}
|
|
||||||
- State: ${projectRoot}/.workflow/.loop/${loopId}.json
|
|
||||||
- Output: ${projectRoot}/.workflow/.loop/${loopId}.workers/${action}.output.json
|
|
||||||
- Progress: ${projectRoot}/.workflow/.loop/${loopId}.progress/${action}.md
|
|
||||||
|
|
||||||
Deliverables:
|
|
||||||
- 按 WORKER_RESULT 格式输出
|
|
||||||
- 写入 output.json 和 progress.md
|
|
||||||
|
|
||||||
Quality bar:
|
|
||||||
- ${qualityCriteria(action)}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**关键**: 角色文件由 worker 自己读取,主流程只传递路径。不嵌入角色内容。
|
|
||||||
|
|
||||||
### Worker Output Format (WORKER_RESULT)
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: {action_name}
|
|
||||||
- status: success | failed | needs_input
|
|
||||||
- summary: <brief summary>
|
|
||||||
- files_changed: [list]
|
|
||||||
- next_suggestion: <suggested next action>
|
|
||||||
- loop_back_to: <action name if needs loop back, or null>
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
<action-specific structured output>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Two-Phase Clarification (§5.2)
|
|
||||||
|
|
||||||
Worker 遇到模糊需求时采用两阶段模式:
|
|
||||||
|
|
||||||
```
|
|
||||||
阶段 1: Worker 输出 CLARIFICATION_NEEDED + Open questions
|
|
||||||
阶段 2: 协调器收集用户回答 → send_input → Worker 继续执行
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 解析 worker 是否需要澄清
|
|
||||||
if (output.includes('CLARIFICATION_NEEDED')) {
|
|
||||||
const userAnswers = await collectUserAnswers(output)
|
|
||||||
send_input({
|
|
||||||
id: workerId,
|
|
||||||
message: `## CLARIFICATION ANSWERS\n${userAnswers}\n\n## CONTINUE EXECUTION`
|
|
||||||
})
|
|
||||||
const finalResult = wait({ ids: [workerId], timeout_ms: 600000 })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Parallel Split Strategy (§6)
|
|
||||||
|
|
||||||
### Strategy 1: 按职责域拆分(推荐)
|
|
||||||
|
|
||||||
| Worker | 职责 | 交付物 | 禁止事项 |
|
|
||||||
|--------|------|--------|----------|
|
|
||||||
| develop | 定位入口、调用链、实现方案 | 变更点清单 | 不做测试 |
|
|
||||||
| debug | 问题诊断、风险评估 | 问题清单+修复建议 | 不写代码 |
|
|
||||||
| validate | 测试策略、覆盖分析 | 测试结果+质量报告 | 不改实现 |
|
|
||||||
|
|
||||||
### Strategy 2: 按模块域拆分
|
|
||||||
|
|
||||||
```
|
|
||||||
Worker 1: src/auth/** → 认证模块变更
|
|
||||||
Worker 2: src/api/** → API 层变更
|
|
||||||
Worker 3: src/database/** → 数据层变更
|
|
||||||
```
|
|
||||||
|
|
||||||
### 拆分原则
|
|
||||||
|
|
||||||
1. **文件隔离**: 避免多个 worker 同时修改同一文件
|
|
||||||
2. **职责单一**: 每个 worker 只做一件事
|
|
||||||
3. **边界清晰**: 超出范围用 `CLARIFICATION_NEEDED` 请求确认
|
|
||||||
4. **最小上下文**: 只传递完成任务所需的最小信息
|
|
||||||
|
|
||||||
## Result Merge (Parallel Mode)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function mergeWorkerOutputs(outputs) {
|
|
||||||
return {
|
|
||||||
develop: parseWorkerResult(outputs.develop),
|
|
||||||
debug: parseWorkerResult(outputs.debug),
|
|
||||||
validate: parseWorkerResult(outputs.validate),
|
|
||||||
conflicts: detectConflicts(outputs), // 检查 worker 间建议冲突
|
|
||||||
merged_at: getUtc8ISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**冲突检测**: 当多个 worker 建议修改同一文件时,协调器标记冲突,由用户决定。
|
|
||||||
|
|
||||||
## TodoWrite Pattern
|
|
||||||
|
|
||||||
### Phase-Level Tracking (Attached)
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
|
||||||
{"content": "Phase 2: Orchestration Loop (auto mode)", "status": "in_progress"},
|
|
||||||
{"content": " → Worker: init", "status": "completed"},
|
|
||||||
{"content": " → Worker: develop (task 2/5)", "status": "in_progress"},
|
|
||||||
{"content": " → Worker: validate", "status": "pending"},
|
|
||||||
{"content": " → Worker: complete", "status": "pending"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Mode Tracking
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
|
||||||
{"content": "Phase 2: Parallel Analysis", "status": "in_progress"},
|
|
||||||
{"content": " → Worker: develop (parallel)", "status": "in_progress"},
|
|
||||||
{"content": " → Worker: debug (parallel)", "status": "in_progress"},
|
|
||||||
{"content": " → Worker: validate (parallel)", "status": "in_progress"},
|
|
||||||
{"content": " → Merge results", "status": "pending"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Rules
|
|
||||||
|
|
||||||
1. **Start Immediately**: First action is TodoWrite initialization, then Phase 1 execution
|
|
||||||
2. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute
|
|
||||||
3. **Parse Every Output**: Extract WORKER_RESULT from worker output for next decision
|
|
||||||
4. **Worker 生命周期**: spawn → wait → [send_input if needed] → close,不长期保留 worker
|
|
||||||
5. **结果持久化**: Worker 输出写入 `{projectRoot}/.workflow/.loop/{loopId}.workers/`
|
|
||||||
6. **状态同步**: 每次 worker 完成后更新 master state
|
|
||||||
7. **超时处理**: send_input 请求收敛,再超时则使用已有结果继续
|
|
||||||
8. **DO NOT STOP**: Continuous execution until completed, paused, or max iterations
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Worker timeout | send_input 请求收敛 → 再超时则跳过 |
|
|
||||||
| Worker failed | Log error, 协调器决策是否重试 |
|
|
||||||
| Batch wait partial timeout | 使用已完成结果继续 |
|
|
||||||
| State corrupted | 从 progress 文件和 worker output 重建 |
|
|
||||||
| Conflicting worker results | 标记冲突,由用户决定 |
|
|
||||||
| Max iterations reached | 生成总结,记录未完成项 |
|
|
||||||
|
|
||||||
## Coordinator Checklist
|
|
||||||
|
|
||||||
### Before Each Phase
|
|
||||||
|
|
||||||
- [ ] Read phase reference document
|
|
||||||
- [ ] Check current state and control signals
|
|
||||||
- [ ] Update TodoWrite with phase tasks
|
|
||||||
|
|
||||||
### After Each Worker
|
|
||||||
|
|
||||||
- [ ] Parse WORKER_RESULT from output
|
|
||||||
- [ ] Persist output to `.workers/{action}.output.json`
|
|
||||||
- [ ] Update master state file
|
|
||||||
- [ ] close_agent (确认不再需要交互)
|
|
||||||
- [ ] Determine next action (continue / loop back / complete)
|
|
||||||
|
|
||||||
## Reference Documents
|
|
||||||
|
|
||||||
| Document | Purpose |
|
|
||||||
|----------|---------|
|
|
||||||
| [workers/](workers/) | Worker 定义 (init, develop, debug, validate, complete) |
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Interactive mode (default)
|
|
||||||
/ccw-loop-b TASK="Implement user authentication"
|
|
||||||
|
|
||||||
# Auto mode
|
|
||||||
/ccw-loop-b --mode=auto TASK="Fix login bug"
|
|
||||||
|
|
||||||
# Parallel analysis mode
|
|
||||||
/ccw-loop-b --mode=parallel TASK="Analyze and improve payment module"
|
|
||||||
|
|
||||||
# Resume existing loop
|
|
||||||
/ccw-loop-b --loop-id=loop-b-20260122-abc123
|
|
||||||
```
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
# Phase 1: Session Initialization
|
|
||||||
|
|
||||||
Create or resume a development loop, initialize state file and directory structure, detect execution mode.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Parse user arguments (TASK, --loop-id, --mode)
|
|
||||||
- Create new loop with unique ID OR resume existing loop
|
|
||||||
- Initialize directory structure (progress + workers)
|
|
||||||
- Create master state file
|
|
||||||
- Output: loopId, state, progressDir, mode
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 0: Determine Project Root
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Step 0: Determine Project Root
|
|
||||||
const projectRoot = Bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.1: Parse Arguments
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const { loopId: existingLoopId, task, mode = 'interactive' } = options
|
|
||||||
|
|
||||||
// Validate mutual exclusivity
|
|
||||||
if (!existingLoopId && !task) {
|
|
||||||
console.error('Either --loop-id or task description is required')
|
|
||||||
return { status: 'error', message: 'Missing loopId or task' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate mode
|
|
||||||
const validModes = ['interactive', 'auto', 'parallel']
|
|
||||||
if (!validModes.includes(mode)) {
|
|
||||||
console.error(`Invalid mode: ${mode}. Use: ${validModes.join(', ')}`)
|
|
||||||
return { status: 'error', message: 'Invalid mode' }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.2: Utility Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
|
||||||
|
|
||||||
function readState(loopId) {
|
|
||||||
const stateFile = `${projectRoot}/.workflow/.loop/${loopId}.json`
|
|
||||||
if (!fs.existsSync(stateFile)) return null
|
|
||||||
return JSON.parse(Read(stateFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveState(loopId, state) {
|
|
||||||
state.updated_at = getUtc8ISOString()
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.3: New Loop Creation
|
|
||||||
|
|
||||||
When `TASK` is provided (no `--loop-id`):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
|
||||||
const random = Math.random().toString(36).substring(2, 10)
|
|
||||||
const loopId = `loop-b-${timestamp}-${random}`
|
|
||||||
|
|
||||||
console.log(`Creating new loop: ${loopId}`)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Create Directory Structure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ${projectRoot}/.workflow/.loop/${loopId}.workers
|
|
||||||
mkdir -p ${projectRoot}/.workflow/.loop/${loopId}.progress
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Initialize State File
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function createState(loopId, taskDescription, mode) {
|
|
||||||
const now = getUtc8ISOString()
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
loop_id: loopId,
|
|
||||||
title: taskDescription.substring(0, 100),
|
|
||||||
description: taskDescription,
|
|
||||||
mode: mode,
|
|
||||||
status: 'running',
|
|
||||||
current_iteration: 0,
|
|
||||||
max_iterations: 10,
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
|
|
||||||
skill_state: {
|
|
||||||
phase: 'init',
|
|
||||||
action_index: 0,
|
|
||||||
workers_completed: [],
|
|
||||||
parallel_results: null,
|
|
||||||
pending_tasks: [],
|
|
||||||
completed_tasks: [],
|
|
||||||
findings: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.4: Resume Existing Loop
|
|
||||||
|
|
||||||
When `--loop-id` is provided:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loopId = existingLoopId
|
|
||||||
const state = readState(loopId)
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
console.error(`Loop not found: ${loopId}`)
|
|
||||||
return { status: 'error', message: 'Loop not found' }
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Resuming loop: ${loopId}`)
|
|
||||||
console.log(`Mode: ${state.mode}, Status: ${state.status}`)
|
|
||||||
|
|
||||||
// Override mode if provided
|
|
||||||
if (options['--mode']) {
|
|
||||||
state.mode = options['--mode']
|
|
||||||
saveState(loopId, state)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.5: Control Signal Check
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function checkControlSignals(loopId) {
|
|
||||||
const state = readState(loopId)
|
|
||||||
|
|
||||||
switch (state?.status) {
|
|
||||||
case 'paused':
|
|
||||||
return { continue: false, action: 'pause_exit' }
|
|
||||||
case 'failed':
|
|
||||||
return { continue: false, action: 'stop_exit' }
|
|
||||||
case 'running':
|
|
||||||
return { continue: true, action: 'continue' }
|
|
||||||
default:
|
|
||||||
return { continue: false, action: 'stop_exit' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Variable**: `loopId` - Unique loop identifier
|
|
||||||
- **Variable**: `state` - Initialized or resumed loop state object
|
|
||||||
- **Variable**: `progressDir` - `${projectRoot}/.workflow/.loop/${loopId}.progress`
|
|
||||||
- **Variable**: `workersDir` - `${projectRoot}/.workflow/.loop/${loopId}.workers`
|
|
||||||
- **Variable**: `mode` - `'interactive'` / `'auto'` / `'parallel'`
|
|
||||||
- **TodoWrite**: Mark Phase 1 completed, Phase 2 in_progress
|
|
||||||
|
|
||||||
## Next Phase
|
|
||||||
|
|
||||||
Return to orchestrator, then auto-continue to [Phase 2: Orchestration Loop](02-orchestration-loop.md).
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
# Phase 2: Orchestration Loop
|
|
||||||
|
|
||||||
Run main orchestration loop with 3-mode dispatch: Interactive, Auto, Parallel.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Dispatch to appropriate mode handler based on `state.mode`
|
|
||||||
- Spawn workers with structured prompts (Goal/Scope/Context/Deliverables)
|
|
||||||
- Handle batch wait, timeout, two-phase clarification
|
|
||||||
- Parse WORKER_RESULT, update state per iteration
|
|
||||||
- close_agent after confirming no more interaction needed
|
|
||||||
- Exit on completion, pause, stop, or max iterations
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 2.1: Mode Dispatch
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const mode = state.mode || 'interactive'
|
|
||||||
|
|
||||||
console.log(`=== CCW Loop-B Orchestrator (${mode} mode) ===`)
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case 'interactive':
|
|
||||||
return await runInteractiveMode(loopId, state)
|
|
||||||
|
|
||||||
case 'auto':
|
|
||||||
return await runAutoMode(loopId, state)
|
|
||||||
|
|
||||||
case 'parallel':
|
|
||||||
return await runParallelMode(loopId, state)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.2: Interactive Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runInteractiveMode(loopId, state) {
|
|
||||||
while (state.status === 'running') {
|
|
||||||
// 1. Check control signals
|
|
||||||
const signal = checkControlSignals(loopId)
|
|
||||||
if (!signal.continue) break
|
|
||||||
|
|
||||||
// 2. Show menu, get user choice
|
|
||||||
const action = await showMenuAndGetChoice(state)
|
|
||||||
if (action === 'exit') {
|
|
||||||
state.status = 'user_exit'
|
|
||||||
saveState(loopId, state)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Spawn worker
|
|
||||||
const workerId = spawn_agent({
|
|
||||||
message: buildWorkerPrompt(action, loopId, state)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 4. Wait for result (with two-phase clarification support)
|
|
||||||
let output = await waitWithClarification(workerId, action)
|
|
||||||
|
|
||||||
// 5. Process and persist output
|
|
||||||
const workerResult = parseWorkerResult(output)
|
|
||||||
persistWorkerOutput(loopId, action, workerResult)
|
|
||||||
state = processWorkerOutput(loopId, action, workerResult, state)
|
|
||||||
|
|
||||||
// 6. Cleanup worker
|
|
||||||
close_agent({ id: workerId })
|
|
||||||
|
|
||||||
// 7. Display result
|
|
||||||
displayResult(workerResult)
|
|
||||||
|
|
||||||
// 8. Update iteration
|
|
||||||
state.current_iteration++
|
|
||||||
saveState(loopId, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: state.status, loop_id: loopId, iterations: state.current_iteration }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.3: Auto Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runAutoMode(loopId, state) {
|
|
||||||
const sequence = ['init', 'develop', 'debug', 'validate', 'complete']
|
|
||||||
let idx = state.skill_state?.action_index || 0
|
|
||||||
|
|
||||||
while (idx < sequence.length && state.status === 'running') {
|
|
||||||
// Check control signals
|
|
||||||
const signal = checkControlSignals(loopId)
|
|
||||||
if (!signal.continue) break
|
|
||||||
|
|
||||||
// Check iteration limit
|
|
||||||
if (state.current_iteration >= state.max_iterations) {
|
|
||||||
console.log(`Max iterations (${state.max_iterations}) reached`)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = sequence[idx]
|
|
||||||
|
|
||||||
// Spawn worker
|
|
||||||
const workerId = spawn_agent({
|
|
||||||
message: buildWorkerPrompt(action, loopId, state)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait with two-phase clarification
|
|
||||||
let output = await waitWithClarification(workerId, action)
|
|
||||||
|
|
||||||
// Parse and persist
|
|
||||||
const workerResult = parseWorkerResult(output)
|
|
||||||
persistWorkerOutput(loopId, action, workerResult)
|
|
||||||
state = processWorkerOutput(loopId, action, workerResult, state)
|
|
||||||
|
|
||||||
close_agent({ id: workerId })
|
|
||||||
|
|
||||||
// Determine next step
|
|
||||||
if (workerResult.loop_back_to && workerResult.loop_back_to !== 'null') {
|
|
||||||
idx = sequence.indexOf(workerResult.loop_back_to)
|
|
||||||
if (idx === -1) idx = sequence.indexOf('develop') // fallback
|
|
||||||
} else if (workerResult.status === 'failed') {
|
|
||||||
console.log(`Worker ${action} failed: ${workerResult.summary}`)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
state.skill_state.action_index = idx
|
|
||||||
state.current_iteration++
|
|
||||||
saveState(loopId, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: state.status, loop_id: loopId, iterations: state.current_iteration }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.4: Parallel Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runParallelMode(loopId, state) {
|
|
||||||
// 1. Run init worker first (sequential)
|
|
||||||
const initWorker = spawn_agent({
|
|
||||||
message: buildWorkerPrompt('init', loopId, state)
|
|
||||||
})
|
|
||||||
const initResult = wait({ ids: [initWorker], timeout_ms: 300000 })
|
|
||||||
const initOutput = parseWorkerResult(initResult.status[initWorker].completed)
|
|
||||||
persistWorkerOutput(loopId, 'init', initOutput)
|
|
||||||
state = processWorkerOutput(loopId, 'init', initOutput, state)
|
|
||||||
close_agent({ id: initWorker })
|
|
||||||
|
|
||||||
// 2. Spawn analysis workers in parallel
|
|
||||||
const workers = {
|
|
||||||
develop: spawn_agent({ message: buildWorkerPrompt('develop', loopId, state) }),
|
|
||||||
debug: spawn_agent({ message: buildWorkerPrompt('debug', loopId, state) }),
|
|
||||||
validate: spawn_agent({ message: buildWorkerPrompt('validate', loopId, state) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Batch wait for all workers
|
|
||||||
const results = wait({
|
|
||||||
ids: Object.values(workers),
|
|
||||||
timeout_ms: 900000 // 15 minutes for all
|
|
||||||
})
|
|
||||||
|
|
||||||
// 4. Handle partial timeout
|
|
||||||
if (results.timed_out) {
|
|
||||||
console.log('Partial timeout - using completed results')
|
|
||||||
// Send convergence request to timed-out workers
|
|
||||||
for (const [role, workerId] of Object.entries(workers)) {
|
|
||||||
if (!results.status[workerId]?.completed) {
|
|
||||||
send_input({
|
|
||||||
id: workerId,
|
|
||||||
message: '## TIMEOUT\nPlease output WORKER_RESULT with current progress immediately.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Brief second wait for convergence
|
|
||||||
const retryResults = wait({ ids: Object.values(workers), timeout_ms: 60000 })
|
|
||||||
Object.assign(results.status, retryResults.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Collect and merge outputs
|
|
||||||
const outputs = {}
|
|
||||||
for (const [role, workerId] of Object.entries(workers)) {
|
|
||||||
const completed = results.status[workerId]?.completed
|
|
||||||
if (completed) {
|
|
||||||
outputs[role] = parseWorkerResult(completed)
|
|
||||||
persistWorkerOutput(loopId, role, outputs[role])
|
|
||||||
}
|
|
||||||
close_agent({ id: workerId })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Merge analysis
|
|
||||||
const mergedResults = mergeWorkerOutputs(outputs)
|
|
||||||
state.skill_state.parallel_results = mergedResults
|
|
||||||
state.current_iteration++
|
|
||||||
saveState(loopId, state)
|
|
||||||
|
|
||||||
// 7. Run complete worker
|
|
||||||
const completeWorker = spawn_agent({
|
|
||||||
message: buildWorkerPrompt('complete', loopId, state)
|
|
||||||
})
|
|
||||||
const completeResult = wait({ ids: [completeWorker], timeout_ms: 300000 })
|
|
||||||
const completeOutput = parseWorkerResult(completeResult.status[completeWorker].completed)
|
|
||||||
persistWorkerOutput(loopId, 'complete', completeOutput)
|
|
||||||
state = processWorkerOutput(loopId, 'complete', completeOutput, state)
|
|
||||||
close_agent({ id: completeWorker })
|
|
||||||
|
|
||||||
return { status: state.status, loop_id: loopId, iterations: state.current_iteration }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### buildWorkerPrompt
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function buildWorkerPrompt(action, loopId, state) {
|
|
||||||
const roleFiles = {
|
|
||||||
init: '~/.codex/agents/ccw-loop-b-init.md',
|
|
||||||
develop: '~/.codex/agents/ccw-loop-b-develop.md',
|
|
||||||
debug: '~/.codex/agents/ccw-loop-b-debug.md',
|
|
||||||
validate: '~/.codex/agents/ccw-loop-b-validate.md',
|
|
||||||
complete: '~/.codex/agents/ccw-loop-b-complete.md'
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
## TASK ASSIGNMENT
|
|
||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
|
||||||
1. **Read role definition**: ${roleFiles[action]} (MUST read first)
|
|
||||||
2. Read: ${projectRoot}/.workflow/project-tech.json
|
|
||||||
3. Read: ${projectRoot}/.workflow/project-guidelines.json
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Goal: Execute ${action} action for loop ${loopId}
|
|
||||||
|
|
||||||
Scope:
|
|
||||||
- 可做: ${action} 相关的所有操作
|
|
||||||
- 不可做: 其他 action 的操作
|
|
||||||
- 目录限制: 项目根目录
|
|
||||||
|
|
||||||
Context:
|
|
||||||
- Loop ID: ${loopId}
|
|
||||||
- Action: ${action}
|
|
||||||
- State File: ${projectRoot}/.workflow/.loop/${loopId}.json
|
|
||||||
- Output File: ${projectRoot}/.workflow/.loop/${loopId}.workers/${action}.output.json
|
|
||||||
- Progress File: ${projectRoot}/.workflow/.loop/${loopId}.progress/${action}.md
|
|
||||||
|
|
||||||
Deliverables:
|
|
||||||
- WORKER_RESULT 格式输出
|
|
||||||
- 写入 output.json 和 progress.md
|
|
||||||
|
|
||||||
## CURRENT STATE
|
|
||||||
|
|
||||||
${JSON.stringify(state, null, 2)}
|
|
||||||
|
|
||||||
## TASK DESCRIPTION
|
|
||||||
|
|
||||||
${state.description}
|
|
||||||
|
|
||||||
## EXPECTED OUTPUT
|
|
||||||
|
|
||||||
\`\`\`
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: ${action}
|
|
||||||
- status: success | failed | needs_input
|
|
||||||
- summary: <brief summary>
|
|
||||||
- files_changed: [list]
|
|
||||||
- next_suggestion: <suggested next action>
|
|
||||||
- loop_back_to: <action name if needs loop back, or null>
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
<action-specific structured output>
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Execute the ${action} action now.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### waitWithClarification (Two-Phase Workflow)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function waitWithClarification(workerId, action) {
|
|
||||||
const result = wait({ ids: [workerId], timeout_ms: 600000 })
|
|
||||||
|
|
||||||
// Handle timeout
|
|
||||||
if (result.timed_out) {
|
|
||||||
send_input({
|
|
||||||
id: workerId,
|
|
||||||
message: '## TIMEOUT\nPlease converge and output WORKER_RESULT with current progress.'
|
|
||||||
})
|
|
||||||
const retry = wait({ ids: [workerId], timeout_ms: 300000 })
|
|
||||||
if (retry.timed_out) {
|
|
||||||
return `WORKER_RESULT:\n- action: ${action}\n- status: failed\n- summary: Worker timeout\n\nNEXT_ACTION_NEEDED: NONE`
|
|
||||||
}
|
|
||||||
return retry.status[workerId].completed
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.status[workerId].completed
|
|
||||||
|
|
||||||
// Check if worker needs clarification (two-phase)
|
|
||||||
if (output.includes('CLARIFICATION_NEEDED')) {
|
|
||||||
// Collect user answers
|
|
||||||
const questions = parseClarificationQuestions(output)
|
|
||||||
const userAnswers = await collectUserAnswers(questions)
|
|
||||||
|
|
||||||
// Send answers back to worker
|
|
||||||
send_input({
|
|
||||||
id: workerId,
|
|
||||||
message: `
|
|
||||||
## CLARIFICATION ANSWERS
|
|
||||||
|
|
||||||
${userAnswers.map(a => `Q: ${a.question}\nA: ${a.answer}`).join('\n\n')}
|
|
||||||
|
|
||||||
## CONTINUE EXECUTION
|
|
||||||
|
|
||||||
Based on clarification answers, continue with the ${action} action.
|
|
||||||
Output WORKER_RESULT when complete.
|
|
||||||
`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for final result
|
|
||||||
const finalResult = wait({ ids: [workerId], timeout_ms: 600000 })
|
|
||||||
return finalResult.status[workerId]?.completed || output
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### parseWorkerResult
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parseWorkerResult(output) {
|
|
||||||
const result = {
|
|
||||||
action: 'unknown',
|
|
||||||
status: 'unknown',
|
|
||||||
summary: '',
|
|
||||||
files_changed: [],
|
|
||||||
next_suggestion: null,
|
|
||||||
loop_back_to: null,
|
|
||||||
detailed_output: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse WORKER_RESULT block
|
|
||||||
const match = output.match(/WORKER_RESULT:\s*([\s\S]*?)(?:DETAILED_OUTPUT:|$)/)
|
|
||||||
if (match) {
|
|
||||||
const lines = match[1].split('\n')
|
|
||||||
for (const line of lines) {
|
|
||||||
const m = line.match(/^-\s*(\w[\w_]*):\s*(.+)$/)
|
|
||||||
if (m) {
|
|
||||||
const [, key, value] = m
|
|
||||||
if (key === 'files_changed') {
|
|
||||||
try { result.files_changed = JSON.parse(value) } catch {}
|
|
||||||
} else {
|
|
||||||
result[key] = value.trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse DETAILED_OUTPUT
|
|
||||||
const detailMatch = output.match(/DETAILED_OUTPUT:\s*([\s\S]*)$/)
|
|
||||||
if (detailMatch) {
|
|
||||||
result.detailed_output = detailMatch[1].trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### mergeWorkerOutputs (Parallel Mode)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function mergeWorkerOutputs(outputs) {
|
|
||||||
const merged = {
|
|
||||||
develop: outputs.develop || null,
|
|
||||||
debug: outputs.debug || null,
|
|
||||||
validate: outputs.validate || null,
|
|
||||||
conflicts: [],
|
|
||||||
merged_at: getUtc8ISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect file conflicts: multiple workers suggest modifying same file
|
|
||||||
const allFiles = {}
|
|
||||||
for (const [role, output] of Object.entries(outputs)) {
|
|
||||||
if (output?.files_changed) {
|
|
||||||
for (const file of output.files_changed) {
|
|
||||||
if (allFiles[file]) {
|
|
||||||
merged.conflicts.push({
|
|
||||||
file,
|
|
||||||
workers: [allFiles[file], role],
|
|
||||||
resolution: 'manual'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
allFiles[file] = role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### showMenuAndGetChoice
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function showMenuAndGetChoice(state) {
|
|
||||||
const ss = state.skill_state
|
|
||||||
const pendingCount = ss?.pending_tasks?.length || 0
|
|
||||||
const completedCount = ss?.completed_tasks?.length || 0
|
|
||||||
|
|
||||||
const response = await ASK_USER([{
|
|
||||||
id: "Action", type: "select",
|
|
||||||
prompt: `Select next action (completed: ${completedCount}, pending: ${pendingCount}):`,
|
|
||||||
options: [
|
|
||||||
{ label: "develop", description: `Continue development (${pendingCount} pending)` },
|
|
||||||
{ label: "debug", description: "Start debugging / diagnosis" },
|
|
||||||
{ label: "validate", description: "Run tests and validation" },
|
|
||||||
{ label: "complete", description: "Complete loop and generate summary" },
|
|
||||||
{ label: "exit", description: "Exit and save progress" }
|
|
||||||
]
|
|
||||||
}]) // BLOCKS (wait for user response)
|
|
||||||
|
|
||||||
return response["Action"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### persistWorkerOutput
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function persistWorkerOutput(loopId, action, workerResult) {
|
|
||||||
const outputPath = `${projectRoot}/.workflow/.loop/${loopId}.workers/${action}.output.json`
|
|
||||||
Write(outputPath, JSON.stringify({
|
|
||||||
...workerResult,
|
|
||||||
timestamp: getUtc8ISOString()
|
|
||||||
}, null, 2))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Return**: `{ status, loop_id, iterations }`
|
|
||||||
- **TodoWrite**: Mark Phase 2 completed
|
|
||||||
|
|
||||||
## Next Phase
|
|
||||||
|
|
||||||
None. Phase 2 is the terminal phase of the orchestrator.
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
# Orchestrator (Hybrid Pattern)
|
|
||||||
|
|
||||||
协调器负责状态管理、worker 调度、结果汇聚。
|
|
||||||
|
|
||||||
## Role
|
|
||||||
|
|
||||||
```
|
|
||||||
Read state -> Select mode -> Spawn workers -> Wait results -> Merge -> Update state -> Loop/Exit
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Management
|
|
||||||
|
|
||||||
### Read State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function readState(loopId) {
|
|
||||||
const stateFile = `${projectRoot}/.workflow/.loop/${loopId}.json`
|
|
||||||
return fs.existsSync(stateFile)
|
|
||||||
? JSON.parse(Read(stateFile))
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function createState(loopId, taskDescription, mode) {
|
|
||||||
const now = new Date().toISOString()
|
|
||||||
|
|
||||||
return {
|
|
||||||
loop_id: loopId,
|
|
||||||
title: taskDescription.substring(0, 100),
|
|
||||||
description: taskDescription,
|
|
||||||
mode: mode,
|
|
||||||
status: 'running',
|
|
||||||
current_iteration: 0,
|
|
||||||
max_iterations: 10,
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
skill_state: {
|
|
||||||
phase: 'init',
|
|
||||||
action_index: 0,
|
|
||||||
workers_completed: [],
|
|
||||||
parallel_results: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mode Handlers
|
|
||||||
|
|
||||||
### Interactive Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runInteractiveMode(loopId, state) {
|
|
||||||
while (state.status === 'running') {
|
|
||||||
// 1. Show menu
|
|
||||||
const action = await showMenu(state)
|
|
||||||
if (action === 'exit') break
|
|
||||||
|
|
||||||
// 2. Spawn worker
|
|
||||||
const worker = spawn_agent({
|
|
||||||
message: buildWorkerPrompt(action, loopId, state)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. Wait for result
|
|
||||||
const result = wait({ ids: [worker], timeout_ms: 600000 })
|
|
||||||
|
|
||||||
// 4. Handle timeout
|
|
||||||
if (result.timed_out) {
|
|
||||||
send_input({ id: worker, message: 'Please converge and output WORKER_RESULT' })
|
|
||||||
const retryResult = wait({ ids: [worker], timeout_ms: 300000 })
|
|
||||||
if (retryResult.timed_out) {
|
|
||||||
console.log('Worker timeout, skipping')
|
|
||||||
close_agent({ id: worker })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Process output
|
|
||||||
const output = result.status[worker].completed
|
|
||||||
state = processWorkerOutput(loopId, action, output, state)
|
|
||||||
|
|
||||||
// 6. Cleanup
|
|
||||||
close_agent({ id: worker })
|
|
||||||
|
|
||||||
// 7. Display result
|
|
||||||
displayResult(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runAutoMode(loopId, state) {
|
|
||||||
const sequence = ['init', 'develop', 'debug', 'validate', 'complete']
|
|
||||||
let idx = state.skill_state?.action_index || 0
|
|
||||||
|
|
||||||
while (idx < sequence.length && state.status === 'running') {
|
|
||||||
const action = sequence[idx]
|
|
||||||
|
|
||||||
// Spawn and wait
|
|
||||||
const worker = spawn_agent({ message: buildWorkerPrompt(action, loopId, state) })
|
|
||||||
const result = wait({ ids: [worker], timeout_ms: 600000 })
|
|
||||||
const output = result.status[worker].completed
|
|
||||||
close_agent({ id: worker })
|
|
||||||
|
|
||||||
// Parse result
|
|
||||||
const workerResult = parseWorkerResult(output)
|
|
||||||
state = processWorkerOutput(loopId, action, output, state)
|
|
||||||
|
|
||||||
// Determine next
|
|
||||||
if (workerResult.loop_back_to) {
|
|
||||||
idx = sequence.indexOf(workerResult.loop_back_to)
|
|
||||||
} else if (workerResult.status === 'failed') {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update action index
|
|
||||||
state.skill_state.action_index = idx
|
|
||||||
saveState(loopId, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function runParallelMode(loopId, state) {
|
|
||||||
// Spawn all workers
|
|
||||||
const workers = {
|
|
||||||
develop: spawn_agent({ message: buildWorkerPrompt('develop', loopId, state) }),
|
|
||||||
debug: spawn_agent({ message: buildWorkerPrompt('debug', loopId, state) }),
|
|
||||||
validate: spawn_agent({ message: buildWorkerPrompt('validate', loopId, state) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch wait
|
|
||||||
const results = wait({
|
|
||||||
ids: Object.values(workers),
|
|
||||||
timeout_ms: 900000
|
|
||||||
})
|
|
||||||
|
|
||||||
// Collect outputs
|
|
||||||
const outputs = {}
|
|
||||||
for (const [role, id] of Object.entries(workers)) {
|
|
||||||
if (results.status[id].completed) {
|
|
||||||
outputs[role] = results.status[id].completed
|
|
||||||
}
|
|
||||||
close_agent({ id })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge analysis
|
|
||||||
state.skill_state.parallel_results = outputs
|
|
||||||
saveState(loopId, state)
|
|
||||||
|
|
||||||
// Coordinator analyzes merged results
|
|
||||||
return analyzeAndDecide(outputs)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Worker Prompt Template
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function buildWorkerPrompt(action, loopId, state) {
|
|
||||||
const roleFiles = {
|
|
||||||
init: '~/.codex/agents/ccw-loop-b-init.md',
|
|
||||||
develop: '~/.codex/agents/ccw-loop-b-develop.md',
|
|
||||||
debug: '~/.codex/agents/ccw-loop-b-debug.md',
|
|
||||||
validate: '~/.codex/agents/ccw-loop-b-validate.md',
|
|
||||||
complete: '~/.codex/agents/ccw-loop-b-complete.md'
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
## TASK ASSIGNMENT
|
|
||||||
|
|
||||||
### MANDATORY FIRST STEPS
|
|
||||||
1. **Read role definition**: ${roleFiles[action]}
|
|
||||||
2. Read: ${projectRoot}/.workflow/project-tech.json
|
|
||||||
3. Read: ${projectRoot}/.workflow/project-guidelines.json
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CONTEXT
|
|
||||||
- Loop ID: ${loopId}
|
|
||||||
- Action: ${action}
|
|
||||||
- State: ${JSON.stringify(state, null, 2)}
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
${state.description}
|
|
||||||
|
|
||||||
## OUTPUT FORMAT
|
|
||||||
\`\`\`
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: ${action}
|
|
||||||
- status: success | failed | needs_input
|
|
||||||
- summary: <brief>
|
|
||||||
- files_changed: []
|
|
||||||
- next_suggestion: <action>
|
|
||||||
- loop_back_to: <action or null>
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
<action-specific output>
|
|
||||||
\`\`\`
|
|
||||||
`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Result Processing
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parseWorkerResult(output) {
|
|
||||||
const result = {
|
|
||||||
action: 'unknown',
|
|
||||||
status: 'unknown',
|
|
||||||
summary: '',
|
|
||||||
files_changed: [],
|
|
||||||
next_suggestion: null,
|
|
||||||
loop_back_to: null
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = output.match(/WORKER_RESULT:\s*([\s\S]*?)(?:DETAILED_OUTPUT:|$)/)
|
|
||||||
if (match) {
|
|
||||||
const lines = match[1].split('\n')
|
|
||||||
for (const line of lines) {
|
|
||||||
const m = line.match(/^-\s*(\w+):\s*(.+)$/)
|
|
||||||
if (m) {
|
|
||||||
const [, key, value] = m
|
|
||||||
if (key === 'files_changed') {
|
|
||||||
try { result.files_changed = JSON.parse(value) } catch {}
|
|
||||||
} else {
|
|
||||||
result[key] = value.trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Termination Conditions
|
|
||||||
|
|
||||||
1. User exits (interactive)
|
|
||||||
2. Sequence complete (auto)
|
|
||||||
3. Worker failed with no recovery
|
|
||||||
4. Max iterations reached
|
|
||||||
5. API paused/stopped
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Worker 生命周期**: spawn → wait → close,不保留 worker
|
|
||||||
2. **结果持久化**: Worker 输出写入 `{projectRoot}/.workflow/.loop/{loopId}.workers/`
|
|
||||||
3. **状态同步**: 每次 worker 完成后更新 state
|
|
||||||
4. **超时处理**: send_input 请求收敛,再超时则跳过
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
# State Schema (CCW Loop-B)
|
|
||||||
|
|
||||||
## Master State Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"loop_id": "loop-b-20260122-abc123",
|
|
||||||
"title": "Implement user authentication",
|
|
||||||
"description": "Full task description here",
|
|
||||||
"mode": "interactive | auto | parallel",
|
|
||||||
"status": "running | paused | completed | failed",
|
|
||||||
"current_iteration": 3,
|
|
||||||
"max_iterations": 10,
|
|
||||||
"created_at": "2026-01-22T10:00:00.000Z",
|
|
||||||
"updated_at": "2026-01-22T10:30:00.000Z",
|
|
||||||
|
|
||||||
"skill_state": {
|
|
||||||
"phase": "develop | debug | validate | complete",
|
|
||||||
"action_index": 2,
|
|
||||||
"workers_completed": ["init", "develop"],
|
|
||||||
"parallel_results": null,
|
|
||||||
"pending_tasks": [],
|
|
||||||
"completed_tasks": [],
|
|
||||||
"findings": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Field Descriptions
|
|
||||||
|
|
||||||
### Core Fields (API Compatible)
|
|
||||||
|
|
||||||
| Field | Type | Description |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| `loop_id` | string | Unique identifier |
|
|
||||||
| `title` | string | Short title (max 100 chars) |
|
|
||||||
| `description` | string | Full task description |
|
|
||||||
| `mode` | enum | Execution mode |
|
|
||||||
| `status` | enum | Current status |
|
|
||||||
| `current_iteration` | number | Iteration counter |
|
|
||||||
| `max_iterations` | number | Safety limit |
|
|
||||||
| `created_at` | ISO string | Creation timestamp |
|
|
||||||
| `updated_at` | ISO string | Last update timestamp |
|
|
||||||
|
|
||||||
### Skill State Fields
|
|
||||||
|
|
||||||
| Field | Type | Description |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| `phase` | enum | Current execution phase |
|
|
||||||
| `action_index` | number | Position in action sequence (auto mode) |
|
|
||||||
| `workers_completed` | array | List of completed worker actions |
|
|
||||||
| `parallel_results` | object | Merged results from parallel mode |
|
|
||||||
| `pending_tasks` | array | Tasks waiting to be executed |
|
|
||||||
| `completed_tasks` | array | Tasks already done |
|
|
||||||
| `findings` | array | Discoveries during execution |
|
|
||||||
|
|
||||||
## Worker Output Structure
|
|
||||||
|
|
||||||
Each worker writes to `{projectRoot}/.workflow/.loop/{loopId}.workers/{action}.output.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "develop",
|
|
||||||
"status": "success",
|
|
||||||
"summary": "Implemented 3 functions",
|
|
||||||
"files_changed": ["src/auth.ts", "src/utils.ts"],
|
|
||||||
"next_suggestion": "validate",
|
|
||||||
"loop_back_to": null,
|
|
||||||
"timestamp": "2026-01-22T10:15:00.000Z",
|
|
||||||
"detailed_output": {
|
|
||||||
"tasks_completed": [
|
|
||||||
{ "id": "T1", "description": "Create auth module" }
|
|
||||||
],
|
|
||||||
"metrics": {
|
|
||||||
"lines_added": 150,
|
|
||||||
"lines_removed": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Progress File Structure
|
|
||||||
|
|
||||||
Human-readable progress in `{projectRoot}/.workflow/.loop/{loopId}.progress/{action}.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Develop Progress
|
|
||||||
|
|
||||||
## Session: loop-b-20260122-abc123
|
|
||||||
|
|
||||||
### Iteration 1 (2026-01-22 10:15)
|
|
||||||
|
|
||||||
**Task**: Implement auth module
|
|
||||||
|
|
||||||
**Changes**:
|
|
||||||
- Created `src/auth.ts` with login/logout functions
|
|
||||||
- Added JWT token handling in `src/utils.ts`
|
|
||||||
|
|
||||||
**Status**: Success
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Iteration 2 (2026-01-22 10:30)
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Status Transitions
|
|
||||||
|
|
||||||
```
|
|
||||||
+--------+
|
|
||||||
| init |
|
|
||||||
+--------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+------> +---------+
|
|
||||||
| | develop |
|
|
||||||
| +---------+
|
|
||||||
| |
|
|
||||||
| +--------+--------+
|
|
||||||
| | |
|
|
||||||
| v v
|
|
||||||
| +-------+ +---------+
|
|
||||||
| | debug |<------| validate|
|
|
||||||
| +-------+ +---------+
|
|
||||||
| | |
|
|
||||||
| +--------+--------+
|
|
||||||
| |
|
|
||||||
| v
|
|
||||||
| [needs fix?]
|
|
||||||
| yes | | no
|
|
||||||
| v v
|
|
||||||
+------------+ +----------+
|
|
||||||
| complete |
|
|
||||||
+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## Parallel Results Schema
|
|
||||||
|
|
||||||
When `mode === 'parallel'`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"parallel_results": {
|
|
||||||
"develop": {
|
|
||||||
"status": "success",
|
|
||||||
"summary": "...",
|
|
||||||
"suggestions": []
|
|
||||||
},
|
|
||||||
"debug": {
|
|
||||||
"status": "success",
|
|
||||||
"issues_found": [],
|
|
||||||
"suggestions": []
|
|
||||||
},
|
|
||||||
"validate": {
|
|
||||||
"status": "success",
|
|
||||||
"test_results": {},
|
|
||||||
"coverage": {}
|
|
||||||
},
|
|
||||||
"merged_at": "2026-01-22T10:45:00.000Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
{projectRoot}/.workflow/.loop/
|
|
||||||
+-- loop-b-20260122-abc123.json # Master state
|
|
||||||
+-- loop-b-20260122-abc123.workers/
|
|
||||||
| +-- init.output.json
|
|
||||||
| +-- develop.output.json
|
|
||||||
| +-- debug.output.json
|
|
||||||
| +-- validate.output.json
|
|
||||||
| +-- complete.output.json
|
|
||||||
+-- loop-b-20260122-abc123.progress/
|
|
||||||
+-- develop.md
|
|
||||||
+-- debug.md
|
|
||||||
+-- validate.md
|
|
||||||
+-- summary.md
|
|
||||||
```
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
# Action Catalog (CCW Loop-B)
|
|
||||||
|
|
||||||
Complete reference of worker actions and their capabilities.
|
|
||||||
|
|
||||||
## Action Matrix
|
|
||||||
|
|
||||||
| Action | Worker Agent | Purpose | Input Requirements | Output |
|
|
||||||
|--------|--------------|---------|-------------------|--------|
|
|
||||||
| init | ccw-loop-b-init.md | Session initialization | Task description | Task breakdown + execution plan |
|
|
||||||
| develop | ccw-loop-b-develop.md | Code implementation | Task list | Code changes + progress update |
|
|
||||||
| debug | ccw-loop-b-debug.md | Problem diagnosis | Issue description | Root cause analysis + fix suggestions |
|
|
||||||
| validate | ccw-loop-b-validate.md | Testing and verification | Files to test | Test results + coverage report |
|
|
||||||
| complete | ccw-loop-b-complete.md | Session finalization | All worker outputs | Summary + commit message |
|
|
||||||
|
|
||||||
## Detailed Action Specifications
|
|
||||||
|
|
||||||
### INIT
|
|
||||||
|
|
||||||
**Purpose**: Parse requirements, create execution plan
|
|
||||||
|
|
||||||
**Preconditions**:
|
|
||||||
- `status === 'running'`
|
|
||||||
- `skill_state === null` (first time)
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
```
|
|
||||||
- Task description (text)
|
|
||||||
- Project context files
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
1. Read `{projectRoot}/.workflow/project-tech.json`
|
|
||||||
2. Read `{projectRoot}/.workflow/project-guidelines.json`
|
|
||||||
3. Parse task into phases
|
|
||||||
4. Create task breakdown
|
|
||||||
5. Generate execution plan
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: init
|
|
||||||
- status: success
|
|
||||||
- summary: "Initialized with 5 tasks"
|
|
||||||
- next_suggestion: develop
|
|
||||||
|
|
||||||
TASK_BREAKDOWN:
|
|
||||||
- T1: Create auth module
|
|
||||||
- T2: Implement JWT utils
|
|
||||||
- T3: Write tests
|
|
||||||
- T4: Validate implementation
|
|
||||||
- T5: Documentation
|
|
||||||
|
|
||||||
EXECUTION_PLAN:
|
|
||||||
1. Develop (T1-T2)
|
|
||||||
2. Validate (T3-T4)
|
|
||||||
3. Complete (T5)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects**:
|
|
||||||
- `skill_state.pending_tasks` populated
|
|
||||||
- Progress structure created
|
|
||||||
- Ready for develop phase
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### DEVELOP
|
|
||||||
|
|
||||||
**Purpose**: Implement code, create/modify files
|
|
||||||
|
|
||||||
**Preconditions**:
|
|
||||||
- `skill_state.pending_tasks.length > 0`
|
|
||||||
- `status === 'running'`
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
```
|
|
||||||
- Task list from state
|
|
||||||
- Project conventions
|
|
||||||
- Existing code patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
1. Load pending tasks
|
|
||||||
2. Find existing patterns
|
|
||||||
3. Implement tasks one by one
|
|
||||||
4. Update progress file
|
|
||||||
5. Mark tasks completed
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: develop
|
|
||||||
- status: success
|
|
||||||
- summary: "Implemented 3 tasks"
|
|
||||||
- files_changed: ["src/auth.ts", "src/utils.ts"]
|
|
||||||
- next_suggestion: validate
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
tasks_completed: [T1, T2]
|
|
||||||
metrics:
|
|
||||||
lines_added: 180
|
|
||||||
lines_removed: 15
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects**:
|
|
||||||
- Files created/modified
|
|
||||||
- `skill_state.completed_tasks` updated
|
|
||||||
- Progress documented
|
|
||||||
|
|
||||||
**Failure Modes**:
|
|
||||||
- Pattern unclear → suggest debug
|
|
||||||
- Task blocked → mark blocked, continue
|
|
||||||
- Partial completion → set `loop_back_to: "develop"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### DEBUG
|
|
||||||
|
|
||||||
**Purpose**: Diagnose issues, root cause analysis
|
|
||||||
|
|
||||||
**Preconditions**:
|
|
||||||
- Issue exists (test failure, bug report, etc.)
|
|
||||||
- `status === 'running'`
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
```
|
|
||||||
- Issue description
|
|
||||||
- Error messages
|
|
||||||
- Stack traces
|
|
||||||
- Reproduction steps
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
1. Understand problem symptoms
|
|
||||||
2. Gather evidence from code
|
|
||||||
3. Form hypothesis
|
|
||||||
4. Test hypothesis
|
|
||||||
5. Document root cause
|
|
||||||
6. Suggest fixes
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: debug
|
|
||||||
- status: success
|
|
||||||
- summary: "Root cause: memory leak in event listeners"
|
|
||||||
- next_suggestion: develop (apply fixes)
|
|
||||||
|
|
||||||
ROOT_CAUSE_ANALYSIS:
|
|
||||||
hypothesis: "Listener accumulation"
|
|
||||||
confidence: high
|
|
||||||
evidence: [...]
|
|
||||||
mechanism: "Detailed explanation"
|
|
||||||
|
|
||||||
FIX_RECOMMENDATIONS:
|
|
||||||
1. Add removeAllListeners() on disconnect
|
|
||||||
2. Verification: Monitor memory usage
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects**:
|
|
||||||
- `skill_state.findings` updated
|
|
||||||
- Fix recommendations documented
|
|
||||||
- Ready for develop to apply fixes
|
|
||||||
|
|
||||||
**Failure Modes**:
|
|
||||||
- Insufficient info → request more data
|
|
||||||
- Multiple hypotheses → rank by likelihood
|
|
||||||
- Inconclusive → suggest investigation areas
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### VALIDATE
|
|
||||||
|
|
||||||
**Purpose**: Run tests, check coverage, quality gates
|
|
||||||
|
|
||||||
**Preconditions**:
|
|
||||||
- Code exists to validate
|
|
||||||
- `status === 'running'`
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
```
|
|
||||||
- Files to test
|
|
||||||
- Test configuration
|
|
||||||
- Coverage requirements
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
1. Identify test framework
|
|
||||||
2. Run unit tests
|
|
||||||
3. Run integration tests
|
|
||||||
4. Measure coverage
|
|
||||||
5. Check quality (lint, types, security)
|
|
||||||
6. Generate report
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: validate
|
|
||||||
- status: success
|
|
||||||
- summary: "113 tests pass, coverage 95%"
|
|
||||||
- next_suggestion: complete (all pass) | develop (fix failures)
|
|
||||||
|
|
||||||
TEST_RESULTS:
|
|
||||||
unit_tests: { passed: 98, failed: 0 }
|
|
||||||
integration_tests: { passed: 15, failed: 0 }
|
|
||||||
coverage: "95%"
|
|
||||||
|
|
||||||
QUALITY_CHECKS:
|
|
||||||
lint: ✓ Pass
|
|
||||||
types: ✓ Pass
|
|
||||||
security: ✓ Pass
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects**:
|
|
||||||
- Test results documented
|
|
||||||
- Coverage measured
|
|
||||||
- Quality gates verified
|
|
||||||
|
|
||||||
**Failure Modes**:
|
|
||||||
- Tests fail → document failures, suggest fixes
|
|
||||||
- Coverage low → identify gaps
|
|
||||||
- Quality issues → flag problems
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### COMPLETE
|
|
||||||
|
|
||||||
**Purpose**: Finalize session, generate summary, commit
|
|
||||||
|
|
||||||
**Preconditions**:
|
|
||||||
- All tasks completed
|
|
||||||
- Tests passing
|
|
||||||
- `status === 'running'`
|
|
||||||
|
|
||||||
**Input**:
|
|
||||||
```
|
|
||||||
- All worker outputs
|
|
||||||
- Progress files
|
|
||||||
- Current state
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
1. Read all worker outputs
|
|
||||||
2. Consolidate achievements
|
|
||||||
3. Verify completeness
|
|
||||||
4. Generate summary
|
|
||||||
5. Prepare commit message
|
|
||||||
6. Cleanup and archive
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: complete
|
|
||||||
- status: success
|
|
||||||
- summary: "Session completed successfully"
|
|
||||||
- next_suggestion: null
|
|
||||||
|
|
||||||
SESSION_SUMMARY:
|
|
||||||
achievements: [...]
|
|
||||||
files_changed: [...]
|
|
||||||
test_results: { ... }
|
|
||||||
quality_checks: { ... }
|
|
||||||
|
|
||||||
COMMIT_SUGGESTION:
|
|
||||||
message: "feat: ..."
|
|
||||||
files: [...]
|
|
||||||
ready_for_pr: true
|
|
||||||
```
|
|
||||||
|
|
||||||
**Effects**:
|
|
||||||
- `status` → 'completed'
|
|
||||||
- Summary file created
|
|
||||||
- Progress archived
|
|
||||||
- Commit message ready
|
|
||||||
|
|
||||||
**Failure Modes**:
|
|
||||||
- Pending tasks remain → mark partial
|
|
||||||
- Quality gates fail → list failures
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Action Flow Diagrams
|
|
||||||
|
|
||||||
### Interactive Mode Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
+------+
|
|
||||||
| INIT |
|
|
||||||
+------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+------+ user selects
|
|
||||||
| MENU |-------------+
|
|
||||||
+------+ |
|
|
||||||
^ v
|
|
||||||
| +--------------+
|
|
||||||
| | spawn worker |
|
|
||||||
| +--------------+
|
|
||||||
| |
|
|
||||||
| v
|
|
||||||
| +------+-------+
|
|
||||||
+---------| wait result |
|
|
||||||
+------+-------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+------+-------+
|
|
||||||
| update state |
|
|
||||||
+--------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
[completed?] --no--> [back to MENU]
|
|
||||||
|
|
|
||||||
yes
|
|
||||||
v
|
|
||||||
+----------+
|
|
||||||
| COMPLETE |
|
|
||||||
+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto Mode Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
+------+ +---------+ +-------+ +----------+ +----------+
|
|
||||||
| INIT | ---> | DEVELOP | ---> | DEBUG | ---> | VALIDATE | ---> | COMPLETE |
|
|
||||||
+------+ +---------+ +-------+ +----------+ +----------+
|
|
||||||
^ | |
|
|
||||||
| +--- [issues] |
|
|
||||||
+--------------------------------+
|
|
||||||
[tests fail]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Mode Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
+------+
|
|
||||||
| INIT |
|
|
||||||
+------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+---------------------+
|
|
||||||
| spawn all workers |
|
|
||||||
| [develop, debug, |
|
|
||||||
| validate] |
|
|
||||||
+---------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+---------------------+
|
|
||||||
| wait({ ids: all }) |
|
|
||||||
+---------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+---------------------+
|
|
||||||
| merge results |
|
|
||||||
+---------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+---------------------+
|
|
||||||
| coordinator decides |
|
|
||||||
+---------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+----------+
|
|
||||||
| COMPLETE |
|
|
||||||
+----------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## Worker Coordination
|
|
||||||
|
|
||||||
| Scenario | Worker Sequence | Mode |
|
|
||||||
|----------|-----------------|------|
|
|
||||||
| Simple task | init → develop → validate → complete | Auto |
|
|
||||||
| Complex task | init → develop → debug → develop → validate → complete | Auto |
|
|
||||||
| Bug fix | init → debug → develop → validate → complete | Auto |
|
|
||||||
| Analysis | init → [develop \|\| debug \|\| validate] → complete | Parallel |
|
|
||||||
| Interactive | init → menu → user selects → worker → menu → ... | Interactive |
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Init always first**: Parse requirements before execution
|
|
||||||
2. **Validate often**: After each develop phase
|
|
||||||
3. **Debug when needed**: Don't skip diagnosis
|
|
||||||
4. **Complete always last**: Ensure proper cleanup
|
|
||||||
5. **Use parallel wisely**: For independent analysis tasks
|
|
||||||
6. **Follow sequence**: In auto mode, respect dependencies
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
# Worker: COMPLETE
|
|
||||||
|
|
||||||
Session finalization worker. Aggregate results, generate summary, cleanup.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Aggregate all worker results into comprehensive summary
|
|
||||||
- Verify completeness of tasks
|
|
||||||
- Generate commit message suggestion
|
|
||||||
- Offer expansion options
|
|
||||||
- Mark loop as completed
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- `state.status === 'running'`
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 1: Read All Worker Outputs
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const workerOutputs = {}
|
|
||||||
for (const action of ['init', 'develop', 'debug', 'validate']) {
|
|
||||||
const outputPath = `${workersDir}/${action}.output.json`
|
|
||||||
if (fs.existsSync(outputPath)) {
|
|
||||||
workerOutputs[action] = JSON.parse(Read(outputPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Aggregate Statistics
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const stats = {
|
|
||||||
duration: Date.now() - new Date(state.created_at).getTime(),
|
|
||||||
iterations: state.current_iteration,
|
|
||||||
tasks_completed: state.skill_state.completed_tasks.length,
|
|
||||||
tasks_total: state.skill_state.completed_tasks.length + state.skill_state.pending_tasks.length,
|
|
||||||
files_changed: collectAllFilesChanged(workerOutputs),
|
|
||||||
test_passed: workerOutputs.validate?.summary?.passed || 0,
|
|
||||||
test_total: workerOutputs.validate?.summary?.total || 0,
|
|
||||||
coverage: workerOutputs.validate?.coverage || 'N/A'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Generate Summary
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Write(`${progressDir}/summary.md`, `# CCW Loop-B Session Summary
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Task**: ${state.description}
|
|
||||||
**Mode**: ${state.mode}
|
|
||||||
**Started**: ${state.created_at}
|
|
||||||
**Completed**: ${getUtc8ISOString()}
|
|
||||||
**Duration**: ${formatDuration(stats.duration)}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Results
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Iterations | ${stats.iterations} |
|
|
||||||
| Tasks Completed | ${stats.tasks_completed}/${stats.tasks_total} |
|
|
||||||
| Tests | ${stats.test_passed}/${stats.test_total} |
|
|
||||||
| Coverage | ${stats.coverage} |
|
|
||||||
| Files Changed | ${stats.files_changed.length} |
|
|
||||||
|
|
||||||
## Files Changed
|
|
||||||
|
|
||||||
${stats.files_changed.map(f => `- \`${f}\``).join('\n') || '- None'}
|
|
||||||
|
|
||||||
## Worker Summary
|
|
||||||
|
|
||||||
${Object.entries(workerOutputs).map(([action, output]) => `
|
|
||||||
### ${action}
|
|
||||||
- Status: ${output.status}
|
|
||||||
- Summary: ${output.summary}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
${generateRecommendations(stats, state)}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Generated by CCW Loop-B at ${getUtc8ISOString()}*
|
|
||||||
`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Generate Commit Suggestion
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const commitSuggestion = {
|
|
||||||
message: generateCommitMessage(state.description, stats),
|
|
||||||
files: stats.files_changed,
|
|
||||||
ready_for_pr: stats.test_passed > 0 && stats.tasks_completed === stats.tasks_total
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
state.status = 'completed'
|
|
||||||
state.completed_at = getUtc8ISOString()
|
|
||||||
state.skill_state.phase = 'complete'
|
|
||||||
state.skill_state.workers_completed.push('complete')
|
|
||||||
saveState(loopId, state)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: complete
|
|
||||||
- status: success
|
|
||||||
- summary: Loop completed. {tasks_completed} tasks, {test_passed} tests pass
|
|
||||||
- files_changed: []
|
|
||||||
- next_suggestion: null
|
|
||||||
- loop_back_to: null
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
SESSION_SUMMARY:
|
|
||||||
achievements: [...]
|
|
||||||
files_changed: [...]
|
|
||||||
test_results: { passed: N, total: N }
|
|
||||||
|
|
||||||
COMMIT_SUGGESTION:
|
|
||||||
message: "feat: ..."
|
|
||||||
files: [...]
|
|
||||||
ready_for_pr: true
|
|
||||||
|
|
||||||
EXPANSION_OPTIONS:
|
|
||||||
1. [test] Add more test cases
|
|
||||||
2. [enhance] Feature enhancements
|
|
||||||
3. [refactor] Code refactoring
|
|
||||||
4. [doc] Documentation updates
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function formatDuration(ms) {
|
|
||||||
const seconds = Math.floor(ms / 1000)
|
|
||||||
const minutes = Math.floor(seconds / 60)
|
|
||||||
const hours = Math.floor(minutes / 60)
|
|
||||||
if (hours > 0) return `${hours}h ${minutes % 60}m`
|
|
||||||
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
|
|
||||||
return `${seconds}s`
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateRecommendations(stats, state) {
|
|
||||||
const recs = []
|
|
||||||
if (stats.tasks_completed < stats.tasks_total) recs.push('- Complete remaining tasks')
|
|
||||||
if (stats.test_passed < stats.test_total) recs.push('- Fix failing tests')
|
|
||||||
if (stats.coverage !== 'N/A' && parseFloat(stats.coverage) < 80) recs.push(`- Improve coverage (${stats.coverage}%)`)
|
|
||||||
if (recs.length === 0) recs.push('- Consider code review', '- Update documentation')
|
|
||||||
return recs.join('\n')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Missing worker outputs | Generate partial summary |
|
|
||||||
| State write failed | Retry, then report |
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
# Worker: DEBUG
|
|
||||||
|
|
||||||
Problem diagnosis worker. Hypothesis-driven debugging with evidence tracking.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Locate error source and understand failure mechanism
|
|
||||||
- Generate testable hypotheses ranked by likelihood
|
|
||||||
- Collect evidence and evaluate against criteria
|
|
||||||
- Document root cause and fix recommendations
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- Issue exists (test failure, bug report, blocked task)
|
|
||||||
- `state.status === 'running'`
|
|
||||||
|
|
||||||
## Mode Detection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const debugPath = `${progressDir}/debug.md`
|
|
||||||
const debugExists = fs.existsSync(debugPath)
|
|
||||||
|
|
||||||
const debugMode = debugExists ? 'continue' : 'explore'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Mode: Explore (First Debug)
|
|
||||||
|
|
||||||
#### Step E1: Understand Problem
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// From test failures, blocked tasks, or user description
|
|
||||||
const bugDescription = state.skill_state.findings?.[0]
|
|
||||||
|| state.description
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E2: Search Codebase
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const searchResults = mcp__ace_tool__search_context({
|
|
||||||
project_root_path: '.',
|
|
||||||
query: `code related to: ${bugDescription}`
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E3: Generate Hypotheses
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const hypotheses = [
|
|
||||||
{
|
|
||||||
id: 'H1',
|
|
||||||
description: 'Most likely cause',
|
|
||||||
testable_condition: 'What to check',
|
|
||||||
confidence: 'high | medium | low',
|
|
||||||
evidence: [],
|
|
||||||
mechanism: 'Detailed explanation of how this causes the bug'
|
|
||||||
},
|
|
||||||
// H2, H3...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E4: Create Understanding Document
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Write(`${progressDir}/debug.md`, `# Debug Understanding
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Bug**: ${bugDescription}
|
|
||||||
**Started**: ${getUtc8ISOString()}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hypotheses
|
|
||||||
|
|
||||||
${hypotheses.map(h => `
|
|
||||||
### ${h.id}: ${h.description}
|
|
||||||
- Confidence: ${h.confidence}
|
|
||||||
- Testable: ${h.testable_condition}
|
|
||||||
- Mechanism: ${h.mechanism}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
[To be collected]
|
|
||||||
|
|
||||||
## Root Cause
|
|
||||||
|
|
||||||
[Pending investigation]
|
|
||||||
`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode: Continue (Previous Debug Exists)
|
|
||||||
|
|
||||||
#### Step C1: Review Previous Findings
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const previousDebug = Read(`${progressDir}/debug.md`)
|
|
||||||
// Continue investigation based on previous findings
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step C2: Apply Fix and Verify
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// If root cause identified, apply fix
|
|
||||||
// Record fix in progress document
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: debug
|
|
||||||
- status: success
|
|
||||||
- summary: Root cause: {description}
|
|
||||||
- files_changed: []
|
|
||||||
- next_suggestion: develop
|
|
||||||
- loop_back_to: develop
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
ROOT_CAUSE_ANALYSIS:
|
|
||||||
hypothesis: "H1: {description}"
|
|
||||||
confidence: high
|
|
||||||
evidence: [...]
|
|
||||||
mechanism: "Detailed explanation"
|
|
||||||
|
|
||||||
FIX_RECOMMENDATIONS:
|
|
||||||
1. {specific fix action}
|
|
||||||
2. {verification step}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Clarification Mode
|
|
||||||
|
|
||||||
If insufficient information:
|
|
||||||
|
|
||||||
```
|
|
||||||
CLARIFICATION_NEEDED:
|
|
||||||
Q1: Can you reproduce the issue? | Options: [Yes, No, Sometimes] | Recommended: [Yes]
|
|
||||||
Q2: When did this start? | Options: [Recent change, Always, Unknown] | Recommended: [Recent change]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Insufficient info | Output CLARIFICATION_NEEDED |
|
|
||||||
| All hypotheses rejected | Generate new hypotheses |
|
|
||||||
| >5 iterations | Suggest escalation |
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
# Worker: DEVELOP
|
|
||||||
|
|
||||||
Code implementation worker. Execute pending tasks, record changes.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Execute next pending development task
|
|
||||||
- Implement code changes following project conventions
|
|
||||||
- Record progress to markdown and NDJSON log
|
|
||||||
- Update task status in state
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- `state.skill_state.pending_tasks.length > 0`
|
|
||||||
- `state.status === 'running'`
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 1: Find Pending Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const tasks = state.skill_state.pending_tasks
|
|
||||||
const currentTask = tasks.find(t => t.status === 'pending')
|
|
||||||
|
|
||||||
if (!currentTask) {
|
|
||||||
// All tasks done
|
|
||||||
return WORKER_RESULT with next_suggestion: 'validate'
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTask.status = 'in_progress'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Find Existing Patterns
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use ACE search_context to find similar implementations
|
|
||||||
const patterns = mcp__ace_tool__search_context({
|
|
||||||
project_root_path: '.',
|
|
||||||
query: `implementation patterns for: ${currentTask.description}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Study 3+ similar features/components
|
|
||||||
// Follow existing conventions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Implement Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use appropriate tools:
|
|
||||||
// - ACE search_context for finding patterns
|
|
||||||
// - Read for loading files
|
|
||||||
// - Edit/Write for making changes
|
|
||||||
|
|
||||||
const filesChanged = []
|
|
||||||
// ... implementation logic ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Record Changes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Append to progress document
|
|
||||||
const progressEntry = `
|
|
||||||
### Task ${currentTask.id} - ${currentTask.description} (${getUtc8ISOString()})
|
|
||||||
|
|
||||||
**Files Changed**:
|
|
||||||
${filesChanged.map(f => `- \`${f}\``).join('\n')}
|
|
||||||
|
|
||||||
**Summary**: [implementation description]
|
|
||||||
|
|
||||||
**Status**: COMPLETED
|
|
||||||
|
|
||||||
---
|
|
||||||
`
|
|
||||||
|
|
||||||
const existingProgress = Read(`${progressDir}/develop.md`)
|
|
||||||
Write(`${progressDir}/develop.md`, existingProgress + progressEntry)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
currentTask.status = 'completed'
|
|
||||||
state.skill_state.completed_tasks.push(currentTask)
|
|
||||||
state.skill_state.pending_tasks = tasks.filter(t => t.status === 'pending')
|
|
||||||
saveState(loopId, state)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: develop
|
|
||||||
- status: success
|
|
||||||
- summary: Implemented: {task_description}
|
|
||||||
- files_changed: ["file1.ts", "file2.ts"]
|
|
||||||
- next_suggestion: develop | validate
|
|
||||||
- loop_back_to: null
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
tasks_completed: [T1]
|
|
||||||
tasks_remaining: [T2, T3]
|
|
||||||
metrics:
|
|
||||||
lines_added: 180
|
|
||||||
lines_removed: 15
|
|
||||||
```
|
|
||||||
|
|
||||||
## Clarification Mode
|
|
||||||
|
|
||||||
If task is ambiguous, output:
|
|
||||||
|
|
||||||
```
|
|
||||||
CLARIFICATION_NEEDED:
|
|
||||||
Q1: [question about implementation approach] | Options: [A, B] | Recommended: [A]
|
|
||||||
Q2: [question about scope] | Options: [A, B, C] | Recommended: [B]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Pattern unclear | Output CLARIFICATION_NEEDED |
|
|
||||||
| Task blocked | Mark blocked, suggest debug |
|
|
||||||
| Partial completion | Set loop_back_to: "develop" |
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# Worker: INIT
|
|
||||||
|
|
||||||
Session initialization worker. Parse requirements, create execution plan.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Parse task description and project context
|
|
||||||
- Break task into development phases
|
|
||||||
- Generate initial task list
|
|
||||||
- Create progress document structure
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- `state.status === 'running'`
|
|
||||||
- `state.skill_state.phase === 'init'` or first run
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 1: Read Project Context
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// MANDATORY FIRST STEPS (already in prompt)
|
|
||||||
// 1. Read role definition
|
|
||||||
// 2. Read ${projectRoot}/.workflow/project-tech.json
|
|
||||||
// 3. Read ${projectRoot}/.workflow/project-guidelines.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Analyze Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use ACE search_context to find relevant patterns
|
|
||||||
const searchResults = mcp__ace_tool__search_context({
|
|
||||||
project_root_path: '.',
|
|
||||||
query: `code related to: ${state.description}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse task into 3-7 development tasks
|
|
||||||
const tasks = analyzeAndDecompose(state.description, searchResults)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Create Task Breakdown
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const breakdown = tasks.map((t, i) => ({
|
|
||||||
id: `T${i + 1}`,
|
|
||||||
description: t.description,
|
|
||||||
priority: t.priority || i + 1,
|
|
||||||
status: 'pending',
|
|
||||||
files: t.relatedFiles || []
|
|
||||||
}))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Initialize Progress Document
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const progressPath = `${progressDir}/develop.md`
|
|
||||||
|
|
||||||
Write(progressPath, `# Development Progress
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Task**: ${state.description}
|
|
||||||
**Started**: ${getUtc8ISOString()}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task List
|
|
||||||
|
|
||||||
${breakdown.map((t, i) => `${i + 1}. [ ] ${t.description}`).join('\n')}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Progress Timeline
|
|
||||||
|
|
||||||
`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
state.skill_state.pending_tasks = breakdown
|
|
||||||
state.skill_state.phase = 'init'
|
|
||||||
state.skill_state.workers_completed.push('init')
|
|
||||||
saveState(loopId, state)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: init
|
|
||||||
- status: success
|
|
||||||
- summary: Initialized with {N} development tasks
|
|
||||||
- files_changed: []
|
|
||||||
- next_suggestion: develop
|
|
||||||
- loop_back_to: null
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
TASK_BREAKDOWN:
|
|
||||||
- T1: {description}
|
|
||||||
- T2: {description}
|
|
||||||
...
|
|
||||||
|
|
||||||
EXECUTION_PLAN:
|
|
||||||
1. Develop (T1-T2)
|
|
||||||
2. Validate
|
|
||||||
3. Complete
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Task analysis failed | Create single generic task |
|
|
||||||
| Project context missing | Proceed without context |
|
|
||||||
| State write failed | Retry once, then report |
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# Worker: VALIDATE
|
|
||||||
|
|
||||||
Testing and verification worker. Run tests, check coverage, quality gates.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Detect test framework and run tests
|
|
||||||
- Measure code coverage
|
|
||||||
- Check quality gates (lint, types, security)
|
|
||||||
- Generate validation report
|
|
||||||
- Determine pass/fail status
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- Code exists to validate
|
|
||||||
- `state.status === 'running'`
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 1: Detect Test Framework
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const packageJson = JSON.parse(Read('package.json') || '{}')
|
|
||||||
const testScript = packageJson.scripts?.test || 'npm test'
|
|
||||||
const coverageScript = packageJson.scripts?.['test:coverage']
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Run Tests
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const testResult = await Bash({
|
|
||||||
command: testScript,
|
|
||||||
timeout: 300000 // 5 minutes
|
|
||||||
})
|
|
||||||
|
|
||||||
const testResults = parseTestOutput(testResult.stdout, testResult.stderr)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Run Coverage (if available)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let coverageData = null
|
|
||||||
if (coverageScript) {
|
|
||||||
const coverageResult = await Bash({ command: coverageScript, timeout: 300000 })
|
|
||||||
coverageData = parseCoverageReport(coverageResult.stdout)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Quality Checks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Lint check
|
|
||||||
const lintResult = await Bash({ command: 'npm run lint 2>&1 || true' })
|
|
||||||
|
|
||||||
// Type check
|
|
||||||
const typeResult = await Bash({ command: 'npx tsc --noEmit 2>&1 || true' })
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Generate Validation Report
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Write(`${progressDir}/validate.md`, `# Validation Report
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Validated**: ${getUtc8ISOString()}
|
|
||||||
|
|
||||||
## Test Results
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Total | ${testResults.total} |
|
|
||||||
| Passed | ${testResults.passed} |
|
|
||||||
| Failed | ${testResults.failed} |
|
|
||||||
| Pass Rate | ${((testResults.passed / testResults.total) * 100).toFixed(1)}% |
|
|
||||||
|
|
||||||
## Coverage
|
|
||||||
|
|
||||||
${coverageData ? `Overall: ${coverageData.overall}%` : 'N/A'}
|
|
||||||
|
|
||||||
## Quality Checks
|
|
||||||
|
|
||||||
- Lint: ${lintResult.exitCode === 0 ? 'PASS' : 'FAIL'}
|
|
||||||
- Types: ${typeResult.exitCode === 0 ? 'PASS' : 'FAIL'}
|
|
||||||
|
|
||||||
## Failed Tests
|
|
||||||
|
|
||||||
${testResults.failures?.map(f => `- ${f.name}: ${f.error}`).join('\n') || 'None'}
|
|
||||||
`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Save Structured Results
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Write(`${workersDir}/validate.output.json`, JSON.stringify({
|
|
||||||
action: 'validate',
|
|
||||||
timestamp: getUtc8ISOString(),
|
|
||||||
summary: { total: testResults.total, passed: testResults.passed, failed: testResults.failed },
|
|
||||||
coverage: coverageData?.overall || null,
|
|
||||||
quality: { lint: lintResult.exitCode === 0, types: typeResult.exitCode === 0 }
|
|
||||||
}, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
WORKER_RESULT:
|
|
||||||
- action: validate
|
|
||||||
- status: success
|
|
||||||
- summary: {passed}/{total} tests pass, coverage {N}%
|
|
||||||
- files_changed: []
|
|
||||||
- next_suggestion: complete | develop
|
|
||||||
- loop_back_to: develop (if tests fail)
|
|
||||||
|
|
||||||
DETAILED_OUTPUT:
|
|
||||||
TEST_RESULTS:
|
|
||||||
unit_tests: { passed: 98, failed: 0 }
|
|
||||||
integration_tests: { passed: 15, failed: 0 }
|
|
||||||
coverage: "95%"
|
|
||||||
|
|
||||||
QUALITY_CHECKS:
|
|
||||||
lint: PASS
|
|
||||||
types: PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Tests don't run | Check config, report error |
|
|
||||||
| All tests fail | Suggest debug action |
|
|
||||||
| Coverage tool missing | Skip coverage, tests only |
|
|
||||||
| Timeout | Increase timeout or split tests |
|
|
||||||
@@ -57,6 +57,20 @@ Stateless iterative development loop using Codex single-agent deep interaction p
|
|||||||
| --loop-id | One of TASK or --loop-id | Existing loop ID to continue |
|
| --loop-id | One of TASK or --loop-id | Existing loop ID to continue |
|
||||||
| --auto | No | Auto-cycle mode (develop → debug → validate → complete) |
|
| --auto | No | Auto-cycle mode (develop → debug → validate → complete) |
|
||||||
|
|
||||||
|
## Prep Package Integration
|
||||||
|
|
||||||
|
When `prep-package.json` exists at `{projectRoot}/.workflow/.loop/prep-package.json`, Phase 1 consumes it to:
|
||||||
|
- Load pre-built task list from `prep-tasks.jsonl` instead of generating tasks from scratch
|
||||||
|
- Apply auto-loop config (max_iterations, timeout)
|
||||||
|
- Preserve source provenance and convergence criteria from upstream planning/analysis skills
|
||||||
|
|
||||||
|
Prep packages are generated by the interactive prompt `/prompts:prep-loop`, which accepts JSONL from:
|
||||||
|
- `collaborative-plan-with-file` (tasks.jsonl)
|
||||||
|
- `analyze-with-file` (tasks.jsonl)
|
||||||
|
- `brainstorm-to-cycle` (cycle-task.md → converted to task format)
|
||||||
|
|
||||||
|
See [phases/00-prep-checklist.md](phases/00-prep-checklist.md) for schema and validation rules.
|
||||||
|
|
||||||
## Execution Modes
|
## Execution Modes
|
||||||
|
|
||||||
### Mode 1: Interactive
|
### Mode 1: Interactive
|
||||||
@@ -101,6 +115,7 @@ Phase 2: Orchestration Loop
|
|||||||
|
|
||||||
| Phase | Document | Purpose |
|
| Phase | Document | Purpose |
|
||||||
|-------|----------|---------|
|
|-------|----------|---------|
|
||||||
|
| 0 | [phases/00-prep-checklist.md](phases/00-prep-checklist.md) | Prep package schema and validation rules |
|
||||||
| 1 | [phases/01-session-init.md](phases/01-session-init.md) | Argument parsing, state creation/resume, directory init |
|
| 1 | [phases/01-session-init.md](phases/01-session-init.md) | Argument parsing, state creation/resume, directory init |
|
||||||
| 2 | [phases/02-orchestration-loop.md](phases/02-orchestration-loop.md) | Agent spawn, main loop, result parsing, send_input dispatch |
|
| 2 | [phases/02-orchestration-loop.md](phases/02-orchestration-loop.md) | Agent spawn, main loop, result parsing, send_input dispatch |
|
||||||
|
|
||||||
|
|||||||
@@ -43,26 +43,36 @@ const progressDir = `${projectRoot}/.workflow/.loop/${loopId}.progress`
|
|||||||
### Step 3: Analyze Task and Generate Tasks
|
### Step 3: Analyze Task and Generate Tasks
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Analyze task description
|
// Check if prep tasks already loaded by orchestrator (from prep-package)
|
||||||
const taskDescription = state.description
|
// If skill_state already has tasks (pre-populated by Phase 1), skip generation
|
||||||
|
const existingTasks = state.skill_state?.develop?.tasks
|
||||||
|
if (existingTasks && existingTasks.length > 0) {
|
||||||
|
console.log(`✓ Using ${existingTasks.length} pre-built tasks from prep-package`)
|
||||||
|
console.log(` Source: ${state.prep_source?.tool || 'unknown'}`)
|
||||||
|
// Skip to Step 4 — tasks already available
|
||||||
|
tasks = existingTasks
|
||||||
|
} else {
|
||||||
|
// No prep tasks — analyze task description and generate 3-7 development tasks
|
||||||
|
const taskDescription = state.description
|
||||||
|
|
||||||
// Generate 3-7 development tasks based on analysis
|
// Generate 3-7 development tasks based on analysis
|
||||||
// Use ACE search or smart_search to find relevant patterns
|
// Use ACE search or smart_search to find relevant patterns
|
||||||
|
|
||||||
const tasks = [
|
tasks = [
|
||||||
{
|
{
|
||||||
id: 'task-001',
|
id: 'task-001',
|
||||||
description: 'Task description based on analysis',
|
description: 'Task description based on analysis',
|
||||||
tool: 'gemini',
|
tool: 'gemini',
|
||||||
mode: 'write',
|
mode: 'write',
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
files: [],
|
files: [],
|
||||||
created_at: getUtc8ISOString(),
|
created_at: getUtc8ISOString(),
|
||||||
completed_at: null
|
completed_at: null
|
||||||
}
|
}
|
||||||
// ... more tasks
|
// ... more tasks
|
||||||
]
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Initialize Progress Document
|
### Step 4: Initialize Progress Document
|
||||||
|
|||||||
116
.codex/skills/ccw-loop/phases/00-prep-checklist.md
Normal file
116
.codex/skills/ccw-loop/phases/00-prep-checklist.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Phase 0: Prep Package Schema & Integration
|
||||||
|
|
||||||
|
Schema reference for `prep-package.json` consumed by ccw-loop Phase 1. Generated by interactive prompt `/prompts:prep-loop`.
|
||||||
|
|
||||||
|
## prep-package.json Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"generated_at": "ISO8601 (UTC+8)",
|
||||||
|
"prep_status": "ready | cancelled | needs_refinement",
|
||||||
|
"target_skill": "ccw-loop",
|
||||||
|
|
||||||
|
"environment": {
|
||||||
|
"project_root": "absolute path",
|
||||||
|
"tech_stack": "string",
|
||||||
|
"test_framework": "string"
|
||||||
|
},
|
||||||
|
|
||||||
|
"source": {
|
||||||
|
"tool": "collaborative-plan-with-file | analyze-with-file | brainstorm-to-cycle | manual",
|
||||||
|
"session_id": "string",
|
||||||
|
"jsonl_path": "absolute path to original JSONL",
|
||||||
|
"task_count": "number",
|
||||||
|
"tasks_with_convergence": "number"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tasks": {
|
||||||
|
"total": "number",
|
||||||
|
"by_priority": { "high": 0, "medium": 0, "low": 0 },
|
||||||
|
"by_type": { "feature": 0, "fix": 0, "refactor": 0, "enhancement": 0, "testing": 0 }
|
||||||
|
},
|
||||||
|
|
||||||
|
"auto_loop": {
|
||||||
|
"enabled": true,
|
||||||
|
"no_confirmation": true,
|
||||||
|
"max_iterations": 10,
|
||||||
|
"timeout_per_action_ms": 600000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## prep-tasks.jsonl Schema
|
||||||
|
|
||||||
|
One task per line, each in ccw-loop `develop.tasks[]` format with extended fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-001",
|
||||||
|
"description": "Title: detailed description",
|
||||||
|
"tool": "gemini",
|
||||||
|
"mode": "write",
|
||||||
|
"status": "pending",
|
||||||
|
"priority": 1,
|
||||||
|
"files_changed": ["path/to/file.ts"],
|
||||||
|
"created_at": "ISO8601",
|
||||||
|
"completed_at": null,
|
||||||
|
"_source": { "tool": "collaborative-plan-with-file", "session_id": "...", "original_id": "TASK-001" },
|
||||||
|
"_convergence": { "criteria": ["..."], "verification": "...", "definition_of_done": "..." },
|
||||||
|
"_type": "feature",
|
||||||
|
"_effort": "medium",
|
||||||
|
"_depends_on": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
| # | Check | Condition | On Failure |
|
||||||
|
|---|-------|-----------|------------|
|
||||||
|
| 1 | prep_status | `=== "ready"` | Skip prep, use default INIT |
|
||||||
|
| 2 | target_skill | `=== "ccw-loop"` | Skip prep, use default INIT |
|
||||||
|
| 3 | project_root | Matches current `projectRoot` | Skip prep, warn mismatch |
|
||||||
|
| 4 | freshness | `generated_at` within 24h | Skip prep, warn stale |
|
||||||
|
| 5 | tasks file | `prep-tasks.jsonl` exists and readable | Skip prep, use default INIT |
|
||||||
|
| 6 | tasks content | At least 1 valid task line in JSONL | Skip prep, use default INIT |
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### Phase 1: Session Initialization
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Load prep-package.json (generated by /prompts:prep-loop)
|
||||||
|
let prepPackage = null
|
||||||
|
const prepPath = `${projectRoot}/.workflow/.loop/prep-package.json`
|
||||||
|
|
||||||
|
if (fs.existsSync(prepPath)) {
|
||||||
|
const raw = JSON.parse(Read(prepPath))
|
||||||
|
const checks = validateLoopPrepPackage(raw, projectRoot)
|
||||||
|
|
||||||
|
if (checks.valid) {
|
||||||
|
prepPackage = raw
|
||||||
|
// Load pre-built tasks from prep-tasks.jsonl
|
||||||
|
const tasksPath = `${projectRoot}/.workflow/.loop/prep-tasks.jsonl`
|
||||||
|
const prepTasks = loadPrepTasks(tasksPath)
|
||||||
|
// → Inject into state.skill_state.develop.tasks
|
||||||
|
// → Set max_iterations from auto_loop config
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠ Prep package failed validation, using default INIT`)
|
||||||
|
prepPackage = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### INIT Action (action-init.md)
|
||||||
|
|
||||||
|
When prep tasks are loaded:
|
||||||
|
- **Skip** Step 3 (Analyze Task and Generate Tasks) — tasks already provided
|
||||||
|
- **Use** prep tasks directly in Step 5 (Update State)
|
||||||
|
- **Preserve** `_convergence` fields for VALIDATE action reference
|
||||||
|
|
||||||
|
### VALIDATE Action
|
||||||
|
|
||||||
|
When `_convergence` exists on a task:
|
||||||
|
- Use `convergence.verification` as validation command/steps
|
||||||
|
- Use `convergence.criteria` as pass/fail conditions
|
||||||
|
- Fall back to default test validation if `_convergence` is null
|
||||||
@@ -19,7 +19,7 @@ Create or resume a development loop, initialize state file and directory structu
|
|||||||
const projectRoot = Bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()
|
const projectRoot = Bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 1.1: Parse Arguments
|
### Step 1.1: Parse Arguments & Load Prep Package
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { loopId: existingLoopId, task, mode = 'interactive' } = options
|
const { loopId: existingLoopId, task, mode = 'interactive' } = options
|
||||||
@@ -32,6 +32,123 @@ if (!existingLoopId && !task) {
|
|||||||
|
|
||||||
// Determine mode
|
// Determine mode
|
||||||
const executionMode = options['--auto'] ? 'auto' : 'interactive'
|
const executionMode = options['--auto'] ? 'auto' : 'interactive'
|
||||||
|
|
||||||
|
// ── Prep Package: Detect → Validate → Consume ──
|
||||||
|
let prepPackage = null
|
||||||
|
let prepTasks = null
|
||||||
|
const prepPath = `${projectRoot}/.workflow/.loop/prep-package.json`
|
||||||
|
|
||||||
|
if (fs.existsSync(prepPath)) {
|
||||||
|
const raw = JSON.parse(Read(prepPath))
|
||||||
|
const checks = validateLoopPrepPackage(raw, projectRoot)
|
||||||
|
|
||||||
|
if (checks.valid) {
|
||||||
|
prepPackage = raw
|
||||||
|
|
||||||
|
// Load pre-built tasks
|
||||||
|
const tasksPath = `${projectRoot}/.workflow/.loop/prep-tasks.jsonl`
|
||||||
|
prepTasks = loadPrepTasks(tasksPath)
|
||||||
|
|
||||||
|
if (prepTasks && prepTasks.length > 0) {
|
||||||
|
console.log(`✓ Prep package loaded: ${prepTasks.length} tasks from ${prepPackage.source.tool}`)
|
||||||
|
console.log(` Checks passed: ${checks.passed.join(', ')}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠ Prep tasks file empty or invalid, falling back to default INIT`)
|
||||||
|
prepPackage = null
|
||||||
|
prepTasks = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠ Prep package found but failed validation:`)
|
||||||
|
checks.failures.forEach(f => console.warn(` ✗ ${f}`))
|
||||||
|
console.warn(` → Falling back to default behavior (prep-package ignored)`)
|
||||||
|
prepPackage = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate prep-package.json integrity before consumption.
|
||||||
|
* Returns { valid: bool, passed: string[], failures: string[] }
|
||||||
|
*/
|
||||||
|
function validateLoopPrepPackage(prep, projectRoot) {
|
||||||
|
const passed = []
|
||||||
|
const failures = []
|
||||||
|
|
||||||
|
// Check 1: prep_status must be "ready"
|
||||||
|
if (prep.prep_status === 'ready') {
|
||||||
|
passed.push('status=ready')
|
||||||
|
} else {
|
||||||
|
failures.push(`prep_status is "${prep.prep_status}", expected "ready"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 2: target_skill must match
|
||||||
|
if (prep.target_skill === 'ccw-loop') {
|
||||||
|
passed.push('target_skill match')
|
||||||
|
} else {
|
||||||
|
failures.push(`target_skill is "${prep.target_skill}", expected "ccw-loop"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 3: project_root must match current project
|
||||||
|
if (prep.environment?.project_root === projectRoot) {
|
||||||
|
passed.push('project_root match')
|
||||||
|
} else {
|
||||||
|
failures.push(`project_root mismatch: prep="${prep.environment?.project_root}", current="${projectRoot}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 4: generated_at must be within 24 hours
|
||||||
|
const generatedAt = new Date(prep.generated_at)
|
||||||
|
const hoursSince = (Date.now() - generatedAt.getTime()) / (1000 * 60 * 60)
|
||||||
|
if (hoursSince <= 24) {
|
||||||
|
passed.push(`age=${Math.round(hoursSince)}h`)
|
||||||
|
} else {
|
||||||
|
failures.push(`prep-package is ${Math.round(hoursSince)}h old (max 24h), may be stale`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 5: prep-tasks.jsonl must exist
|
||||||
|
const tasksPath = `${projectRoot}/.workflow/.loop/prep-tasks.jsonl`
|
||||||
|
if (fs.existsSync(tasksPath)) {
|
||||||
|
passed.push('prep-tasks.jsonl exists')
|
||||||
|
} else {
|
||||||
|
failures.push('prep-tasks.jsonl not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 6: task count > 0
|
||||||
|
if ((prep.tasks?.total || 0) > 0) {
|
||||||
|
passed.push(`tasks=${prep.tasks.total}`)
|
||||||
|
} else {
|
||||||
|
failures.push('task count is 0')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: failures.length === 0,
|
||||||
|
passed,
|
||||||
|
failures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load pre-built tasks from prep-tasks.jsonl.
|
||||||
|
* Returns array of task objects or null on failure.
|
||||||
|
*/
|
||||||
|
function loadPrepTasks(tasksPath) {
|
||||||
|
if (!fs.existsSync(tasksPath)) return null
|
||||||
|
|
||||||
|
const content = Read(tasksPath)
|
||||||
|
const lines = content.trim().split('\n').filter(l => l.trim())
|
||||||
|
const tasks = []
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
try {
|
||||||
|
const task = JSON.parse(line)
|
||||||
|
if (task.id && task.description) {
|
||||||
|
tasks.push(task)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`⚠ Skipping invalid task line: ${e.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks.length > 0 ? tasks : null
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 1.2: Utility Functions
|
### Step 1.2: Utility Functions
|
||||||
@@ -79,14 +196,51 @@ function createLoopState(loopId, taskDescription) {
|
|||||||
loop_id: loopId,
|
loop_id: loopId,
|
||||||
title: taskDescription.substring(0, 100),
|
title: taskDescription.substring(0, 100),
|
||||||
description: taskDescription,
|
description: taskDescription,
|
||||||
max_iterations: 10,
|
max_iterations: prepPackage?.auto_loop?.max_iterations || 10,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
current_iteration: 0,
|
current_iteration: 0,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
|
|
||||||
// Skill extension fields (initialized by INIT action)
|
// Skill extension fields
|
||||||
skill_state: null
|
// When prep tasks available, pre-populate skill_state instead of null
|
||||||
|
skill_state: prepTasks ? {
|
||||||
|
current_action: 'init',
|
||||||
|
last_action: null,
|
||||||
|
completed_actions: [],
|
||||||
|
mode: executionMode,
|
||||||
|
|
||||||
|
develop: {
|
||||||
|
total: prepTasks.length,
|
||||||
|
completed: 0,
|
||||||
|
current_task: null,
|
||||||
|
tasks: prepTasks,
|
||||||
|
last_progress_at: null
|
||||||
|
},
|
||||||
|
|
||||||
|
debug: {
|
||||||
|
active_bug: null,
|
||||||
|
hypotheses_count: 0,
|
||||||
|
hypotheses: [],
|
||||||
|
confirmed_hypothesis: null,
|
||||||
|
iteration: 0,
|
||||||
|
last_analysis_at: null
|
||||||
|
},
|
||||||
|
|
||||||
|
validate: {
|
||||||
|
pass_rate: 0,
|
||||||
|
coverage: 0,
|
||||||
|
test_results: [],
|
||||||
|
passed: false,
|
||||||
|
failed_tests: [],
|
||||||
|
last_run_at: null
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: []
|
||||||
|
} : null,
|
||||||
|
|
||||||
|
// Prep package metadata (for traceability)
|
||||||
|
prep_source: prepPackage?.source || null
|
||||||
}
|
}
|
||||||
|
|
||||||
Write(stateFile, JSON.stringify(state, null, 2))
|
Write(stateFile, JSON.stringify(state, null, 2))
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ When `--yes` or `-y`: Auto-continue all phases (skip confirmations), use recomme
|
|||||||
|
|
||||||
When `--with-commit`: Auto-commit after each task completion in Phase 4.
|
When `--with-commit`: Auto-commit after each task completion in Phase 4.
|
||||||
|
|
||||||
|
## Prep Package Integration
|
||||||
|
|
||||||
|
When `plan-prep-package.json` exists at `{projectRoot}/.workflow/.prep/plan-prep-package.json`, the skill consumes it with 6-point validation:
|
||||||
|
|
||||||
|
1. **Phase 1**: Use `task.structured` (GOAL/SCOPE/CONTEXT) for session creation, enrich planning-notes.md with source_refs and quality dimensions
|
||||||
|
2. **Phase 2**: Feed verified source_refs as supplementary docs for exploration agents
|
||||||
|
3. **Phase 3**: Auto-populate Phase 0 User Configuration (execution_method, preferred_cli_tool, supplementary_materials) — skip interactive questions
|
||||||
|
4. **Phase 4**: Apply `execution.with_commit` flag
|
||||||
|
|
||||||
|
Prep packages are generated by the interactive prompt `/prompts:prep-plan`. See [phases/00-prep-checklist.md](phases/00-prep-checklist.md) for schema and validation rules.
|
||||||
|
|
||||||
## Execution Flow
|
## Execution Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
181
.codex/skills/workflow-plan-execute/phases/00-prep-checklist.md
Normal file
181
.codex/skills/workflow-plan-execute/phases/00-prep-checklist.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Prep Package Schema & Integration Spec
|
||||||
|
|
||||||
|
Schema definition for `plan-prep-package.json` and integration points with the workflow-plan-execute skill.
|
||||||
|
|
||||||
|
## File Location
|
||||||
|
|
||||||
|
```
|
||||||
|
{projectRoot}/.workflow/.prep/plan-prep-package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated by: `/prompts:prep-plan` (interactive prompt)
|
||||||
|
Consumed by: Phase 1 (Session Discovery) → feeds into Phase 2, 3, 4
|
||||||
|
|
||||||
|
## JSON Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"generated_at": "ISO8601",
|
||||||
|
"prep_status": "ready | needs_refinement | blocked",
|
||||||
|
"target_skill": "workflow-plan-execute",
|
||||||
|
|
||||||
|
"environment": {
|
||||||
|
"project_root": "/path/to/project",
|
||||||
|
"prerequisites": {
|
||||||
|
"required_passed": true,
|
||||||
|
"recommended_passed": true,
|
||||||
|
"warnings": ["string"]
|
||||||
|
},
|
||||||
|
"tech_stack": "string",
|
||||||
|
"test_framework": "string",
|
||||||
|
"has_project_tech": true,
|
||||||
|
"has_project_guidelines": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"task": {
|
||||||
|
"original": "raw user input",
|
||||||
|
"structured": {
|
||||||
|
"goal": "GOAL string (objective + success criteria)",
|
||||||
|
"scope": "SCOPE string (boundaries)",
|
||||||
|
"context": "CONTEXT string (constraints + tech context)"
|
||||||
|
},
|
||||||
|
"quality_score": 8,
|
||||||
|
"dimensions": {
|
||||||
|
"objective": { "score": 2, "value": "..." },
|
||||||
|
"success_criteria": { "score": 2, "value": "..." },
|
||||||
|
"scope": { "score": 2, "value": "..." },
|
||||||
|
"constraints": { "score": 1, "value": "..." },
|
||||||
|
"context": { "score": 1, "value": "..." }
|
||||||
|
},
|
||||||
|
"source_refs": [
|
||||||
|
{
|
||||||
|
"path": "docs/prd.md",
|
||||||
|
"type": "local_file | url | auto_detected",
|
||||||
|
"status": "verified | linked | not_found",
|
||||||
|
"preview": "first ~20 lines (local_file only)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"execution": {
|
||||||
|
"auto_yes": true,
|
||||||
|
"with_commit": true,
|
||||||
|
"execution_method": "agent | cli | hybrid",
|
||||||
|
"preferred_cli_tool": "codex | gemini | qwen | auto",
|
||||||
|
"supplementary_materials": {
|
||||||
|
"type": "none | paths | inline",
|
||||||
|
"content": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Rules (6 checks)
|
||||||
|
|
||||||
|
Phase 1 对 plan-prep-package.json 执行 **6 项验证**,全部通过才加载:
|
||||||
|
|
||||||
|
| # | 检查项 | 条件 | 失败处理 |
|
||||||
|
|---|--------|------|----------|
|
||||||
|
| 1 | prep_status | `=== "ready"` | 跳过 prep |
|
||||||
|
| 2 | target_skill | `=== "workflow-plan-execute"` | 跳过 prep(防错误 skill) |
|
||||||
|
| 3 | project_root | 与当前 projectRoot 一致 | 跳过 prep(防错误项目) |
|
||||||
|
| 4 | quality_score | `>= 6` | 跳过 prep(任务质量不达标) |
|
||||||
|
| 5 | 时效性 | generated_at 在 24h 以内 | 跳过 prep(可能过期) |
|
||||||
|
| 6 | 必需字段 | task.structured.goal, execution 全部存在 | 跳过 prep |
|
||||||
|
|
||||||
|
## Phase 1 Integration (Session Discovery)
|
||||||
|
|
||||||
|
After session creation, enrich planning-notes.md with prep data:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const prepPath = `${projectRoot}/.workflow/.prep/plan-prep-package.json`
|
||||||
|
let prepPackage = null
|
||||||
|
|
||||||
|
if (fs.existsSync(prepPath)) {
|
||||||
|
const raw = JSON.parse(Read(prepPath))
|
||||||
|
const checks = validatePlanPrepPackage(raw, projectRoot)
|
||||||
|
|
||||||
|
if (checks.valid) {
|
||||||
|
prepPackage = raw
|
||||||
|
// Use structured task for session creation
|
||||||
|
structuredDescription = {
|
||||||
|
goal: prepPackage.task.structured.goal,
|
||||||
|
scope: prepPackage.task.structured.scope,
|
||||||
|
context: prepPackage.task.structured.context
|
||||||
|
}
|
||||||
|
console.log(`✓ Prep package loaded: score=${prepPackage.task.quality_score}/10`)
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠ Prep package validation failed, using defaults`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After session created, enrich planning-notes.md:
|
||||||
|
if (prepPackage) {
|
||||||
|
// 1. Add source refs section
|
||||||
|
const sourceRefsSection = prepPackage.task.source_refs
|
||||||
|
?.filter(r => r.status === 'verified' || r.status === 'linked')
|
||||||
|
.map(r => `- **${r.type}**: ${r.path}`)
|
||||||
|
.join('\n') || 'None'
|
||||||
|
|
||||||
|
// 2. Add quality dimensions
|
||||||
|
const dimensionsSection = Object.entries(prepPackage.task.dimensions)
|
||||||
|
.map(([k, v]) => `- **${k}**: ${v.value} (score: ${v.score}/2)`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
// Append to planning-notes.md under User Intent
|
||||||
|
Edit(planningNotesPath, {
|
||||||
|
old: `- **KEY_CONSTRAINTS**: ${userConstraints}`,
|
||||||
|
new: `- **KEY_CONSTRAINTS**: ${userConstraints}
|
||||||
|
|
||||||
|
### Requirement Sources (from prep)
|
||||||
|
${sourceRefsSection}
|
||||||
|
|
||||||
|
### Quality Dimensions (from prep)
|
||||||
|
${dimensionsSection}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 3 Integration (Task Generation - Phase 0 User Config)
|
||||||
|
|
||||||
|
Prep package auto-populates Phase 0 user configuration:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Phase 3, Phase 0 (User Configuration):
|
||||||
|
if (prepPackage) {
|
||||||
|
// Auto-answer all Phase 0 questions from prep
|
||||||
|
userConfig = {
|
||||||
|
supplementaryMaterials: prepPackage.execution.supplementary_materials,
|
||||||
|
executionMethod: prepPackage.execution.execution_method,
|
||||||
|
preferredCliTool: prepPackage.execution.preferred_cli_tool,
|
||||||
|
enableResume: true
|
||||||
|
}
|
||||||
|
console.log(`✓ Phase 0 auto-configured from prep: ${userConfig.executionMethod} (${userConfig.preferredCliTool})`)
|
||||||
|
// Skip interactive questions, proceed to Phase 1 (Context Prep)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 2 Integration (Context Gathering)
|
||||||
|
|
||||||
|
Source refs from prep feed into exploration context:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Phase 2, Step 2 (spawn explore agents):
|
||||||
|
// Add source_refs as supplementary context for exploration
|
||||||
|
if (prepPackage?.task?.source_refs?.length > 0) {
|
||||||
|
const verifiedRefs = prepPackage.task.source_refs.filter(r => r.status === 'verified')
|
||||||
|
// Include verified local docs in exploration agent prompt
|
||||||
|
explorationAgentPrompt += `\n## SUPPLEMENTARY REQUIREMENT DOCUMENTS\n`
|
||||||
|
explorationAgentPrompt += verifiedRefs.map(r => `Read and analyze: ${r.path}`).join('\n')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4 Integration (Execution)
|
||||||
|
|
||||||
|
Commit flag from prep:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Phase 4:
|
||||||
|
const withCommit = prepPackage?.execution?.with_commit || $ARGUMENTS.includes('--with-commit')
|
||||||
|
```
|
||||||
@@ -9,6 +9,79 @@ Discover existing sessions or start new workflow session with intelligent sessio
|
|||||||
- Generate unique session ID (WFS-xxx format)
|
- Generate unique session ID (WFS-xxx format)
|
||||||
- Initialize session directory structure
|
- Initialize session directory structure
|
||||||
|
|
||||||
|
## Step 0.0: Load Prep Package (if exists)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Load plan-prep-package.json (generated by /prompts:prep-plan)
|
||||||
|
let prepPackage = null
|
||||||
|
const prepPath = `${projectRoot}/.workflow/.prep/plan-prep-package.json`
|
||||||
|
|
||||||
|
if (fs.existsSync(prepPath)) {
|
||||||
|
const raw = JSON.parse(Read(prepPath))
|
||||||
|
const checks = validatePlanPrepPackage(raw, projectRoot)
|
||||||
|
|
||||||
|
if (checks.valid) {
|
||||||
|
prepPackage = raw
|
||||||
|
console.log(`✓ Prep package loaded: score=${prepPackage.task.quality_score}/10, exec=${prepPackage.execution.execution_method}`)
|
||||||
|
console.log(` Checks passed: ${checks.passed.join(', ')}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠ Prep package found but failed validation:`)
|
||||||
|
checks.failures.forEach(f => console.warn(` ✗ ${f}`))
|
||||||
|
console.warn(` → Falling back to default behavior (prep-package ignored)`)
|
||||||
|
prepPackage = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate plan-prep-package.json integrity before consumption.
|
||||||
|
*/
|
||||||
|
function validatePlanPrepPackage(prep, projectRoot) {
|
||||||
|
const passed = []
|
||||||
|
const failures = []
|
||||||
|
|
||||||
|
// Check 1: prep_status
|
||||||
|
if (prep.prep_status === 'ready') passed.push('status=ready')
|
||||||
|
else failures.push(`prep_status is "${prep.prep_status}", expected "ready"`)
|
||||||
|
|
||||||
|
// Check 2: target_skill
|
||||||
|
if (prep.target_skill === 'workflow-plan-execute') passed.push('target_skill match')
|
||||||
|
else failures.push(`target_skill is "${prep.target_skill}", expected "workflow-plan-execute"`)
|
||||||
|
|
||||||
|
// Check 3: project_root
|
||||||
|
if (prep.environment?.project_root === projectRoot) passed.push('project_root match')
|
||||||
|
else failures.push(`project_root mismatch: "${prep.environment?.project_root}" vs "${projectRoot}"`)
|
||||||
|
|
||||||
|
// Check 4: quality_score >= 6
|
||||||
|
if ((prep.task?.quality_score || 0) >= 6) passed.push(`quality=${prep.task.quality_score}/10`)
|
||||||
|
else failures.push(`quality_score ${prep.task?.quality_score || 0} < 6`)
|
||||||
|
|
||||||
|
// Check 5: generated_at within 24h
|
||||||
|
const hoursSince = (Date.now() - new Date(prep.generated_at).getTime()) / 3600000
|
||||||
|
if (hoursSince <= 24) passed.push(`age=${Math.round(hoursSince)}h`)
|
||||||
|
else failures.push(`prep-package is ${Math.round(hoursSince)}h old (max 24h)`)
|
||||||
|
|
||||||
|
// Check 6: required fields
|
||||||
|
const required = ['task.structured.goal', 'task.structured.scope', 'execution.execution_method']
|
||||||
|
const missing = required.filter(p => !p.split('.').reduce((o, k) => o?.[k], prep))
|
||||||
|
if (missing.length === 0) passed.push('fields complete')
|
||||||
|
else failures.push(`missing: ${missing.join(', ')}`)
|
||||||
|
|
||||||
|
return { valid: failures.length === 0, passed, failures }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build structured description from prep or raw input
|
||||||
|
let structuredDescription
|
||||||
|
if (prepPackage) {
|
||||||
|
structuredDescription = {
|
||||||
|
goal: prepPackage.task.structured.goal,
|
||||||
|
scope: prepPackage.task.structured.scope,
|
||||||
|
context: prepPackage.task.structured.context
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
structuredDescription = null // Will be parsed from user input later
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Step 0: Initialize Project State (First-time Only)
|
## Step 0: Initialize Project State (First-time Only)
|
||||||
|
|
||||||
**Executed before all modes** - Ensures project-level state files exist by calling `workflow:init`.
|
**Executed before all modes** - Ensures project-level state files exist by calling `workflow:init`.
|
||||||
@@ -73,23 +146,47 @@ CONTEXT: Existing user database schema, REST API endpoints
|
|||||||
|
|
||||||
### Step 1.4: Initialize Planning Notes
|
### Step 1.4: Initialize Planning Notes
|
||||||
|
|
||||||
Create `planning-notes.md` with N+1 context support:
|
Create `planning-notes.md` with N+1 context support, enriched with prep data:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const planningNotesPath = `${projectRoot}/.workflow/active/${sessionId}/planning-notes.md`
|
const planningNotesPath = `${projectRoot}/.workflow/active/${sessionId}/planning-notes.md`
|
||||||
const userGoal = structuredDescription.goal
|
const userGoal = structuredDescription?.goal || taskDescription
|
||||||
const userConstraints = structuredDescription.context || "None specified"
|
const userScope = structuredDescription?.scope || "Not specified"
|
||||||
|
const userConstraints = structuredDescription?.context || "None specified"
|
||||||
|
|
||||||
|
// Build source refs section from prep
|
||||||
|
const sourceRefsSection = (prepPackage?.task?.source_refs?.length > 0)
|
||||||
|
? prepPackage.task.source_refs
|
||||||
|
.filter(r => r.status === 'verified' || r.status === 'linked')
|
||||||
|
.map(r => `- **${r.type}**: ${r.path}`)
|
||||||
|
.join('\n')
|
||||||
|
: 'None'
|
||||||
|
|
||||||
|
// Build quality dimensions section from prep
|
||||||
|
const dimensionsSection = prepPackage?.task?.dimensions
|
||||||
|
? Object.entries(prepPackage.task.dimensions)
|
||||||
|
.map(([k, v]) => `- **${k}**: ${v.value} (${v.score}/2)`)
|
||||||
|
.join('\n')
|
||||||
|
: ''
|
||||||
|
|
||||||
Write(planningNotesPath, `# Planning Notes
|
Write(planningNotesPath, `# Planning Notes
|
||||||
|
|
||||||
**Session**: ${sessionId}
|
**Session**: ${sessionId}
|
||||||
**Created**: ${new Date().toISOString()}
|
**Created**: ${new Date().toISOString()}
|
||||||
|
${prepPackage ? `**Prep Package**: plan-prep-package.json (score: ${prepPackage.task.quality_score}/10)` : ''}
|
||||||
|
|
||||||
## User Intent (Phase 1)
|
## User Intent (Phase 1)
|
||||||
|
|
||||||
- **GOAL**: ${userGoal}
|
- **GOAL**: ${userGoal}
|
||||||
|
- **SCOPE**: ${userScope}
|
||||||
- **KEY_CONSTRAINTS**: ${userConstraints}
|
- **KEY_CONSTRAINTS**: ${userConstraints}
|
||||||
|
${sourceRefsSection !== 'None' ? `
|
||||||
|
### Requirement Sources (from prep)
|
||||||
|
${sourceRefsSection}
|
||||||
|
` : ''}${dimensionsSection ? `
|
||||||
|
### Quality Dimensions (from prep)
|
||||||
|
${dimensionsSection}
|
||||||
|
` : ''}
|
||||||
---
|
---
|
||||||
|
|
||||||
## Context Findings (Phase 2)
|
## Context Findings (Phase 2)
|
||||||
|
|||||||
@@ -127,6 +127,15 @@ const sessionFolder = `${projectRoot}/.workflow/active/${session_id}/.process`;
|
|||||||
// 2.2 Launch Parallel Explore Agents (with conflict detection)
|
// 2.2 Launch Parallel Explore Agents (with conflict detection)
|
||||||
const explorationAgents = [];
|
const explorationAgents = [];
|
||||||
|
|
||||||
|
// Load source_refs from prep-package for supplementary context
|
||||||
|
const prepPath = `${projectRoot}/.workflow/.prep/plan-prep-package.json`
|
||||||
|
const prepSourceRefs = fs.existsSync(prepPath)
|
||||||
|
? (JSON.parse(Read(prepPath))?.task?.source_refs || []).filter(r => r.status === 'verified')
|
||||||
|
: []
|
||||||
|
const sourceRefsDirective = prepSourceRefs.length > 0
|
||||||
|
? `\n## SUPPLEMENTARY REQUIREMENT DOCUMENTS (from prep)\nRead these before exploration:\n${prepSourceRefs.map((r, i) => `${i + 1}. Read: ${r.path} (${r.type})`).join('\n')}\nCross-reference findings against these source documents.\n`
|
||||||
|
: ''
|
||||||
|
|
||||||
// Spawn all agents in parallel
|
// Spawn all agents in parallel
|
||||||
selectedAngles.forEach((angle, index) => {
|
selectedAngles.forEach((angle, index) => {
|
||||||
const agentId = spawn_agent({
|
const agentId = spawn_agent({
|
||||||
@@ -144,6 +153,7 @@ selectedAngles.forEach((angle, index) => {
|
|||||||
Execute **${angle}** exploration for task planning context. Analyze codebase from this specific angle to discover relevant structure, patterns, and constraints.
|
Execute **${angle}** exploration for task planning context. Analyze codebase from this specific angle to discover relevant structure, patterns, and constraints.
|
||||||
|
|
||||||
**CONFLICT DETECTION**: Additionally detect conflict indicators including module overlaps, breaking changes, incompatible patterns, and scenario boundary ambiguities.
|
**CONFLICT DETECTION**: Additionally detect conflict indicators including module overlaps, breaking changes, incompatible patterns, and scenario boundary ambiguities.
|
||||||
|
${sourceRefsDirective}
|
||||||
|
|
||||||
## Assigned Context
|
## Assigned Context
|
||||||
- **Exploration Angle**: ${angle}
|
- **Exploration Angle**: ${angle}
|
||||||
|
|||||||
@@ -78,12 +78,17 @@ Phase 3: Integration (+1 Coordinator, Multi-Module Only)
|
|||||||
```javascript
|
```javascript
|
||||||
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||||
|
|
||||||
if (autoYes) {
|
// Check for prep-package auto-configuration (from /prompts:prep-plan)
|
||||||
console.log(`[--yes] Using defaults: No materials, Agent executor, Codex CLI`)
|
const prepPath = `${projectRoot}/.workflow/.prep/plan-prep-package.json`
|
||||||
|
const prepExec = fs.existsSync(prepPath) ? JSON.parse(Read(prepPath))?.execution : null
|
||||||
|
|
||||||
|
if (autoYes || prepExec) {
|
||||||
|
const source = prepExec ? 'prep-package' : '--yes flag'
|
||||||
|
console.log(`[${source}] Using defaults: ${prepExec?.execution_method || 'agent'} executor, ${prepExec?.preferred_cli_tool || 'codex'} CLI`)
|
||||||
userConfig = {
|
userConfig = {
|
||||||
supplementaryMaterials: { type: "none", content: [] },
|
supplementaryMaterials: prepExec?.supplementary_materials || { type: "none", content: [] },
|
||||||
executionMethod: "agent",
|
executionMethod: prepExec?.execution_method || "agent",
|
||||||
preferredCliTool: "codex",
|
preferredCliTool: prepExec?.preferred_cli_tool || "codex",
|
||||||
enableResume: true
|
enableResume: true
|
||||||
}
|
}
|
||||||
// Skip to Phase 1
|
// Skip to Phase 1
|
||||||
|
|||||||
@@ -16,11 +16,25 @@ interface StopOptions {
|
|||||||
*/
|
*/
|
||||||
async function findProcessOnPort(port: number): Promise<string | null> {
|
async function findProcessOnPort(port: number): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
|
// Avoid filtering on the localized state column (e.g. not always "LISTENING").
|
||||||
const lines = stdout.trim().split('\n');
|
const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
|
||||||
if (lines.length > 0) {
|
const lines = stdout.trim().split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
||||||
const parts = lines[0].trim().split(/\s+/);
|
|
||||||
return parts[parts.length - 1]; // PID is the last column
|
for (const line of lines) {
|
||||||
|
// Typical format:
|
||||||
|
// TCP 0.0.0.0:3457 0.0.0.0:0 LISTENING 31736
|
||||||
|
// TCP [::]:3457 [::]:0 LISTENING 31736
|
||||||
|
const parts = line.split(/\s+/);
|
||||||
|
if (parts.length < 4) continue;
|
||||||
|
const proto = parts[0]?.toUpperCase();
|
||||||
|
const localAddress = parts[1] || '';
|
||||||
|
const pidCandidate = parts[parts.length - 1] || '';
|
||||||
|
|
||||||
|
if (proto !== 'TCP') continue;
|
||||||
|
if (!localAddress.endsWith(`:${port}`)) continue;
|
||||||
|
if (!/^\d+$/.test(pidCandidate)) continue;
|
||||||
|
|
||||||
|
return pidCandidate; // PID is the last column
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// No process found
|
// No process found
|
||||||
@@ -28,20 +42,62 @@ async function findProcessOnPort(port: number): Promise<string | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getProcessCommandLine(pid: string): Promise<string | null> {
|
||||||
|
if (!/^\d+$/.test(pid)) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const probeCommand =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? `powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine"`
|
||||||
|
: `ps -p ${pid} -o command=`;
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(probeCommand);
|
||||||
|
const commandLine = stdout.trim();
|
||||||
|
return commandLine.length > 0 ? commandLine : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLikelyViteCommandLine(commandLine: string, port: number): boolean {
|
||||||
|
const lower = commandLine.toLowerCase();
|
||||||
|
if (!lower.includes('vite')) return false;
|
||||||
|
|
||||||
|
const portStr = String(port);
|
||||||
|
return (
|
||||||
|
lower.includes(`--port ${portStr}`) ||
|
||||||
|
lower.includes(`--port=${portStr}`) ||
|
||||||
|
// Some npm wrappers pass through the port in a slightly different shape.
|
||||||
|
lower.includes(`port ${portStr}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kill process by PID (Windows)
|
* Kill process by PID (Windows)
|
||||||
* @param {string} pid - Process ID
|
* @param {string} pid - Process ID
|
||||||
* @returns {Promise<boolean>} Success status
|
* @returns {Promise<boolean>} Success status
|
||||||
*/
|
*/
|
||||||
async function killProcess(pid: string): Promise<boolean> {
|
async function killProcess(pid: string): Promise<boolean> {
|
||||||
|
if (!/^\d+$/.test(pid)) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use PowerShell to avoid Git Bash path expansion issues with /PID
|
// Prefer taskkill to terminate the entire process tree on Windows (npm/cmd wrappers can orphan children).
|
||||||
await execAsync(`powershell -Command "Stop-Process -Id ${pid} -Force -ErrorAction Stop"`);
|
if (process.platform === 'win32') {
|
||||||
|
await execAsync(`cmd /c "taskkill /PID ${pid} /T /F"`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort on non-Windows platforms (mockable via child_process.exec in tests).
|
||||||
|
await execAsync(`kill -TERM ${pid}`);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback to taskkill via cmd
|
|
||||||
try {
|
try {
|
||||||
await execAsync(`cmd /c "taskkill /PID ${pid} /F"`);
|
if (process.platform === 'win32') {
|
||||||
|
await execAsync(`powershell -NoProfile -Command "Stop-Process -Id ${pid} -Force -ErrorAction Stop"`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await execAsync(`kill -KILL ${pid}`);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
@@ -105,6 +161,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
await cleanupReactFrontend(reactPort);
|
await cleanupReactFrontend(reactPort);
|
||||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Best-effort verify shutdown (may still succeed even if shutdown endpoint didn't return ok)
|
// Best-effort verify shutdown (may still succeed even if shutdown endpoint didn't return ok)
|
||||||
@@ -116,6 +173,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
await cleanupReactFrontend(reactPort);
|
await cleanupReactFrontend(reactPort);
|
||||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusHint = shutdownResponse ? `HTTP ${shutdownResponse.status}` : 'no response';
|
const statusHint = shutdownResponse ? `HTTP ${shutdownResponse.status}` : 'no response';
|
||||||
@@ -132,7 +190,11 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
const reactPid = await findProcessOnPort(reactPort);
|
const reactPid = await findProcessOnPort(reactPort);
|
||||||
if (reactPid) {
|
if (reactPid) {
|
||||||
console.log(chalk.yellow(` React frontend still running on port ${reactPort} (PID: ${reactPid})`));
|
console.log(chalk.yellow(` React frontend still running on port ${reactPort} (PID: ${reactPid})`));
|
||||||
if (force) {
|
|
||||||
|
const commandLine = await getProcessCommandLine(reactPid);
|
||||||
|
const isLikelyVite = commandLine ? isLikelyViteCommandLine(commandLine, reactPort) : false;
|
||||||
|
|
||||||
|
if (force || isLikelyVite) {
|
||||||
console.log(chalk.cyan(' Cleaning up React frontend...'));
|
console.log(chalk.cyan(' Cleaning up React frontend...'));
|
||||||
const killed = await killProcess(reactPid);
|
const killed = await killProcess(reactPid);
|
||||||
if (killed) {
|
if (killed) {
|
||||||
@@ -141,10 +203,12 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
console.log(chalk.red(' Failed to stop React frontend.\n'));
|
console.log(chalk.red(' Failed to stop React frontend.\n'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.gray(`\n Use --force to clean it up:\n ccw stop --force\n`));
|
console.log(chalk.gray(`\n React process does not look like Vite on port ${reactPort}.`));
|
||||||
|
console.log(chalk.gray(` Use --force to clean it up:\n ccw stop --force\n`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port is in use by another process
|
// Port is in use by another process
|
||||||
@@ -174,9 +238,11 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
|
|
||||||
console.log(chalk.green.bold('\n All processes stopped successfully!\n'));
|
console.log(chalk.green.bold('\n All processes stopped successfully!\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Also check React frontend port
|
// Also check React frontend port
|
||||||
@@ -188,11 +254,13 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
||||||
console.log(chalk.white(` ccw stop --force\n`));
|
console.log(chalk.white(` ccw stop --force\n`));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,17 +21,43 @@ describe('stop command module', async () => {
|
|||||||
const childProcess = require('child_process');
|
const childProcess = require('child_process');
|
||||||
const originalExec = childProcess.exec;
|
const originalExec = childProcess.exec;
|
||||||
const execCalls: string[] = [];
|
const execCalls: string[] = [];
|
||||||
|
const netstatByPort = new Map<number, string>();
|
||||||
|
const commandLineByPid = new Map<string, string>();
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// Patch child_process.exec BEFORE importing stop module (it captures exec at module init).
|
// Patch child_process.exec BEFORE importing stop module (it captures exec at module init).
|
||||||
childProcess.exec = (command: string, cb: any) => {
|
childProcess.exec = (command: string, cb: any) => {
|
||||||
execCalls.push(command);
|
execCalls.push(command);
|
||||||
if (/^netstat -ano/i.test(command)) {
|
if (/^netstat -ano/i.test(command)) {
|
||||||
const stdout = 'TCP 0.0.0.0:56792 0.0.0.0:0 LISTENING 4242\r\n';
|
const portMatch = command.match(/findstr\s+:([0-9]+)/i);
|
||||||
|
const port = portMatch ? Number(portMatch[1]) : NaN;
|
||||||
|
const stdout = Number.isFinite(port) ? (netstatByPort.get(port) ?? '') : '';
|
||||||
cb(null, stdout, '');
|
cb(null, stdout, '');
|
||||||
return {} as any;
|
return {} as any;
|
||||||
}
|
}
|
||||||
if (/^taskkill /i.test(command)) {
|
if (/taskkill\b/i.test(command)) {
|
||||||
|
cb(null, '', '');
|
||||||
|
return {} as any;
|
||||||
|
}
|
||||||
|
if (/^powershell\b/i.test(command) && /Get-CimInstance\s+Win32_Process/i.test(command)) {
|
||||||
|
const pidMatch = command.match(/ProcessId=([0-9]+)/i);
|
||||||
|
const pid = pidMatch ? pidMatch[1] : '';
|
||||||
|
const stdout = commandLineByPid.get(pid) ?? '';
|
||||||
|
cb(null, stdout, '');
|
||||||
|
return {} as any;
|
||||||
|
}
|
||||||
|
if (/^ps\s+-p\s+/i.test(command)) {
|
||||||
|
const pidMatch = command.match(/^ps\s+-p\s+([0-9]+)/i);
|
||||||
|
const pid = pidMatch ? pidMatch[1] : '';
|
||||||
|
const stdout = commandLineByPid.get(pid) ?? '';
|
||||||
|
cb(null, stdout, '');
|
||||||
|
return {} as any;
|
||||||
|
}
|
||||||
|
if (/^powershell\b/i.test(command) && /Stop-Process\s+-Id/i.test(command)) {
|
||||||
|
cb(null, '', '');
|
||||||
|
return {} as any;
|
||||||
|
}
|
||||||
|
if (/^kill\s+-/i.test(command)) {
|
||||||
cb(null, '', '');
|
cb(null, '', '');
|
||||||
return {} as any;
|
return {} as any;
|
||||||
}
|
}
|
||||||
@@ -44,6 +70,8 @@ describe('stop command module', async () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
execCalls.length = 0;
|
execCalls.length = 0;
|
||||||
|
netstatByPort.clear();
|
||||||
|
commandLineByPid.clear();
|
||||||
mock.restoreAll();
|
mock.restoreAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,9 +112,10 @@ describe('stop command module', async () => {
|
|||||||
|
|
||||||
// No server responding, fall back to netstat/taskkill
|
// No server responding, fall back to netstat/taskkill
|
||||||
mock.method(globalThis as any, 'fetch', async () => null);
|
mock.method(globalThis as any, 'fetch', async () => null);
|
||||||
|
netstatByPort.set(56792, 'TCP 0.0.0.0:56792 0.0.0.0:0 LISTENING 4242\r\n');
|
||||||
|
|
||||||
await stopModule.stopCommand({ port: 56792, force: true });
|
await stopModule.stopCommand({ port: 56792, force: true });
|
||||||
assert.ok(execCalls.some((c) => /^taskkill /i.test(c)));
|
assert.ok(execCalls.some((c) => /taskkill\b/i.test(c) || /Stop-Process\b/i.test(c) || /^kill\s+-/i.test(c)));
|
||||||
assert.ok(exitCodes.includes(0));
|
assert.ok(exitCodes.includes(0));
|
||||||
assert.ok(!exitCodes.includes(1));
|
assert.ok(!exitCodes.includes(1));
|
||||||
});
|
});
|
||||||
@@ -100,10 +129,35 @@ describe('stop command module', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mock.method(globalThis as any, 'fetch', async () => null);
|
mock.method(globalThis as any, 'fetch', async () => null);
|
||||||
|
netstatByPort.set(56792, 'TCP 0.0.0.0:56792 0.0.0.0:0 LISTENING 4242\r\n');
|
||||||
|
|
||||||
await stopModule.stopCommand({ port: 56792, force: false });
|
await stopModule.stopCommand({ port: 56792, force: false });
|
||||||
assert.ok(execCalls.some((c) => /^netstat -ano/i.test(c)));
|
assert.ok(execCalls.some((c) => /^netstat -ano/i.test(c)));
|
||||||
assert.ok(!execCalls.some((c) => /^taskkill /i.test(c)));
|
assert.ok(!execCalls.some((c) => /taskkill\b/i.test(c) || /Stop-Process\b/i.test(c) || /^kill\s+-/i.test(c)));
|
||||||
|
assert.ok(exitCodes.includes(0));
|
||||||
|
assert.ok(!exitCodes.includes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto-cleans Vite on react port when main server is not running (no --force)', async () => {
|
||||||
|
mock.method(console, 'log', () => {});
|
||||||
|
mock.method(console, 'error', () => {});
|
||||||
|
const exitCodes: Array<number | undefined> = [];
|
||||||
|
mock.method(process as any, 'exit', (code?: number) => {
|
||||||
|
exitCodes.push(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
// No server responding, main port free, react port occupied by Vite.
|
||||||
|
mock.method(globalThis as any, 'fetch', async () => null);
|
||||||
|
netstatByPort.set(56792, '');
|
||||||
|
netstatByPort.set(56793, 'TCP 0.0.0.0:56793 0.0.0.0:0 LISTENING 4242\r\n');
|
||||||
|
commandLineByPid.set('4242', 'cmd.exe /d /s /c vite --port 56793 --strictPort\r\n');
|
||||||
|
|
||||||
|
await stopModule.stopCommand({ port: 56792, force: false });
|
||||||
|
assert.ok(execCalls.some((c) =>
|
||||||
|
(/^powershell\b/i.test(c) && /Get-CimInstance\s+Win32_Process/i.test(c)) ||
|
||||||
|
/^ps\s+-p\s+/i.test(c)
|
||||||
|
));
|
||||||
|
assert.ok(execCalls.some((c) => /taskkill\b/i.test(c) || /Stop-Process\b/i.test(c) || /^kill\s+-/i.test(c)));
|
||||||
assert.ok(exitCodes.includes(0));
|
assert.ok(exitCodes.includes(0));
|
||||||
assert.ok(!exitCodes.includes(1));
|
assert.ok(!exitCodes.includes(1));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user