mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +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:
@@ -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 |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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()
|
||||
```
|
||||
|
||||
### 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))
|
||||
|
||||
Reference in New Issue
Block a user