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:
catlog22
2026-02-09 15:02:38 +08:00
parent ef7382ecf5
commit c62d26183b
25 changed files with 1596 additions and 2896 deletions

View File

@@ -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 |
| --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
### Mode 1: Interactive
@@ -101,6 +115,7 @@ Phase 2: Orchestration Loop
| 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 |
| 2 | [phases/02-orchestration-loop.md](phases/02-orchestration-loop.md) | Agent spawn, main loop, result parsing, send_input dispatch |

View File

@@ -43,26 +43,36 @@ const progressDir = `${projectRoot}/.workflow/.loop/${loopId}.progress`
### Step 3: Analyze Task and Generate Tasks
```javascript
// Analyze task description
const taskDescription = state.description
// Check if prep tasks already loaded by orchestrator (from prep-package)
// 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
// Use ACE search or smart_search to find relevant patterns
// Generate 3-7 development tasks based on analysis
// Use ACE search or smart_search to find relevant patterns
const tasks = [
{
id: 'task-001',
description: 'Task description based on analysis',
tool: 'gemini',
mode: 'write',
status: 'pending',
priority: 1,
files: [],
created_at: getUtc8ISOString(),
completed_at: null
}
// ... more tasks
]
tasks = [
{
id: 'task-001',
description: 'Task description based on analysis',
tool: 'gemini',
mode: 'write',
status: 'pending',
priority: 1,
files: [],
created_at: getUtc8ISOString(),
completed_at: null
}
// ... more tasks
]
}
```
### Step 4: Initialize Progress Document

View 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

View File

@@ -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()
```
### Step 1.1: Parse Arguments
### Step 1.1: Parse Arguments & Load Prep Package
```javascript
const { loopId: existingLoopId, task, mode = 'interactive' } = options
@@ -32,6 +32,123 @@ if (!existingLoopId && !task) {
// Determine mode
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
@@ -79,14 +196,51 @@ function createLoopState(loopId, taskDescription) {
loop_id: loopId,
title: taskDescription.substring(0, 100),
description: taskDescription,
max_iterations: 10,
max_iterations: prepPackage?.auto_loop?.max_iterations || 10,
status: 'running',
current_iteration: 0,
created_at: now,
updated_at: now,
// Skill extension fields (initialized by INIT action)
skill_state: null
// Skill extension fields
// 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))